React hook의 모든 것(6) (Context와 useContext)

신은수·2023년 6월 11일
0

ReactJS

목록 보기
8/13
post-thumbnail

1. Props drilling과 전역상태관리

1) Props drilling이란?

  • 전역상태관리를 사용하지 않는 경우, 특정 컴포넌트에서의 state 변화를 다른 컴포넌트에게 전달하기 위해 여러차례 props를 전달해야한다.(이를 props drilling 이라고 부름)

    • 예를 들어, 어떤 페이지를 만드는데 Header안에 다크모드 버튼이 있다고 해보자. Header안의 다크모드 버튼을 누르면 Header, Content, Footer의 색이 다크모드로 전환이 되어야 한다.
    • React는 단방향으로만 데이터가 흐른다. (= 부모 컴포넌트에서 데이터를 props로 자식들에게 전달은 가능하나, 자식들간에 props교환은 안된다.)
      -> 만약에 전역상태관리를 사용하지 않으면, App.js에서 다크모드 상태를 선언하고 자식 컴포넌트로 여러 차례 전달해줘야한다(props drilling) App.js에서 Page1으로, Page1에서 Page1의 자식 컴포넌트인(Header, Content,Footer)로


2) 전역상태관리

  • 이러한 props drilling을 막기 위해 전역적으로 상태를 관리 할 수 있다.
    (전역상태관리를 하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.)
  • 전역적으로 상태를 관리할 수 있는 방법에는 Context API, Redux, Recoil, ReactQuery 등 여러가지가 있다.


2. Context API 사용해보기

1) 코드로 이해해보는 Context API 사용법

Context API 사용법

  • createContext라는 함수를 사용해서 Context 만든다.
    (createContext라는 함수를 호출하면 Provider와 Consumer 라는 컴포넌트들이 반환된다.)
  • Provider는 Context에서 사용 할 값을 설정할 때 사용되고, Consumer는 나중에 우리가 설정한 값을 불러와야 할 때 사용된다.
  • ThemeContext.js

    import {createContext} from "react"
    export const ThemeContext = createContext(null)  
  • App.js

    import {ThemeContext} from "./context/ThemeContext"
    function App(){
        const [isDark, setIsDark] = useState(false)
        return (
            <ThemeContext.Provider value={{isDark, setIsDark }}>
                 <Page />
            </ThemeContext.Provider>
        )
    }
  • Page.js

    function Page(){
      return (
            <div>
                <Header/>
                <Content />
                <Footer />
            </div>
      )
    }
  • Footer.js

    import {ThemeContext} from "./context/ThemeContext"
    
    function Footer(){
      return (
          <ThemeContext.Consumer>
               {
                  ({isDark, setIsDark}) => (
                    <div style={{backgroundColor: isDark ? "black" : "white"}}>
                        <button onClick={()=>setIsDark(!isDark)}>다크모드 버튼</button>
                      </div>
                  )
                }
          </ThemeContext.Consumer>
      )
    }

2) useContext 훅 사용하기

  • context를 사용하기 위해 render props 패턴을 사용하는 것이 조금 이상해 보일 수 있다. (render props 패턴 이해하기)

  • 리액트 훅이 도입된 리액트 16.8부터는 context를 사용하는 다른 방법이 등장했다. 이제는 useContext 훅을 사용해 context를 사용할 수 있게 되었다.

  • Footer.js

    import {useContext} from "react"
    import {ThemeContext} from "./context/ThemeContext}
    function Footer(){
      const {isDark, setIsDark}=useContext(ThemeContext);
      return (
        <div style={{backgroundColor: isDark ? "black" : "white"}}>
          <button onClick={()=>setIsDark(!isDark)}>다크모드 버튼</button>
        </div>
      )
    }


3. Context 쓸 때 주의점!

1) 단순히 PropsDrilling을 막기위한다면 Context대신 컴포넌트 합성을 쓰자!

  • 공식문서에 따르면 context를 사용하면 컴포넌트를 재사용하기가 어려워지므로 꼭 필요할 때만 써야한다고 한다.

  • 단순히 props drilling을 하지 않기 위해 context를 사용하는 것은 좋지 않고, 이런 경우 컴포넌트를 재사용하기가 어려워지므로이 더 간단한 해결책일 수도 있다고 한다.

  • props drilling을 해결하기 위해 컴포넌트 합성을 이용한 예시

    • 예시1) 컴포넌트 합성 전

      const Hello1 = (props) => (
        <div>
          this is Hello1.
          <Hello2 name={props.name} /> 
        </div>
      );
      
      const Hello2 = (props) => (
        <div>
          this is Hello2.
          <Hello3 name={props.name} />
       </div>
      );
      
      const Hello3 = (props) => (
        <div>
          this is Hello3.
          <Hello4 name={props.name} />
       </div>
      );
      
      const Hello4 = (props) => (
        <div>
          this is Hello4.
          <div>Hello {props.name}!</div>
       </div>
      );
      
      // App.tsx
      <Hello1 name='asdf' />

      -> 단지 에서 name을 사용하기 위해 props drilling을 통해 name을 출력하게 된다.

    • 예시2) 컴포넌트 합성을 통해 해결

       const Hello1 = (props) => (
         <div>
             this is Hello1.
             {props.children} 
         </div>
       );
      
       const Hello2 = (props) => (
         <div>
           this is Hello2.
           {props.children} 
         </div>
       );
      
       const Hello3 = (props) => (
         <div>
           this is Hello3.
           {props.children} 
         </div>
       );
      
       const Hello4 = (props) => (
         <div>
           this is Hello4.
           <div>Hello {props.name}!</div> 
         </div>
       );
      
       // App.jsx
       const name = "은수";
       <Hello1>
         <Hello2>
               <Hello3>
                   <Hello4 name={name} >
           </Hello3>
         </Hello2>
       </Hello1>

      -> 최상위 App.jsx 에서 모든 컴포넌트를 중첩하여 합성 컴포넌트 형태로 작성한다면, 나머지 컴포넌트에서는 props.children으로 랜더링을 해주고, 으로만 name을 전달해줄 수 있다. 즉, name의 값을 알아야하는건 App.jsx 뿐이다. (다른 component들은 name을 전달받지 않음.)

      -> 하지만 이렇게 간단한 로직이 아닌 복잡한 로직을 상위에 작성한다면, 오히려 더 난해해질 수 있으니 각자의 로직에 알맞는 방법을 적용해야할 것이다.

2) Provider에 제공한 value가 달라지면 useContext를 쓰고 있는 모든 컴포넌트가 리렌더링 된다는 것

value가 객체 일 때 하나만 바뀌어도 그 하나를 쓰지 않는 다른 자식 컴포넌트도 리렌더링 된다.

따라서 context분리가 필요하다.

(이내용은 더 알아봐야할 것 같다)


useContext 정리

  • props drilling을 막기위해서 전역상태관리 도구를 사용해야하고 ContextAPI, Redux, Recoil, ReactQuery등을 쓸 수 있다.
  • 원래는 render props 패턴으로 전역상태를 사용할 수 있었는데, useContext라는 React Hook이 나와서 useContext를 써서 전역상태를 사용할 수 있게 되었다.
  • 단순히 props drilling을 막기위해서 Context를 쓰기보다는, 컴포넌트 합성도 고려해보자.

출처

React | React.Context 를 활용한 전역 상태 관리
React Hooks! useContext편(React 17)

profile
🙌꿈꾸는 프론트엔드 개발자 신은수입니당🙌

0개의 댓글