[2단계 - 행운의 로또 미션] 빌리(정환희) 미션 제출합니다.#378
Conversation
JunilHwang
left a comment
There was a problem hiding this comment.
안녕하세요 빌리! 리뷰가 너무 늦어졌네요 ㅠㅠ 죄송합니다.
개인적인 사정이 있다보니... 송구스럽네요.
여튼 미션 잘 진행해주신 것 같아요!
설계에 대한 부분은 큰 이견은 없습니다 ㅎㅎ 다만 디테일한 부분에 대해 리뷰를 몇 가지 남겼으니 확인 부탁드려요!
리액트와 최대한 비슷하게 구현하려고 노력했는데, 더 개선해볼 부분이 있는지 궁금합니다.
개선이라기보단, 모든 부분을 class component로 표현하지 않아도 무방하답니다!
그냥 문자열 템플릿으로 표현해도 되는 부분이 많아보여요.
state 변경과 UI 업데이트를 더 선언적으로 연결할 방법이 있는지 함께 논의해보고 싶습니다.
이건 빌리가 상상하는 모습을 보여주시면 좋겠어요! 어떤 모습을 상상하고 있나요?
src/constants/rules.js
Outdated
| export const Lotto = { | ||
| SIZE: 6, | ||
| }; |
There was a problem hiding this comment.
Lotto에 대한 상수는 Lotto Class 내부에 정의되어있으면 어떨까요?
class Lotto {
static SIZE = 6
}이렇게요!
| export const LottoShop = { | ||
| PRICE_UNIT: 1_000, | ||
| MIN_PURCHASE_AMOUNT: 1_000, | ||
| MAX_PURCHASE_AMOUNT: 100_000, | ||
| }; | ||
|
|
||
| export const MIN_PURCHASE_AMOUNT = 1_000; | ||
| export const MAX_PURCHASE_AMOUNT = 100_000; | ||
| export const LottoNumber = { | ||
| MIN: 1, | ||
| MAX: 45, | ||
| }; |
There was a problem hiding this comment.
이 친구들도 사실 상수가 아니라 도메인 클래스로 정의되어야할 것 같아요 ㅎㅎ
There was a problem hiding this comment.
엇 그러면
class LottoShop {
static PRICE_UNIT = 1_000;
static MIN_PURCHASE_AMOUNT = 1_000;
static MAX_PURCHASE_AMOUNT = 100_000;
}이렇게 만들어야 하나요?? 새로운 도메인 class가 도입됨에 따라서 역할을 다시 분배해야 하는지 궁금합니다!
src/step2-index.js
Outdated
| document.addEventListener("DOMContentLoaded", () => { | ||
| new App(document.querySelector("#app")); | ||
| }); |
There was a problem hiding this comment.
DOMContentLoaded 내부에서 로직을 실행하게 되면 문제가 발생할 수 있어요 ㅎㅎ
코드를 로딩하는 시점이 이미 DOMContentLoaded 이벤트가 발생한 이후일 경우, 아예 이 구간으로 진입을 못한답니다.
어차피 module 방식으로 불러오고 있어서, 그냥 이 이벤트는 사용하지 않는게 더 안전할 것 같아요.
|
|
||
| this.setup(); | ||
| this.initialRender(); | ||
| } |
There was a problem hiding this comment.
멤버변수로 쓰이는 것들은 클래스 필드에 정의해주세요!
그리고 다 pubilc으로 정의되어 있는데, 접근 제한자도 지정해주시면 좋을 것 같습니다.
src/ui/components/core/Component.js
Outdated
| componentDidMount() {} | ||
| componentWillUpdate() {} | ||
| componentDidUpdate(changedKeys) {} | ||
| componentWillUnmount() {} |
There was a problem hiding this comment.
componentWillUnmount 는 아예 안 쓰이고 있네요..!
| bonusNumber: "", | ||
| isModalOpen: false, | ||
| }; | ||
| } |
There was a problem hiding this comment.
클래스와 상수를 조화롭게 사용할 수 있답니다.
class LottoGame extends Component {
static TITLE = "🎱 내 번호 당첨 확인 🎱";이렇게 사용할 수 있어요.
LottoGame.TITLE
|
|
||
| setIsModalOpen(openModal) { | ||
| this.setState({ isModalOpen: openModal }); | ||
| } |
There was a problem hiding this comment.
isModalOpen -> opened
이 표현이 더 직관적이지 않을까요!?
|
|
||
| close() { | ||
| this.props.onClose(); | ||
| } |
There was a problem hiding this comment.
이 부분이 조금 어색하게 느껴져요.
close란 "닫는다" 라는 행위인데
실제로는 onClose를 실행해주는게 전부네요 ㅎㅎ
이걸 close 라고 부를 수 있는걸까요?
이벤트는 "어떤 일이 발생했을 때 알려주는 것" 이라고 생각해요.
지금은 "이 일을 해줘!" 같은 느낌으로 쓰이고 있습니다.
| super.setup(); | ||
| this.events = { | ||
| ...this.events, | ||
| "click@.modal__restart": this.handleClickRestart, |
There was a problem hiding this comment.
이벤트에서 정의하는 selector를 상수로 만들어서 템플릿에서도 사용해주면 어떨까요!?
|
|
||
| handleClickRestart() { | ||
| this.props.onRestart(); | ||
| } |
|
@JunilHwang 안녕하세요, 해먼드! 😊 코멘트 주신 부분도 반영하여 코드 변경 사항이 살짝 생겼는데, 다시 한 번 확인해주시면 감사하겠습니다! 🙌 따뜻한 연휴 되시길 바라요! |
JunilHwang
left a comment
There was a problem hiding this comment.
안녕하세요 빌리!
watchState에 대한 아이디어는 무척 좋네요 ㅎㅎ
여기에 DX(개발자의 경험)을 더 고려해서, 수동으로 값을 watching 하는게 아니라 자동으로 watching 하는 방법도 고민해보시면 좋을 것 같아요!
이 외에 이벤트 네이밍에 대한 피드백을 반복적으로 남겼는데요
이벤트에 대해 고민해보시면 좋겠어요.
이벤트는 특정 행위가 발생했을 때 일어나는 일이지, 행위를 실행해주는건 아니랍니다.
onOpen -> open이 되었을 때 발생하는 이벤트
현재 빌리가 쓰는 모습 -> onOpen 으로 아예 open 시켜버림
onSubmit -> submit이 발생했을 때 일어나는 일
onClick -> 클릭이 발생했을 때 일어나는 일
FE에서는 컴포넌트간의 소통을 할 때 주로 이벤트를 통해서 하곤 합니다. 그런데 이벤트에 대해 제대로된 기준을 세워놓지 않으면 다른 사람들이 작성한 코드를 가져다 사용할 때 굉장히 힘들어요 ㅠㅠ
빌리가 만든 컴포넌트를 다른 사람이 가져다 쓴다고 했을 때
onOpen을 보고 "알아서 오픈되는 기능이 있구만!?" 이라고 생각할텐데 실제로는 아무 일도 일어나지 않는다고 생각해보세요 ㅎㅎ
네이밍에 대한 기준은 오픈소스를 다양하게 참고해보시면 좋답니다.
혹은 직접 "이벤트"와 "함수"가 어떤 차이인지 고민해보시면 좋겠어요.
로또 미션은 여기서 마무리하도록 하겠습니다.
짧은 기간이었지만 조금이나마 빌리에게 도움이 되었길 바랍니다.
고생하셨습니다!
| evaluate(lotto) { | ||
| const matchCount = lotto.matchCount(this.#winningLotto.getNumbers()); | ||
| const isBonusMatched = lotto.hasBonus(this.#bonusNumber); | ||
| const isBonusMatched = lotto.has(this.#bonusNumber); |
| /** 상태 변경 감시 */ | ||
| watchState(stateKey, callback) { | ||
| this.#stateToUIMap[stateKey] = callback; | ||
| } |
There was a problem hiding this comment.
변경되는 상태를 자동으로 감시하는 방법은 없을까요 ㅎㅎ?
지금은 수동으로 watch를 해야 하는데, 자동으로 하는 방법도 고민해보시면 좋을 것 같아요!
| this.validation = useUIValidation(); | ||
| this.events = { | ||
| [`submit@${LottoForm.SELECTOR.FORM}`]: this.submit.bind(this), | ||
| [`input@${LottoForm.SELECTOR.PRICE}`]: this.activateButton.bind(this), |
| lottoBundle: this.state.lottoBundle, | ||
| }); | ||
| new WinningNumbersForm(document.querySelector("#winning-numbers-form"), { | ||
| onModalOpen: () => this.setOpened(true), |
There was a problem hiding this comment.
onModalOpen이 의미하는 것은 "모달이 오픈되었을 때 실행한다"인데, 지금은 "모달 오픈"함수를 직접적으로 실행하네요 ㅎㅎ
이게 이벤트로서의 기능을 하는게 맞을까요?
| const modalRoot = document.querySelector("#modal-root"); | ||
| if (this.state.opened) { | ||
| new ResultModal(modalRoot, { | ||
| onClose: () => this.setState({ opened: false }), |
| if (this.state.opened) { | ||
| new ResultModal(modalRoot, { | ||
| onClose: () => this.setState({ opened: false }), | ||
| onRestart: () => this.resetGame(), |
|
|
||
| return ` | ||
| <header id="header" class="text-title"> | ||
| <h1>${TITLE}</h1> |
| }; | ||
|
|
||
| setup() { | ||
| this.validation = useUIValidation(); |
There was a problem hiding this comment.
setup을 할 때 마다 useUIValidation을 통해 함수를 만들어내고 있어요.
불필요한 연산이라고 생각합니다.. ㅠ
학습 목표
이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 합니다.
제출 전 체크 리스트
리뷰 요청 & 논의하고 싶은 내용
1) 이번 단계에서 가장 많이 고민했던 문제와 해결 과정에서 배운 점
1. 💡 상태(state) 변경과 UI 업데이트를 어떻게 연결할 것인가?
이번 미션에서 가장 고민했던 부분은
setState()를 호출할 때, UI 전체를 다시 렌더링하지 않고 변경된 부분만 업데이트하는 방법이었습니다.초기 구현에서는
setState()를 호출하면this.target.innerHTML = this.template()가 실행되며 전체 UI가 다시 렌더링되었습니다.그러나 변경된 state에 의존하는 동작만 다시 렌더링되어야 하는데, 불필요한 렌더링이 발생하는 문제가 있었습니다.
🛠 해결 과정
아이디어: state와 UI 업데이트 동작을 미리 연결하자!
React에서는 가상 DOM을 사용해서 변경된 부분을 diffing 알고리즘으로 비교하고, 변경된 부분만 효율적으로 업데이트합니다.
대신 저는
watchState()를 통해 특정 state가 변경됐을 때 실행할 동작을 미리 연결하는 방식으로 해결했습니다.price가 변경되면renderLottoUI()실행opened가 변경되면toggleModal()실행stateToUIMap도입)#stateToUIMap에 저장setState()가 실행되면, 변경된 키(changedKeys)만 찾아서 해당하는 UI 업데이트 실행이 방식 덕분에 UI 변경이 좀 더 명확하게 연결되고, 불필요한 렌더링을 줄일 수 있었습니다.
2) 이번 리뷰를 통해 논의하고 싶은 부분
✅ 리뷰어 체크 포인트
1단계
2단계