TIL 22-05-02

thisisyjin·2022년 5월 2일
1

TIL 📝

목록 보기
36/113

React.js

Today I Learned ... react.js

🙋‍♂️ Reference Book

🙋‍ My Dev Blog


리액트를 다루는 기술 DAY 15

  • context API

Context API

  • 전역적으로 사용할 데이터가 있을 때 유용함.
    (예> 로그인 정보, 환경 설정 및 테마)
  • 많은 라이브러리들은 Context API 기반임. (Redux, react-router, styled-compoents 등)

전역 상태 관리 흐름

  • 리액트에서는 컴포넌트간에 데이터를 props로 전달함.
  • 전역으로 (=여기저기서) 필요한 데이터가 있다면, 주로 최상위 컴포넌트인 Appstate로 넣어 관리함.
  • 하지만 컴포넌트 구조가 복잡해질수록 Props로 넘기기 번거로워짐. (복잡하게 여러 번 거쳐서 전달해야 함)
    -> 유지보수성이 낮아지게 됨.
  • Redux나 MobX같은 상태 관리 라이브러리를 사용하여 전역 상태 관리 작업을 간편하게 할 수 있음.
  • BUT! React v16.3 이후 Context API가 많이 개선되었음.

기존에는 최상위 컴포넌트에서 여러 컴포넌트를 거쳐 props를 통해 데이터(state, setState)를 전달했지만,
Context API를 사용하면 단 한번에 원하는 값을 받아와 사용 가능함.


Context API 사용법

context 생성

src/contexts/color.js

import { createContext } from "react";

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

export default ColorContext;
  • 새로은 context를 만들 때는 React.createContext 함수를 사용.
  • 함수의 인자로는 context의 기본값이 들어감.

⚡️ Consumer

  • 참고) Provider과 Consumer

    관찰자 패턴 (공급자와 소비자)

  • 데이터에 접근하고 싶은 컴포넌트를 context API의 Provider로 묶어줘야함.
    전달해줄 데이터는 value 어트리뷰트로 적어줌.

예 >

return (
    <TableContext.Provider
      value={{tableData: state.tableData, dispatch}}
    > ... </TableContext.Provider>
  );

  • ColorBox 라는 컴포넌트를 생성하여 ColorContext 안에 들어있는 color 데이터를 보여줌.

src/components/ColorBox.js

import ColorContext from "../contexts/color";

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

export default ColorBox;

Consumer 사이에 중괄호를 열어 안에 함수를 넣어줌.
-> function as a child (= Render Props)

Function as a child (=Render Props)

  • children이 있어야 할 자리에 JSX나 문자열이 아닌 함수를 전달해줌.

ex>

const RenderPropsSample = ({ children }) => {
  return <div>결과: {children(5)}</div>;
};
<RenderPropsSample>{value => value * 2}</RenderPropsSample>

-> RenderPropsSample에 props.childeren으로 함수를 넘기고,
RenderPropsSample 컴포넌트에서는 children을 함수로 사용 -> children(5) 를 해서 10을 리턴.

App에 렌더링

App.js

import ColorBox from './components/ColorBox';

function App() {
  return (
    <div>
      <ColorBox />
    </div>
  );
}

export default App;
  • ColorBox를 App에 렌더링함.

RESULT

  • 64*64 짜리 backgroud가 black인 div가 잘 나타난다.
  • background: value.color 로 가져왔기 때문에, 데이터를 잘 받아온 것을 알 수 있다.

즉, Consumer로 감싸주면
내부에서 createContext()로 만든 colorContext의 데이터를 사용할 수 있다.


⚡️ Provider

  • Provider을 사용하면 Context의 value를 변경할 수 있다.
  • 데이터에 접근하고 싶은 컴포넌트를 context API의 Provider로 묶어줘야함.
  • 전달해줄 데이터는 value 어트리뷰트로 적어줌.

App.js (수정)

import ColorBox from './components/ColorBox';
import ColorContext from './contexts/color';

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

export default App;

RESULT

  • 기존에 createContext 함수에서 인자로 기본값인 {color: 'black'}을 넣어줬었다.
  • createContext함수에서 설정한 기본값Provider을 사용하지 않았을 때만 사용됨.

