Context

September 03, 2020

정의

  • React 컴포넌트 트리 안에서 데이터를 쉽게 공유하는 방법중 하나이다.

  • 컴포넌트가 깊어지면 props를 내리는데 리소스 증가에 따라 사용되는 방법 중 하나

    // context 사용전
    class App extends React.Component {
        render() {
            return <Toolbar theme="dark" />
        }
    }
    
    function Toolbar(props) {
        return (
            <div>
                <ThemedButton theme={props.theme} />
            </div>
        )
    }
    
    class ThemedButton extends React.Component {
        render() {
            return <Button theme={this.props.theme} />
        }
    }
    
    // context 사용 후
    const ThemeContext = React.createContext('light')
    
    class App extends React.Component {
        render() {
            return (
                <ThemeContext.Provider value="dark">
                    <Toolbar />
                </ThemeContext.Provider>
            )
        }
    }
    
    // props 과정이 사라짐
    function Toolbar() {
        return (
            <div>
                <ThemedButton />
            </div>
        )
    }
    
    // 컨텍스트를 활용하여 value 접근
    class ThemedButton extends React.Component {
        static contextType = ThemeContext
        render() {
            return <Button theme={this.context} />
        }
    }

    Context를 사용하기 전에 고려할 것

    1. Context에 의존성이 높아지므로 재사용이 어려워짐.
    • context보다는 컴포넌트 합성으로 문제점 해결(props.children)
    1. IOC(제어의 역전)를 이용하여 내리는 props 줄일 수 있으므로, props를 여러개 내리지 않아도 된다.
    • 장점 : props는 수는 줄고 최상위 컴포넌트의 제어력이 커지기 떄문에 클린코드 작성 가능
    • 단점 : 복잡한 로직이 상위에 있기 때문에, 상위컴포넌트는 복잡성이 증대되고, 하위 컴포넌트는 필요이상으로 유연해져야 한다.
    function Page(props) {
      const user = props.user;
      const userLink = (
        <Link href={user.permalink}>
          <Avatar user={user} size={props.avatarSize} />
        </Link>
      );
      return <PageLayout userLink={userLink} />;
    }
    
    // 이제 이렇게 쓸 수 있습니다.
    <Page user={user} avatarSize={avatarSize} />
    // ... 그 아래에 ...
    <PageLayout userLink={...} />
    // ... 그 아래에 ...
    <NavigationBar userLink={...} />
    // ... 그 아래에 ...
    {props.userLink}
    1. Context가 변경이되면, 모든 하위 컴포넌트가 리렌더링이 된다.
    • 한번 변경이 되면 향후 변경되지 않은 데이터를 Context로 활용한다(테마, 데이터캐쉬)

    API

    React.createContext

    const MyContext = React.createContext(dafaultValue)
    
    export const ThemeContext = React.createContext({
        theme: themes.dark,
        toggleTheme: () => {},
    })
    • Context 객체 생성하며, dafaultValue는 값이 없을때 사용되며, 객체( = 값)에 값과 메소드를 넣어 사용할수도 있다.

    Context.Privider

    <MyContext.Provider value={}>
    • Context를 통해 전달될 값을 넣어주며 Provider 하위 컴포넌트에게 value가 변경될때마다 전파한다.
    • value가 변하면 props가 변하기 때문에 불필요한 렌더링이 발생한다(shouldComponentUpdate 미적용)
      • Object.is를 통해서 Context의 값 변경 여부를 체크한다(true/false)

    Class.contextType

    class MyClass extends React.Component {
        componentDidMount() {
            let value = this.context
        }
        componentDidUpdate() {
            let value = this.context
        }
        componentWillUnmount() {
            let value = this.context
        }
        render() {
            let value = this.context
        }
    }
    MyClass.contextType = MyContext
    • contextType을 통해 class내에서 사용하는 Context를 지정할 수 있다

    • 모든 컴포넌트의 생명주기 메서드에서 사용가능하다.

      class MyClass extends React.Component {
          static contextType = MyContext
          render() {
              let value = this.context
              /* context 값을 이용한 렌더링 */
          }
      }
    • public class field 문법으로 contextType을 지정할 수 도 있다.

    Context.Consumer

    <MyContext.Consumer>
      {value => /* context 값을 이용한 렌더링 */}
    </MyContext.Consumer>
    • Provider의 하위 트리에서 사용할 수 있는 Cosumer 컴포넌트입니다.
    • Consumer 컴포넌트의 children은 함수형으로 작성되면 props는 해당 Context의 value값이다.
    • value값이 없다면, defaultValue가 적용이 된다.

Context.displayName

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools
  • 개발자 도구에서 손 쉽게 확인하려고 라벨링을 해 줄 수 있다.

Context의 value값이 reference일때!!

  • value 값이 객체이면 매번 새로운 객체가 생성되므로 Provider가 렌더링될때마다 리렌더링 된다
  • state를 객체로 생성하고 객체를 가리키고 있는 포인터를 넘기자
class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            value: { something: 'something' },
        }
    }

    render() {
        return (
            <Provider value={this.state.value}>
                <Toolbar />
            </Provider>
        )
    }
}

Hook - useContext

  • 함수형 컴포넌트에서 Context를 사용할때 사용
  • 가장 가까이 있는 Context.Provider의 value를 리턴한다.
  • React.memo, shouldComponentUpdate를 사용해도 최적화가 안되며 리렌더링이 된다
const themes = {
    light: {
        foreground: '#000000',
        background: '#eeeeee',
    },
    dark: {
        foreground: '#ffffff',
        background: '#222222',
    },
}

const ThemeContext = React.createContext(themes.light)

function App() {
    return (
        <ThemeContext.Provider value={themes.dark}>
            <Toolbar />
        </ThemeContext.Provider>
    )
}

function Toolbar(props) {
    return (
        <div>
            <ThemedButton />
        </div>
    )
}

function ThemedButton() {
    const theme = useContext(ThemeContext)
    return (
        <button
            style={{ background: theme.background, color: theme.foreground }}
        >
            I am styled by theme context!
        </button>
    )
}

© 2023, Customized by Joon