Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
수강자 분께서 MyReact로 컨택스트를 구현하는 과정에 질문을 주셨습니다.
간단히 말해 "왜 MyReact의 컨택스트를 사용하면 컴포넌트를 두 번 렌더하는가?" 입니다. 컨택스트 값이 바뀌지 않으면 한 번만 렌더링 되어야하는데 말이죠.
재현하기
문제를 재현해 봅시다. 2-hook/src/App.jsx 파일을 아래 코드로 교체합니다.
countContext를 만듭니다. 값을 제공하는 CountProvider와 값을 소비하는 Count 컴포넌트를 만들어 조합했습니다. 컨택스트를 소비하는 Count 컴포넌트를 렌더링할 때마다 로그를 출력할 것입니다.
npm start --workspace 2-hook 으로 개발 서버를 띄고오 브라우져로 접속해서 확인해 봅니다.
로그가 두 번 찍혔습니다. 컨택스트를 소비하는 컨슈머 컴포넌트가 두 번 렌더링 되었습니다.
테스트로 재현하기
테스트 코드로 문제를 재현해 보겠습니다. 문제를 곧장 해결할수도 있습니다만 테스트를 만들어 두는 것이 좋겠습니다. 나중에 이 부분을 바꾸더라도 문제 재발을 예방할 수 있기 때문입니다.
2-hook/test/MyReact.test.jsx 파일을 만들어 테스트 코드를 작성합니다. cc0dbeb
npm test --workspace 1-hook 명령어로 테스트를 실행합니다.
테스트가 실패합니다. 자 이제 문제를 해결할 준비를 마쳤습니다.
문서 확인
리액트 컨택스트를 잘못 이해한 것은 아닐까요? 공식 문서를 다시 확인해 보았습니다.
createContext(defaultValue) 의 인자가 기본 값입니다. 리액트 렌더 트리에서 컨택스트 제공자를 찾지 못할 경우 사용할 값입니다. 컨택스트를 사용할 때 실수로 제공자 컴포넌트를 잊을 경우 최수의 수단으로 이 값을 사용한다고 합니다.
우리가 구현했던 MyReact는 이 값을 초기값(initialValue)으로 사용했습니다.
initialValue 인자로 이벤트 에미터를 만들었습니다. 곧장 구독 객체에게 이 값이 전달될 것입니다. Provider는 props.value가 바뀔 때 마다 이 값을 구독 객체에게 전달합니다. 구독 객체가 리액트 컴포넌트라면 두 번 렌더링 될 것입니다. 문제의 원인입니다.
해결하기
createContext의 인자를 initialValue가 아니라 defaultValue로 바꾸겠습니다.
defaultValue 인자로 이벤트 에미터 객체를 만들지 않고 Provider에게 객체 생성 역할을 맡겼습니다. 컨택스에 제공할 값을 props.value로 받는 함수이기 때문입니다.
이전에는 에미터 객체가 항상 있다고 가정하고 값을 직접 조회했습니다. 이제 이벤트 에미터를 Provider가 생성하기 때문에 객체가 없는 경우도 챙겨야합니다. getValue()는 이벤트 에미터 객체를 확인하고 값을 조회하거나 기본 값을 제공하는 함수 입니다.
컨택스트를 소비하는 방식도 바뀐 이터페이스에 맞게 수정합니다.
컨택스트의 getValue() 함수로 값을 조회했습니다. 컨택스트에 이벤트 에미터가 없으면, 다시 말해 컨택스트 제공자를 찾지 못하면 기본값을 반환할 것입니다.
변경사항: 079fff7
문제 해결 확인
문제를 수정했으니 다시 테스트를 실행해 보겠습니다. npm test --wordspace 2-hook
테스트를 통과했습니다.
브라우져도 확인해 보면 로그를 한번만 찍습니다.
컨택스트 소비자를 사용하는 Count 컴포넌트를 한 번만 렌더했습니다. 불필요한 렌더링 동작을 제거했습니다.
결론
기존에 구현한 MyReact.createContext()가 미흡했습니다. 인자를 initialValue로 받았기 때문입니다. 이 값으로 이벤트 에미터를 만들어 컨택스트가 맨 처음 제공합니다. Provider가 props.value로 값을 받으면 이를 컨택스트로 다시 제공합니다. 값이 두 번 바뀌기 때문에 소비하는 컴포넌트에서는 두 번 렌더하는 현상이었습니다.
리액트 문서를 보고 문제를 고쳤습니다. createContext()의 인자는 기본값 역할입니다. 제공자를 찾지 못할 경우 마지막 값으로 사용합니다.
이벤트 에미터를 곧장 만들지 않습니다.대신 Provider가 생성합니다. Provider를 실수로 사용하지 않으면 이벤트 에미터는 없을 것입니다. 이 경우를 예방하기 위해 기본값을 사용하는 getValue()를 제공했습니다.