❗️ 단, Provider을 사용하려면 반드시 'value' 값을 명시해줘야 오류가 발생하지 ❌


동적 Context 사용

  • 위 예제는 고정적인 값을 사용한 것이고, Context 값을 업데이트해야 하는 경우는 다음과 같다.

Context 파일 수정

src/contexts/color.js

import { createContext, useState } from "react";

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>
    );
};

// ColorContext.Provider = ColorProvider (value, children 포함
// ColorContext.Consumer = ColorConsumer 
const { Consumer: ColorConsumer } = ColorContext;
export { ColorProvider, ColorConsumer };

export default ColorContext;

1. createContext()

  • 초기값은 객체이다. (state, actions 필드를 가진 객체)
  • state에는 color, subcolor가 있고 / actions는 setState함수들이 있다. (기본값은 함수 틀만 작성해둠)

2. ColorProvider 컴포넌트

  • color, subcolor라는 state를 가짐. (useState)
  • value는 객체임. state: { color, subcolor }라는 것은 state: {color: color, subcolor: subcolor}과 같음.
  • 즉, state인 color과 subcolor, 그리고 setColor과 setSubcolor을 각각 state와 actions 필드에 넣어주는 것.
  • Provider을 사용하므로 -> value 를 꼭 적어줘야함. (동명의 value 객체를 넣어줌)
  • children은 자신이 children으로 받은 것을 렌더링해줌.

3. export { ColorProvider, ColorConsumer }

  • export default가 아닌 모듈로서 export해줌.

🔺 정리

  • createContext로 ColorContext의 기본값 설정
  • ColorProvider, ColorConsumer을 export함.
  • ColorProvider에서는 value에 데이터를 저장
  • ColorContext.Consumer은 ColorConsumer임.

App.js 수정 (Provider)

  • ColorContext.Provider을 ColorProvider로 수정.
import ColorBox from './components/ColorBox';
import { ColorProvider } from './contexts/color';

function App() {
  return (
    <ColorProvider>
      <div>
        <ColorBox />
      </div>
    </ColorProvider>
  );
}

export default App;

ColorBox.js 수정 (Consumer)

  • ColorContext.Consumer을 ColorConsumer로 수정.
  • div 한개 더 추가. (width, height 다르게 하고 / background를 subcolor로)
import { ColorConsumer } from "../contexts/color";

const ColorBox = () => {
    return (
        <ColorConsumer>
            {value => (
                <>
                <div
                    style={{
                        width: '64px',
                        height: '64px',
                        background: value.state.color,
                    }}
                />
                <div
                    style={{
                        width: '32px',
                        height: '32px',
                        background: value.state.subcolor,
                    }}
                />
                </>
            )}
        </ColorConsumer>
    )
}

export default ColorBox;
  • ❗️ 주의 - value.color을 value.state.color로 수정. (객체 구조가 바뀌었으므로)

RESULT

  • 고정값이 아닌 동적으로 Context를 이용할 수 있다.

참고 - value.state.color 대신
구조분해 할당으로 {({ state }) => ( ... state.color) } 과 같이 사용 가능!
-> value.action 필드 사용시에도 마찬가지임.


색상 선택 컴포넌트

SelectColors 컴포넌트 생성

components/SelectColors.js

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

const SelectColors = () => {

    return (
        <div>
            <h2>색상을 선택하세요.</h2>
            <div style={{ display: 'flex' }}>
                {colors.map(color =>
                    (<div
                        key={color}
                        style={{
                            background: color,
                            width: '24px',
                            height: '24px',
                            cursor: 'pointer',
                        }}
                    />
                    ))}
            </div>
            <hr />
        </div>
    );
}

export default SelectColors;
  • 색상 좌클릭시 큰 정사각형의 색이 바뀌고, (=color)
  • 우클릭시 작은 정사각형의 색이 바뀌도록 구현. (=subcolor)

map에서 각 div를 onClick 이벤트로는 setColor(color)을 해주면 되고,
oncontextMenu 이벤트로는 setSubcolor(color)을 해주면 된다.

