• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    해결됨

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

24.03.16 16:29 작성 24.03.16 16:31 수정 조회수 125

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

질문자

2024.03.17

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

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