Context API

김동현·2021년 12월 7일
0

React

목록 보기
14/27
post-thumbnail

props chain

이전에 살펴보았듯이 컴포넌트와 컴포넌트끼리 데이터를 주고 받을 수 있는 "유일한 통로는 props"입니다. 만약 컴포넌트의 데이터를 다른 컴포넌트에게 전달할 때 컴포넌트의 깊이가 깊어질 수록 매번 props로 전달해야합니다. 이는 규모가 큰 어플리케이션인 경우 props로 전달하는 것이 점점 더 길어질 수 있습니다.

Context API 사용하기

우리는 리액트의 내장된 기능인 "Context"라는 개념을 통해 props chain을 만들지 않고도 데이터를 다른 컴포넌트에게 "직접" 전달할 수 있습니다.

// context.js
import React from 'react';

  // Context 생성
const MyContext = React.createContext();
console.log(MyContext) // -> Object { Provider: Object, Consumer: Object }

export default MyContext;

React.createContext를 호출하여 Context를 생성합니다. 생성된 Context는 객체입니다. 참고로 Context는 하나만 존재할 수 있는 것이 아니라 여러 개 존재할 수 있습니다.

React.creteContext를 호출할 때 인수로 기본값을 설정할 수 있습니다. 이때 작성한 기본값은 Provider 컴포넌트 외부에 존재하는 컴포넌트에서 사용될 값이며, Provider 컴포넌트 내부에 작성된 컴포넌트들은 인수로 전달한 기본값을 무시합니다.

createContext는 객체를 반환합니다. 반환된 객체는 ProviderConsumer 프로퍼티를 갖는 객체입니다.

  • Provider 프로퍼티는 "데이터를 제공하는 컴포넌트" 역할

  • Consumer 프로퍼티는 "데이터를 조회하고 싶을 때 사용하는 컴포넌트"입니다.

이 두 프로퍼티 모두 "컴포넌트 역할"을 한다는 점을 유의해야 합니다.

Provider 컴포넌트

  1. createContext 함수로 생성된 객체 안에 존재하는 Provider 컴포넌트로 Content 데이터를 사용할 컴포넌트들을 "래퍼 컴포넌트처럼 감싸줍니다".

  2. Provider 컴포넌트에 value 어트리뷰트를 작성하여 Context의 "초기값"을 설정할 수 있습니다.

만약 createContext를 호출할 때 인수로 기본값을 전달했더라도 Provider 컴포넌트를 사용하는 경우 "value 어트리뷰트를 생략할 수 없으며", Context의 초기값은 value 어트리뷰트로 지정한 값으로 설정됩니다.
즉, Provider 컴포넌트를 사용하는 경우 createContext에 인수로 전달한 초기값은 무시되고 "value" 어트리뷰트로 지정한 초기값을 사용하게 됩니다.

Provider 컴포넌트의 하위 컴포넌트들은 어디서든 "Context의 값을 직접 조회하여 사용"할 수 있습니다.
즉, 이전처럼 props를 통해 연결된 모든 컴포넌트를 통할 필요 없이 Provider 컴포넌트로 감싼 어떤 후손 컴포넌트든 Context의 값이 필요하다면 "다른 컴포넌트를 거치지 않고" 바로 조회하여 사용할 수 있도록 해줍니다.
우리는 Context의 값이 필요한 컴포넌트들을 가장 상위에 Provider 컴포넌트로 래핑만 하면 됩니다.

import MyContext from './store/MyContext';

  // value 어트리뷰트에 Context의 초기값을 작성
<MyContext.Provider value={defaultValue}>
    // MyContext.Provider의 모든 하위 컴포넌트들은 Context에 직접 접근 가능
    ,,,
</MyContext.Provider>

Provider 컴포넌트 외부

Provider 컴포넌트를 사용하지 않는다면, 우리는 createContext를 호출할 때 전달된 인수를 사용할 수 있습니다.

// MyContext.js
import React from 'react';

const MyContext = React.createContext({
    data: 'Hello!'  // -> Provider 컴포넌트의 외부 컴포넌트들이 접근할 Context 값
});

export default MyContext;

먼저 MyContext라는 컨텍스트를 생성하고, 이때 Context의 기본값으로 { data: 'Hello!' }를 전달하였습니다.

// ChildComponent.js
import React from 'react';
import MyContext from './MyContext';

const ChildComponent = () => {
    return (
        <MyContext.Consumer>
            {context => {
                return <span>{context.data}</span>;
            }}
        </MyContext.Consumer>
    );
};

export default ChildComponent;
// ParentComponent.js
import React from 'react';
import ChildComponent from './ChildComponent';
import MyContext from './MyContext';

const ParentComponent = () => {
    return (
        <>
            <MyContext.Provider value={{ data: 'World!' }}>
                // 1. Provider의 value 어트리뷰트에 작성된 값 접근
                <ChildComponent />
            </MyContext.Provider>
            
            // 2. React.createContext에 전달된 인수에 접근
            <ChildComponent />
        </>
    );
};