🔺 정리

color.js에서 value.action을 통해 setColor, setSubcolor을 전달해주었음 (colorProvider)
-> ColorBox 컴포넌트에서는 ColorConsumer을 임포트하여 value.state.color을 가져다 씀.
-> 마찬가지로 value.action.setColor도 ColorBox 컴포넌트에서 사용 가능.

  • ColorProvider는 App.js에서 쓰고, (데이터를 사용할 컴포넌트들을 감싸줌)
  • ColorConsumer는 ColorBox.js에서 씀.
    -> 데이터를 사용 + 변경도 함. (value.state.color을 받아오고, value.state.setColor함수도 전달받아 직접 상태변경도 가능)

ColorConsumer 사용

  • SelectColors.js (수정)
import { ColorConsumer } from "../contexts/color";

const colors = ['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.setColor(color)}
                        onContextMenu={e => {
                                e.preventDefault();
                                actions.setSubColor(color);
                            }
                        }   
                    />
                    ))}
                </div>
                )}
            </ColorConsumer>
            <hr />
        </div>
    );
}

export default SelectColors;

ColorBox에서 했던 것 처럼 <ColorConsumer></ColorConsumer> 사이에 Function as child 방식으로 함수 넣어줌. (JSX를 리턴하는 함수)
-> value값을 받아와서 value.actions.setColor과 value.actions.setSubcolor을 이용함.

RESULT (완성)


Hooks/static contextType 사용

  • Consumer 대신 Hooks나 static ContextType을 사용하는 방법도 有.

1) useContext 사용

import { useContext } from "react";
import ColorContext from "../contexts/color";

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

export default ColorBox;
  • const value = useContext(context명) 을 해주면 된다.
  • 위에서는 구조분할을 위해 const {state} 와 같이 해줌.
<ColorConsumer>
{() => <>...</>}
</ColorConsumer>

위와 같이 Consumer사이에 Function as child 방식 (=Render Props) 을 사용하는 것 보다 훨씬 간결해졌다.

const { state } = useContext(ColorContext);

return ( <>...</> );

2) static contextType <클래스형>

  • 클래스형 컴포넌트에서는 Hook을 사용하지 못하므로, useContext를 사용할 수 X.
  • static contextType을 사용하며 간편함.
import { Component } from "react";
import ColorContext from "../contexts/color";

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

class SelectColors extends Component {
    static contextType = ColorContext;
    render() {
        return (
            <div>
                <h2>색상을 선택하세요.</h2>
                <div style={{ display: 'flex' }}>
                    {colors.map(color =>
                    (<div
                        key={color}
                        style={{
                            background: color,
                            width: '24px',
                            height: '24px',
                            cursor: 'pointer',
                        }}
                    />
                    ))}
                </div>
                <hr />
            </div>
        );
    }
}

export default SelectColors;
  1. static 변수로 저장해줌.
static contextType = ColorContext;

-> this.context에 현재 context의 value가 저장됨.

  1. value 사용시
    ex> this.context.actions.setColor 이런식으로 사용하면 됨.

SelectColors.js (수정)

import { Component } from "react";
import ColorContext from "../contexts/color";

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

class SelectColors extends Component {
    static contextType = ColorContext;

    onClickColor = color => {
        this.context.actions.setColor(color);
    }

    onRightClickColor = subcolor => {
        this.context.actions.setSubcolor(subcolor);
    }


    render() {
        return (
            <div>
                <h2>색상을 선택하세요.</h2>
                <div style={{ display: 'flex' }}>
                    {colors.map(color =>
                    (<div
                        key={color}
                        style={{
                            background: color,
                            width: '24px',
                            height: '24px',
                            cursor: 'pointer',
                        }}
                        onClick={() => this.onClickColor(color)}
                        onContextMenu={(e) => {
                            e.preventDefault();
                            this.onRightClickColor(color);
                        }}
                    />
                    ))}
                </div>
                <hr />
            </div>
        );
    }
}

export default SelectColors;
profile
기억은 한계가 있지만, 기록은 한계가 없다.

0개의 댓글