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

[3.5장 컨텍스트 훅] , custom hook

Tony Lim 2024. 3. 16. 13:46
  function createContext(initialValue) {
    const emitter = createEventEmitter(initialValue);

    function Provider({ value, children }) {
      React.useEffect(() => {
        emitter.set(value);
      }, [value]);

      return <>{children}</>;
    }

    return {
      Provider,
      emitter,
    };
  }

  function useContext(context) {
    const [value, setValue] = React.useState(context.emitter.get());

    React.useEffect(() => {
      context.emitter.on(setValue);
      return () => context.emitter.off(setValue);
    }, [context]);

    return value;
  }

createContext에서 init value가 지정된 emitter를 생성한다. 

Provider에서는 넘겨받은 value가 변화가 생기면 emitter 에 등록된 callback들을 변화된value를 인자로
주어 호출하게 된다.
(즉 초기에 넘겨받은 value를 Conusmer가 쓸 수 있도록 set 해주는 역할이다.)

useContext 즉 Consumer 역할이다. emitter의 value값에 대하여 setValue 함수를 callback으로 emitter에게 등록을 한다.

 

const countContext = MyReact.createContext({});

const CounterProvider = ({ children }) => {
  const [count, setCount] = React.useState(0);
  const value = { count, setCount };
  return (
    <countContext.Provider value={value}>{children}</countContext.Provider>
  );
};

const Count = () => {
  const { count } = MyReact.useContext(countContext);
  return <div>{count}</div>;
};

const PlusButton = () => {
  const { count, setCount } = MyReact.useContext(countContext);
  const handleClick = () => {
    setCount(count+1);
  };
  return <button onClick={handleClick}>count plus</button>;
};

export default () => (
  <CounterProvider>
    <Count></Count>
    <PlusButton></PlusButton>
  </CounterProvider>
);

CounterProvider가 countContext Provider를 하위 component에게 제공한다.

이를 Consume하는 Count, PlustButton은 useContxt를 통해 Provider에서 전달해준 count, setCount를 사용하게 된다.

Count는 count prop을 render , PlusButton은 setCount를 통해 값을 증가시킨다. 

첫 context.emitter.get에서는 createContext에서 주어진 {} 빈객체가 들어가 있는것을 확인 할 수 있다.

이후 Consumer useEffect에서 emitter.on을 통해 useState를 빈 객체로 초기화하고 return으로 나온 setValue를 등록한다.

Provider useEffect에서 emitter.set을 통해 CountProvider에서 전달받은 value를 consumer에게 제대로 set하게된다.

이때 왜 state도 없는 Count, PlusButton이 render가 다시될까? 조건이 뭘까?

https://www.inflearn.com/questions/1208635/3-5%EC%9E%A5-%EC%BB%A8%ED%83%9D%EC%8A%A4%ED%8A%B8-%ED%9B%85-3-5-2-usecontenxt-%EC%97%90%EC%84%9C-%EC%A7%88%EB%AC%B8%EC%9D%B4-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4

 

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

안녕하세요 선생님 Count, PlusButton이 re render 되는 조건을 알고 싶습니다.예전예시에서는 class Consumer extends React.Component { constructor(props) { super(props);...

www.inflearn.com

 


custom hook의 조건은 내부에서 react hook을 쓰기만 하면 된다.

export const useNavigate = () => {
  const { path, changePath } = useContext(routerContext);
  const navigate = (nextPath) => {
    if (path !== nextPath) changePath(nextPath);
  };
  return navigate;
};

// const navigate = useNavigate()
// navigate("/cart")

export const useMatch = () => {
  const { path } = useContext(routerContext);
  const match = (comparedPath) => path === comparedPath;
  return match;
};

export const useParams = () => {
  const params = () => {
    const params = new URLSearchParams(window.location.search);
    const paramObject = {};
    for (const [key, value] of params) {
      paramObject[key] = value;
    }
    return paramObject;
  };
	return params
};

useParams는 내부에서 react hook을 사용하지 않기 때문에 custom hook이 아니다.

기존의 고차 컴포넌트를 함수 컴포넌트에서 사용하기 편하게 변경한것이다.

export const withRouter = (WrappedComponent) => {
  const WithRouter = (props) => (
    <routerContext.Consumer>
      {({ path, changePath }) => {
        const navigate = (nextPath) => {
          if (path !== nextPath) changePath(nextPath);
        };

        const match = (comparedPath) => path === comparedPath;

        const params = () => {
          const params = new URLSearchParams(window.location.search);
          const paramObject = {};
          for (const [key, value] of params) {
            paramObject[key] = value;
          }
          return paramObject;
        };

        const enhancedProps = {
          navigate,
          match,
          params,
        };
        return <WrappedComponent {...props} {...enhancedProps} />;
      }}
    </routerContext.Consumer>
  );
  WithRouter.displayName = `WithRouter(${getComponentName(WrappedComponent)})`;
  return WithRouter;
};