export default ParentComponent;

ParentComponent의 반환문을 보면 MyContext.Provider 안에 작성된 ChildComponent와 MyContext.Provider 바깥에 작성된 ChildComponent가 있습니다.

MyContext.Provider 안에 작성된 ChildComponent에서 접근하는 Context의 값은 MyContext.Provider의 value 어트리뷰트로 작성된 객체인 { data: 'World!' }에 접근하게 됩니다.

MyContext.Provider 밖에 작성된 ChildComponent에서 접근하는 Context의 값은 createContext를 호출할 때 인수로 전달한 값인 { data: 'Hello' }에 접근하게 됩니다.

사용자 정의 Provider 컴포넌트

Provider 컴포넌트는 자신의 하위 모든 컴포넌트에게 Context 값을 제공하는 역할을 합니다. 이때 Provider 컴포넌트를 래퍼 컴포넌트로 갖는 사용자 정의 컴포넌트를 생성하여 사용할 수도 있습니다.

이렇게 사용하는 경우 "Context 값을 상태값"으로 가질 수 있으며, "Context 값과 관련된 로직을 다른 컴포넌트와 분리"할 수 있다는 장점이 있습니다.

import React, { useState } from 'react';
import MyContext from './my-context';

const MyContext = React.createContext(defaultValue);

export const MyContextProvider = props => {
    const [state, setState] = useState(initialState);
    useEffect(effectFn, [...dependencies]);
    ,,,
    
    return (
        <MyContext.Provider valu={ContextValue}>
            {props.children}
        </MyContext.Provider>
    );
}

export default MyContext;

위 코드처럼 MyContext.Provider 컴포넌트를 래퍼 컴포넌트로 갖는 MyContextProvider 컴포넌트는 컴포넌트 함수이므로 리액트 훅을 사용할 수 있고 Context 값을 상태로 관리할 수도 있으며 관련된 로직을 다른 컴포넌트와 분리하여 작성하는 것이 가능합니다.

Consumer 컴포넌트 사용

컴포넌트에서 Context의 값을 사용하기 위해서는 두 가지 방법을 사용할 수 있습니다.

첫 번째로 "Consumer 컴포넌트"를 사용하는 것입니다.

  1. Context 값을 사용할 컴포넌트의 반환값을 Consumer 컴포넌트로 감싼 다음 "Content 영역에는 함수"를 작성합니다.

  2. 작성된 함수는 리액트 엘리먼트를 반환하는 함수여야 합니다. 그리고 이 함수는 Context의 값을 인수로 전달받습니다.

import MyContext from './MyContext';

const MyComponent = () => {
    return (
        <MyContext.Consumer>
            {context => { return 리액트엘리먼트; }}
        </MyContext.Consumer>
    );
}

useContext 훅 사용

두 번째로 "useContext 훅"을 사용하는 방법이 있습니다. Consumer 컴포넌트를 사용하는 것보다 직관적이며 사용하는 것도 간편합니다.

useContext 훅의 인수로는 React.createContext로 생성된 객체를 전달합니다. 이때 반환되는 값은 Provider 컴포넌트의 value 어트리뷰트 값이 반환됩니다.

import { useContext } from 'react';
import MyContext from './MyContext';

const MyContext = () => {
    // useContext인수로 생성한 컨텍스트를 인수로 전달
    // useContext가 반환하는 것은 Context 값을 반환
    const context = useContext(MyContext);
    
    return (
        <span>{context.data}</span>
    );
};

일반적으로 함수형 컴포넌트를 사용하는 경우 "Provider 컴포넌트를 사용하여 Context 값을 제공"하고, "useContext 훅 사용하여 Context 값에 접근"합니다.

Context API를 사용하면 좋지 않은 경우

  1. props로 컴포넌트를 재사용(설정)하는 경우
    Context를 사용하지 않고 props를 그대로 사용하는 것을 권장합니다. prop은 "컴포넌트를 설정"하는 역할을 할 때가 있습니다. JSX 코드로 컴포넌트를 작성할 때 prop로 전달한 값으로 컴포넌트가 설정된다면 이때는 prop을 사용해야 합니다. 즉, props를 통해 컴포넌트를 재사용해야하는 경우 props를 사용해야 합니다.

  2. Context 값의 변경 빈도가 높은 경우
    예를 들어 자주 변경되는 state 값의 경우 최적화가 되지 않기 때문에 Context를 사용하는 것은 좋지 않습니다. 만약 자주 변경되는 값이면서 props chain이 긴 경우 Redux 같은 상태 관리 라이브러리를 사용하는 것을 권장합니다.

  1. props chain이 길지 않은 경우 props 사용을 권장합니다.
profile
Frontend Dev

0개의 댓글