ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Context
    리액트 2021. 9. 5. 20:54

    Context

    context를 사용하면 컴포넌트 트리 전체에 데이터를 공유하여, 부모부터 자식에게 데이터를 props를 통해 전달해주지 않아도 됩니다. 

     

    언제 context를 사용해야하나요?

    리액트를 사용하다면 현재 로그인한 유저, 테마(dark 혹은 light), 선호하는 언어 등 전역적으로 데이터를 공유해야 할 경우 context를 사용하면 됩니다.

     

    context를 사용하지 않은 예시입니다.

    class App extends React.Component {
      render() {
        return <Toolbar theme="dark" />;
      }
    }
    
    function Toolbar(props) {
      // Toolbar 컴포넌트는 불필요한 테마 prop를 받아서
      // ThemeButton에 전달해야 합니다.
      // 앱 안의 모든 버튼이 테마를 알아야 한다면
      // 이 정보를 일일이 넘기는 과정은 매우 곤혹스러울 수 있습니다.
      return (
        <div>
          <ThemedButton theme={props.theme} />
        </div>
      );
    }
    
    class ThemedButton extends React.Component {
      render() {
        return <Button theme={this.props.theme} />;
      }
    }

    Button만에게 현재 theme을 주어서 괜찮아 보이지만 button 뿐만 아닌 다른 컴포넌트에게 하나하나 정보를 줘야한다고 생각하면 불편할 수 있습니다.

     

    context를 사용한 예시입니다.

    // context를 사용하면 모든 컴포넌트를 일일이 통하지 않고도
    // 원하는 값을 컴포넌트 트리 깊숙한 곳까지 보낼 수 있습니다.
    // light를 기본값으로 하는 테마 context를 만들어 봅시다.
    const ThemeContext = React.createContext('light');
    
    class App extends React.Component {
      render() {
        // Provider를 이용해 하위 트리에 테마 값을 보내줍니다.
        // 아무리 깊숙히 있어도, 모든 컴포넌트가 이 값을 읽을 수 있습니다.
        // 아래 예시에서는 dark를 현재 선택된 테마 값으로 보내고 있습니다.
        return (
          <ThemeContext.Provider value="dark">
            <Toolbar />
          </ThemeContext.Provider>
        );
      }
    }
    
    // 이젠 중간에 있는 컴포넌트가 일일이 테마를 넘겨줄 필요가 없습니다.
    function Toolbar() {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    
    class ThemedButton extends React.Component {
      // 현재 선택된 테마 값을 읽기 위해 contextType을 지정합니다.
      // React는 가장 가까이 있는 테마 Provider를 찾아 그 값을 사용할 것입니다.
      // 이 예시에서 현재 선택된 테마는 dark입니다.
      static contextType = ThemeContext;
      render() {
        return <Button theme={this.context} />;
      }
    }

    context를 사용하여 중간단계에 있는 컴포넌트를 안줘도 마지막 child에서 테마값을 받아 적용할 수가 있습니다.

     

    API

    React.createContext

    const MyContext = React.createContext(defaultValue);
    • Context 객체를 만듭니다.
    • 리액트가 해당 context 객체를 구독하는 컴포넌트를 랜더링하는 경우, 상위 트리에서 나한테 가장 가까운 context 값을 Provider로부터 받아옵니다.
    • defaultValue는 트리 안에서 알맞은 Provider을 찾지 못할 때 사용됩니다.

     

    Context.Provider

    <MyContext.Provider value={/* 어떤 값 */}>
    • Context.Provider은 context를 구독하는 컴포넌트들에게 context의 변화를 알립니다.
    • Provider 컴포넌트는 value prop을 받아 하위 컴퍼넌트에게 전달합니다. 
    • Provider 하위에 다른 Provider을 배치 (하위 Provider의 값이 우선됩니다.)하는 것도 가능합니다. 이를 통해 하위 컴포넌트가 여러 context를 구독하게 만들 수 있습니다.
    • value에 객체를 보내는 경우 변화 여부를 체크하는 알고리즘에 문제가 생길 수 있어 권장하지 않습니다.

     

    Context.Consumer

    <MyContext.Consumer>
      {value => /* context 값을 이용한 렌더링 */}
    </MyContext.Consumer>
    • context 변화를 구독하는 컴포넌트입니다.
    • 함수 컴포넌트 안에서 context를 구독할 수 있습니다.

     

    Class.contextType

    class MyClass extends React.Component {
      componentDidMount() {
        let value = this.context;
        /* MyContext의 값을 이용한 코드 */
      }
      componentDidUpdate() {
        let value = this.context;
        /* ... */
      }
      componentWillUnmount() {
        let value = this.context;
        /* ... */
      }
      render() {
        let value = this.context;
        /* ... */
      }
    }
    MyClass.contextType = MyContext;
    • 원하는 클래스의 contextType 프로퍼티로 지정할 수 있습니다.
    • 클래스 안에서 this.context를 이용해 Provider를 찾아 해당 값을 읽을 수 있게 합니다.
    class MyClass extends React.Component {
      static contextType = MyContext;
      render() {
        let value = this.context;
        /* context 값을 이용한 렌더링 */
      }
    }
    • 클래스 안에 static contextType = MyContext 방식으로도 사용가능합니다.

    예시

    ./context/color.js

    import { createContext, useState } from "react";
    
    const ColorContext = createContext({
      state: { backgroundColor: "black", fontColor: "white" },
      actions: {
        setBackgroundColor: () => {},
        setFontColor: () => {},
      },
    });
    
    const ColorProvider = ({ children }) => {
      const [backgroundColor, setBackgroundColor] = useState("black");
      const [fontColor, setFontColor] = useState("white");
    
      const value = {
        state: { backgroundColor, fontColor },
        actions: { setBackgroundColor, setFontColor },
      };
    
      return (
        <ColorContext.Provider value={value}>{children}</ColorContext.Provider>
      );
    };
    
    const { ColorConsumer } = ColorContext.Consumer;
    
    export { ColorProvider, ColorConsumer };
    
    export default ColorContext;

    우선 createContext()를 통해 context 객체를 생성합니다. 

    ColorContext.Provider은 따로 컴포넌트를 만들어 커스텀을 했습니다. 

    ColorContext.Consumer을 통해 ColorConsumer이 해당 context를 구독하도록 했습니다.

     

    App.js

    import React from "react";
    import Main from "./components/Main";
    import SelectColors from "./components/SelectColors";
    import { ColorProvider } from "./contexts/color";
    
    const App = () => {
      return (
        <ColorProvider>
          <>
            <SelectColors />
            <Main />
          </>
        </ColorProvider>
      );
    };
    
    export default App;

     

    App.js에서 ColorProvider 아래에 context를 구독할 컴포넌트를 작성합니다. 아래 컴포넌트는 상위의 Provider을 구독할 것입니다. 

     

    ./components/Main.js

    import React from "react";
    import ColorConsumer from "../contexts/color";
    
    const Main = () => {
      return (
        <ColorConsumer>
          {({ state }) => (
            <>
              <div
                style={{
                  fontSize: "22px",
                  background: state.backgroundColor,
                  color: state.fontColor,
                }}
              >
                Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum
                accusantium odit fuga totam neque. Earum praesentium facilis nisi
                amet quae velit autem reprehenderit quam tenetur ratione,
                consequuntur et consectetur asperiores?
              </div>
            </>
          )}
        </ColorConsumer>
      );
    };
    
    export default Main;

     

    ColorConsumer은 ColorProvider의 매개변수 value객체를 구독합니다. 그중 {state}를 받아옵니다. style에서 state 내 backgroundColor와 fontColor의 값을 받아 올 수 있도록 합니다.

     

    ./components/SelectColors.js

    import React from "react";
    import ColorConsumer from "../contexts/color";
    
    const colors = [
      "black",
      "white",
      "red",
      "orange",
      "yellow",
      "green",
      "blue",
      "indigo",
      "violet",
    ];
    
    const SelectColors = () => {
      return (
        <div>
          <h2>색상을 선택하세요</h2>
          <ColorConsumer>
            {({ actions }) => (
              <div style={{ display: "flex" }}>
                {colors.map((color) => (
                  <div
                    key={color}
                    style={{
                      background: color,
                      width: `24px`,
                      height: "24px",
                      cursor: "pointer",
                    }}
                    onClick={() => actions.setBackgroundColor(color)}
                    onContextMenu={(e) => {
                      e.preventDefault();
                      actions.setFontColor(color);
                    }}
                  />
                ))}
              </div>
            )}
          </ColorConsumer>
          <hr />
        </div>
      );
    };
    
    export default SelectColors;

     

    위 컴포넌트는 ColorProvider에서 action을 구독합니다. onClick() 과 onContextMenu()를 통해 backgroundColor와 fontColor을 조작할 수 있도록 했습니다.

     

    결과

     

    우클릭을 하면 배경이 바뀌며, 좌클릭을 하면 글의 색이 바뀌는 것을 확인할 수 있습니다.

     

    예시 보충할 점

     

    현재 예시 내 Context.Provider의 value에 객체를 넣었습니다만, 공식문서에서는 이를 권장하지 않습니다. 어떻게 다시 수정해야할지 고민해보겠습니다!

     

    참고자료

     

    Context – React

    A JavaScript library for building user interfaces

    ko.reactjs.org

     

    리액트를 다루는 기술 - YES24

    리액트, 어떻게 활용하느냐가 중요하다!기본기를 꼼꼼하게! 효과적으로 활용하는 방법까지 다양하게 배우자!리액트를 이해하기 위한 핵심 개념은 물론이고 어떤 상황에서 어떻게 사용해야 하

    www.yes24.com

     

    '리액트' 카테고리의 다른 글

    ref & useRef() & forwardRef()  (0) 2021.09.11
    Portals  (0) 2021.09.08
    Lists & Keys & Reconciliation  (0) 2021.08.29
    JSX & createElement() & reactDOM.render()  (0) 2021.08.24
    Hooks (useState, useEffect)  (0) 2021.08.23
Designed by Tistory.