본문으로 바로가기

15. Context API

category React/리액트를 다루는 기술 2021. 2. 10. 16:41

컴포넌트간의 데이터를 넘겨줄 때는 상위에서 하위 컴포넌트로 props값을 설정해서 넘겨주는게 일반적이다

지금까지 책의 내용을 따라가면서 작은 프로젝트나 예시들을 해보면서 느낀 것이지만 컴포넌트의 깊이가 깊어질 수록 이렇게 전달하는게 맞나..? 싶은 의문이 들 때가 있었는데

to-do 앱만 봐도 그렇다 내가 클릭하는 항목에 접근하려면 많은 컴포넌트를 거쳐서 클릭 이벤트 함수를 전달해주어야 했고

거기서 실행된 클릭 이벤트에 의해서 변경된 state는 또 다시 렌더링하기위해 많은 컴포넌트를 거쳐서 제자리를 찾아가야했다

이러한 문제들을 해결하기 위해 나온것이 context인데

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e9e02e94-c391-478a-b134-18efe3b6a40f/Untitled.png

그림과 같이 필요한 데이터들을 context에 넣어두면 여러 컴포넌트를 거칠 필요없이 한번에 접근해서 가져올 수 있다

15-1. Context API 사용법 익히기


// in ColorContext.js;
import {createContext} from 'react';

const ColorContext = createContext({color : 'black'});

export default ColorContext;

이렇게 ColorContext를 createContext로 선언해주면 이제 전역에서 이 상태값이 조회가 가능해진다

이때 선언한 상태값은 value로 조회할 수 있다

15-1-1. Consumer


Consumer는 context의 변화를 구독하는 React 컴포넌트이다 함수형 컴포넌트 내에서 context의 값을 읽기위해 쓸 수 있다

//in ColorBox.js
const ColorBox = () => {
    return (
        <div>
            <ColorContext.Consumer>
                {value=>(
                    <div style={{
                        width :'64px',
                        height :'64px',
                        background : value.color,
                    }}/>
                )}
            </ColorContext.Consumer>
        </div>
    );
};

여기서 Consumer 사이에 JSX나 문자열을 넣지 않고 함수를 넣었는데 이러한 패턴을 Fuunction as a child, Render Props라고 한며 Consumer의 자식은 함수여야만 한다

이 때 자식함수가 받는 value값은 가장 가까운 Provider의 value props와 동일한데 지금은 Provider가 존재하지 않기 때문에 제일 처음 초기화해 주었던 createContext({color : 'black'})의 값이 된다

15-1-2. Provider


Provider는 context를 구독하는 컴포넌트들에게 context의 value값이 바뀌었음을 알려주는 역할을 한다

Provider는 props값으로 value를 받는데 이 value로 context의 값이 바뀌게되며 context를 구독하는 모든 컴포넌트들은 리렌더링된다

function App() {
  return (
      <ColorContext.Provider value={{color : 'red'}}>
        <ColorBox/>
      </ColorContext.Provider>
  );
}

기본값


위에서 Provider를 사용하지 않으면 Consumer는 제일 처음에 설정된 value값을 사용한다고 했다

Provider를 사용하고 value값을 명시해주지 않아도 기본값을 사용할까?

function App() {
  return (
      <ColorContext.Provider>
        <ColorBox/>
      </ColorContext.Provider>
  );
}

그렇지 않다 이렇게 사용하면 에러가 난다

15-2. 동적 Context 사용하기


context의 value에는 상태값만 있을 수 있는게 아니라 함수를 전달해 줄 수도 있다

15-2-1. context 수정


const ColorContext = createContext({
    state : {color : 'black', subColor : 'red'},
    actions : {
        setColor : ()=>{},
        setSubColor : ()=>{},
    }
});

const ColorProvider = ({children}) =>{
    const [color,setColor] = useState('black');
    const [subColor,setSubColor] = useState('red');

    const value = {
        state : {color, subColor},
        actions : {setColor, setSubColor},
    };
    return (<ColorContext.Provider value={value}>{children}</ColorContext.Provider>)
}

