Context API로 전역값 관리하기

송은·2023년 6월 13일
0

React 컴포넌트 관계

React에서 props와 state는 부모 컴포넌트와 자식 컴포넌트 또는 한 컴포넌트 안에서 데이터를 다루기 위해 사용된다.

이 props와 state를 사용하게 되면 부모 컴포넌트에서 자식 컴포넌트 → 즉, 위에서 아래, 한쪽으로 데이터가 흐르게 된다.

만약 다른 컴포넌트에서 한쪽으로 흐르고 있는 데이터를 사용하고 싶은 경우 또는 다른 컴포넌트에서 사용하고 있는 데이터를 현재의 데이터 흐름에 넣고 싶은 경우가 발생한다면 어떻게 해야할까?

React에서 데이터는 위에서 아래로 흐르게 되므로 사용하고 싶은 데이터와 이 데이터를 사용할 컴포넌트의 공통 부모 컴포넌트에 state를 만들고, 사용하고자 하는 데이터를 props로 전달하면 이 문제를 해결할 수 있다.

하지만 이처럼 컴포넌트 사이에 공유되는 데이터를 위해 매번 공통 부모 컴포넌트를 수정하고 하위 모든 컴포넌트에 props로 전달하는 것은 매우 비효율적이다. (컴포넌트 기능으로서 작동하는 것이 아닌 중간 다리역할이 늘어난다.)

이와 같은 문제를 해결하기 위해 React의 Context API를 이용하여 해결할 수 있다.

Context API 작동 원리

Context API는 프로젝트 안에서 전역적으로 사용할 수 있는 값을 관리해준다. 컴포넌트에게 함수를 전달해줘야 하는 상황에서 코드의 구조가 훨씬 깔끔해진다.

React에서 Context를 사용하기 위해서는 Context API를 사용해야 하며, Context의 ProviderConsumer를 사용해야 한다.

Context에 저장된 데이터를 사용하기 위해서는 공통 부모 컴포넌트에 Context의 Provider를 사용하여 데이터를 제공해야 하며, 데이터를 사용하려는 컴포넌트에서 Context의 Consumer를 사용하여 실제로 데이터를 사용한다.


사용법

생성하기

전달해줄 값을 createContext를 통해 밖에서 생성해준다.

이 때, createContext의 파라미터에는 Context의 기본값을 설정할 수 있다.

const MyContext = createContext('defaultValue');

전달/사용하기

useContext를 이용하여 createContext로 생성한 전역 데이터값을 조회하여 사용할 수 있다.

전달해줄 값을 따로 설정하지 않았을 때는 createContext에서 파라미터에 적어두었던 기본값이 전달된다.

아래 결과는 안녕하세요? defaultValue로 출력된다.

function Child () {
    const text = useContext(MyContext);
    return <div>안녕하세요? {text}</div>;
};

전달/사용하기(특정값)

전달해주는 값을 Provider라는 컴포넌트를 통해 value에 전달해줄 값을 지정해준다.

이 때 Provider는 공통 부모 컴포넌트에 사용하여 데이터를 제공할 수 있도록 해준다.

그럼 위에 Child 함수에서 return 되는 결과는 안녕하세요? Good이 된다.

이렇게 설정해주고 나면 Provider에 의하여 감싸진 컴포넌트 중 어디서든지 Context의 값을 다른 곳에서 조회해서 사용할 수 있다.

function ContextSample() {
    return (
        <MyContext.Provider value="Good">
            <GrandParent />
        </MyContext.Provider>
    )
}

내보내기

export를 통해 내보내고 나면 나중에 사용하고 싶을 때 사용하고 싶은 곳에서 불러와서 사용할 수 있다.

export const UserDispatch = React.createContext(null);

예시

const MyContext = createContext('defaultValue'); // context 기본값을 'deafultValue'로 설정하여 생성하였다.

function Child() {
    const text = useContext(MyContext);
    return <div>안녕하세요? {text}</div>
};

function Parent() {
    return <Child />
}

function GrandParent() {
    return <Parent />
}

function ContextSample() {
    return (
        <MyContext.Provider value="GOOD">
            <GrandParent text="GOOD" />
        </MyContext.Provider>
    );
}

export default ContextSample;

Context 전달 결과


활용

useState로 유동적인 값 만들기

function ContextSample() {
    const [value, setValue] = useState(true);
    return (
        <MyContext.Provider value={value ? "GOOD" : 'BAD'}> 
            <GrandParent text="GOOD" />
            <button onClick={() => setValue(!value)}>CLICK</button>
        </MyContext.Provider>
    );
}

useReducer로 dispatch Context 만들기

// UserDispatch 라는 context를 생성해서,
export const UserDispatch = React.createContext(null);

// context 사용 시
const dispatch = useContext(UserDispatch);
// Provider 컴포넌트의 value값으로 dispatch를 넘겨준다. 
// (= 어디서든지 dispatch를 꺼내 쓸 수 있도록 만들어준다.)
const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>
  );
}

아래와 같이 dispatch를 사용할 수 있다.

import React, { useContext } from 'react';
import { UserDispatch } from './App';

// UserDispatch 를 App 컴포넌트로 부터 불러와서 사용
const User = React.memo(function User({ user }) {
  const dispatch = useContext(UserDispatch);

  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => {
          dispatch({ type: 'TOGGLE_USER', id: user.id });
        }}
      >
        {user.username}
      </b>
      &nbsp;
      <span>({user.email})</span>
      <button
        onClick={() => {
          dispatch({ type: 'REMOVE_USER', id: user.id });
        }}
      >
        삭제
      </button>
    </div>
  );
});

function UserList({ users }) {
  return (
    <div>
      {users.map(user => (
        <User user={user} key={user.id} />
      ))}
    </div>
  );
}

export default React.memo(UserList);

❓ useState vs. useReducer 차이점

useState만을 이용해서 내부에서 작업했다면 dispatch가 없기 때문에 컴포넌트를 원하는 곳에서 관리하는 것이 어렵다.

물론, Provider 컴포넌트에서 valueset함수를 넘겨주는 방식으로도 구현이 가능하지만, useReducer의 dispatch Context를 이용한 것처럼 깔끔하게 이루어지지 않을 것이다.

이처럼 특정 함수를 여러 컴포넌트에 거쳐서 전달해주어야 하는 값이 있다면 dispatch를 관리하는 Context를 만들어서 필요한 곳에서 바로 dispatch를 불러와서 사용하면 구조도 깔끔해지고 편리할 것이다.




출처

profile
개발자

0개의 댓글