인프런 커뮤니티 질문&답변

dohyun_lim님의 프로필 이미지
dohyun_lim

작성한 질문수

[리액트 2부] 고급 주제와 훅

[3.5장 컨택스트 훅] 3.5.2 useContenxt

[3.5장 컨택스트 훅] 3.5.2 useContenxt 에서 질문이 있습니다.

해결된 질문

작성

·

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

dohyun_lim님의 프로필 이미지
dohyun_lim
질문자

제가 질문을 엉뚱하게 드린 것 같습니다.

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>;
};

imageProvider의 useEffect 의 emitter set을 통해 emitter 값이 빈객체에서 제대로 채워지게 되는데

이때 변경된 것은 useContext의 state인데 왜 Count가 re render되는지 궁금했습니다.


정리하면 함수 컴포넌트에서 state가 변경될 때 re render되는 범위 및 순서가 궁금하였습니다.

김정환님의 프로필 이미지
김정환
지식공유자

useContext가 제공하는 상태를 Count 컴포넌트가 사용하기 때문입니다.

const { count } = MyReact.useContext(countContext);

상태훅에서 다루긴했는데요. 상태가 바귀면 이를 사용한 컴포넌트도 다시 그려주는 것이 리액트 상태 훅의 역할이었습니다.

MyReact.useContext도 상태훅 기반으로 실습했는데요. 이 상태를 제공하는 커스텀 훅인 셈이에요. 이 상태가 바뀌면 이를 사용하는 측에서도 렌더링되는 겁니다.

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;
  }
dohyun_lim님의 프로필 이미지
dohyun_lim

작성한 질문수

질문하기