해결된 질문
작성
·
283
·
수정됨
1
안녕하세요 선생님 Count, PlusButton이 re render 되는 조건을 알고 싶습니다.
예전예시에서는
class Consumer extends React.Component {
constructor(props) {
super(props);
this.state = {
value: emitter.get(),
};
this.setValue = this.setValue.bind(this);
}
setValue(nextValue) {
this.setState({ value: nextValue });
}
componentDidMount() {
emitter.on(this.setValue);
}
componentWillUnmount() {
emitter.off(this.setValue);
}
render() {
return <>{this.props.children(this.state.value)}</>;
}
}
Consumer가 state를 가지고 있음으로
순서가
Provider render -> Consumer render -> Consumer componentDidMount -> Provider componentDidMount (set 을 통해 빈 객체였던 것을 value, setValue로 바꿔줌)
이때 Consumer state는 emitter.get()임으로 변경된 것을 감지 하고 re render 하는 것으로 이해했었습니다.
헌데 이번예시에서는
function useContext(context) {
console.log("userContext, context.emitter.get() = ", context.emitter.get());
const [value, setValue] = React.useState(context.emitter.get());
React.useEffect(() => {
console.log("Consumer useEffect");
context.emitter.on(setValue);
return () => {
console.log("Consumer useEffect clean");
context.emitter.off(setValue);
};
}, [context]);
return value;
}
const Count = () => {
console.log("Count render");
const { count } = MyReact.useContext(countContext);
return <div>{count}</div>;
};
Provider render -> Count(Consumer) render -> Count's useEffect -> Provider's useEffect 을 통해
emitter 값이 빈객체에서 count, setCount로 채워지는것은 이해하였습니다.
이때 Count 가 다시 한번 re render되는데 왜 그런 것인가요?
첫번째 예시처럼 state?같은게 존재하는건가요?
다시 render되는 조건이 궁금합니다.
답변 2
1
제가 질문을 엉뚱하게 드린 것 같습니다.
class component의 경우 자신의 state 변수가 변경이 일어나면 해당 class component와 child들이 순서대로 rendering이 다시 일어나게 되는데
function useContext(context) {
console.log("userContext, context.emitter.get() = ", context.emitter.get());
const [value, setValue] = React.useState(context.emitter.get());
React.useEffect(() => {
console.log("Consumer useEffect");
context.emitter.on(setValue);
return () => {
console.log("Consumer useEffect clean");
context.emitter.off(setValue);
};
}, [context]);
return value;
}
useContext function component 에서 사용된 useState의 state가 변경이 되는 경우를 살펴보면
const Count = () => {
console.log("Count render");
const { count } = MyReact.useContext(countContext);
return <div>{count}</div>;
};
Provider의 useEffect 의 emitter set을 통해 emitter 값이 빈객체에서 제대로 채워지게 되는데
이때 변경된 것은 useContext의 state인데 왜 Count가 re render되는지 궁금했습니다.
정리하면 함수 컴포넌트에서 state가 변경될 때 re render되는 범위 및 순서가 궁금하였습니다.
1
MyReact.useContext() 때문입니다.
Count에서 처음 호출될때 처음 컨택스트 값을 가져옵니다.
const [value, setValue] = React.useState(context.emitter.get());
아직 프로바이더가 값이 제공하기 전이라 폴백으로으로 전달한 빈 객체가 전달 될거에요. Count는 이 컨택스트에서 값을 찾기 때문에 undefined로 처음 렌더합니다.
useContext는 context값에 따라 부수효과를 실행하는데요. 프로바이더가 제공한 context 값이 바뀌어 동작합니다. 컨택스트에게 값이 바뀌면 setValue를 호출하도록 요청하고. 컨택스트가 이 함수를 실행하면 상태가 바뀌면서 다시한번 value가 바뀌게 됩니다. Count가 두번째 레더합니다.
실제 리액트의 훅은 한 번만 렌더하는데요. 마이리액트는 리액트 동작 이해를 위한 용도로 봐주시면 좋겠습니다.
기존 실습 코드에서 두 번 렌더되는 현상을 해결한 코드입니다.
createContext의 인자를 컨택스트의 폴백(fallback) 값으로 사용합니다. 기존에는 이 값으로 에미터를 초기화 했습니다. 그리고 props.value로 에미터의 값을 바꾸는 방식이었습니다. 이것을 에미터의 상태를 바꾸고 구독 함수를 두 번 호출해 리렌더까지 영향주는 원인이었습니다.
// 인자이름을 defaultValue로 바꾸었습니다.
// 프로바이더 컴포넌트로 감싸지 않아 값을 주입하지 못할 경우 기본값으로 사용되기 때문입니다.
function createContext(defaultValue) {
// 에미터 생성을 늦춥니다.
// 기존에는 defaultValue로 객체를 생성했습니다. (문제 원인)
let emitter;
function Provider({ value, children }) {
// 에미터 객체를 props.value로 생성합니다.
if (!emitter) {
emitter = createEventEmitter(value);
}
React.useEffect(() => {
emitter.set(value);
}, [value]);
return <>{children}</>;
}
// 컨택스트 값을 조회합니다.
// 에미터에서 조회하고 프로바이더로 감싸지 않아 값을 제공받지 못하면 지정한 기본 값을 반환합니다.
function getValue() {
return emitter ? emitter.get() : defaultValue;
}
// 컨택스트 값 변화를 구독합니다.
// 기존에는 emitter 객체를 노출했지만 undefined일 경우가 있어 방어 로직이 있는 함수를 정의했습니다.
function on(handler) {
if (emitter) {
emitter.on(handler);
}
}
// 컨택스트 값 변화를 구독 취소합니다.
// 기존에는 emitter 객체를 노출했지만 undefined일 경우가 있어 방어 로직이 있는 함수를 정의했습니다.
function off(handler) {
if (emitter) {
emitter.off(handler);
}
}
return {
Provider,
getValue,
on,
off,
};
}
이를 사용한 useContext 훅도 변경했습니다. 폴백값이 아닌 프롭스를 통해 들어온 컨택스트 값을 이용해 상태를 만들었습니다. 컨택스트가 바뀌면 이 상태도 업데이트하여 훅을 사용하는 컴포넌트가 다시 그려지도록 의도한 코드입니다.
function useContext(context) {
// 컨택스트 값으로 상태를 정의합니다.
const [value, setValue] = React.useState(context.getValue());
React.useEffect(() => {
// 컨택스트 값을 구독합니다.
// 값이 바뀌면 value를 변경해 이 훅을 사용하는 컴포넌트를 다시 그립니다.
context.on(setValue);
return () => {
// 컨택스트 구독을 취소합니다.
context.off(setValue);
};
}, [context]);
return value;
}
useContext가 제공하는 상태를 Count 컴포넌트가 사용하기 때문입니다.
상태훅에서 다루긴했는데요. 상태가 바귀면 이를 사용한 컴포넌트도 다시 그려주는 것이 리액트 상태 훅의 역할이었습니다.
MyReact.useContext도 상태훅 기반으로 실습했는데요. 이 상태를 제공하는 커스텀 훅인 셈이에요. 이 상태가 바뀌면 이를 사용하는 측에서도 렌더링되는 겁니다.