const {Consumer : ColorConsumer} = ColorContext;
export {ColorProvider, ColorConsumer};
export default  {ColorContext};

바뀐 부분이 많지만 진행하면서 차근차근 알아보자

우선 value부분이 바뀌었다 state와 action객체로 나뉘었으며 state안에 원래 쓰던 컬러가 들어가있는 걸로 보아서 저부분에서 데이터를 받아올 것 같다

두 번째로 함수가 생겼는데 이 함수 또한 내부에 ColorContext가 가지고 있는 value값을 동일하게 가지고 있으며 ColorContext.Provider 컴포넌트를 반환하고 있다

이렇게 설정해놓으면 Provider에 value값을 명시하지 않아도 함수 내부에 설정되어있는 value값이 deafault value와 같기 때문에 에러가 발생하지 않는다

function App() {
  return (
      <ColorProvider>
        <ColorBox/>
      </ColorProvider>
  );
}
const ColorBox = () => {
    return (
        <div>
            <ColorConsumer>
                {({state})=>(
                    <div>
                        <div style={{
                            width :'64px',
                            height :'64px',
                            background : state.color,
                        }}/>
                        <div style={{
                            width : '32px',
                            height : '32px',
                            background : state.subColor,
                        }}/>
                    </div>

                )}
            </ColorConsumer>
        </div>
    );
};

15-2-2. 색상 선택 컴포넌트


이제 위에서 알아보지 않은 actions를 이용해서 동적으로 박스의 색을 바꿔보는 컴포넌트를 구현해보자

const colors = ['red','orange','yellow','green','blue','indigo','violet'];

const SelectColors = () => {
        return (
        <div>
            <h2>choose a color.</h2>
                    <div style={{display : 'flex'}}>
                        {colors.map(color=>
                            <div key={color} style={{
                                width : '24px',
                                height : '24px',
                                background : color,
                                cursor : 'pointer',
                            }}/>)}
                    </div>
            <hr/>
        </div>
    );
};

현재의 SelectColors 컴포넌트는 아무런 능력이 없는 그냥 색상을 나열한 팔레트에 불과하다

ColorConsumer에 있는 actions의 setColor와 setSubColor를 이용해 좌 클릭, 우 클릭으로 각각 큰 박스, 작은 박스의 색상을 변화시켜보자

const SelectColors = () => {
    return (
        <div>
            <h2>choose a color.</h2>
            <ColorConsumer>
                {({actions})=>(
                    <div style={{display : 'flex'}}>
                        {colors.map(color=>
                            <div onContextMenu={()=>actions.setSubColor(color)}
                                 onClick={()=>actions.setColor(color)}
                                 key={color} style={{
                                width : '24px',
                                height : '24px',
                                background : color,
                                cursor : 'pointer',
                            }}/>)}
                    </div>
                )}
            </ColorConsumer>
            <hr/>
        </div>
    );
};

ColorConsumer의 자식은 무조건 함수여야한다는 것을 잊지말자

15-3. useContext Hook


context에서 값을 받아와서 사용하려면 consumer컴포넌트를 이용해야하고 consumer컴포넌트를 이용하려면 그 자식이 render props 즉 함수로 작성해야한다

하지만 useContext Hook을 사용하면 매우 편하게 context의 value에 접근할 수 있다

const context = useContext(ColorContext);

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/edcc026e-c9d4-41d1-b5c2-3bf2893d7171/Untitled.png

useContext를 사용해서 ColorContext의 데이터를 받아온 context변수의 출력 결과이다

이제 받아온 데이터로 일반 js변수 접근하듯 사용할 수 있다

const ColorBox = () => {
    const {state} = useContext(ColorContext);
    return (
        <div>
            <div>
                <div style={{
                    width :'64px',
                    height :'64px',
                    background : state.color,
                }}/>
                <div style={{
                    width : '32px',
                    height : '32px',
                    background : state.subColor,
                }}/>
            </div>

        </div>
    );
};