[React] 전역 상태관리 툴 (Context API, Redux, Zustand)

김재훈·2023년 4월 23일
1

상태

상태란 컴포넌트 내부에서 관리되며 애플리케이션의 렌더에 영향을 미치는 플레인 자바스크립트 객체다. 변화하는 데이터라고도 한다.

상태의 종류

지역 상태 (local state)

지역 상태는 특정 컴포넌트 안에서만 관리되는 상태를 뜻한다.
다른 컴포넌트들과 데이터를 공유하지 않는다.
예를 들면 input, selectbox 등에서 사용자의 입력값을 받는 경우가 있다.

컴포넌트 간 상태 (cross component state)

컴포넌트 간 상태는 여러 가지 컴포넌트에서 관리되는 상태를 나타낸다.
다수의 컴포넌트에서 쓰이고, 또 영향을 미치는 상태를 뜻한다.
예를 들면, 프로젝트 곳곳에 쓰이는 모달이 있다.

보통 상위 컴포넌트에서 하위 컴포넌트로 props를 넘겨 해당 컴포넌트까지 전달되도록 하는 props drilling 방식을 필요로 한다.

전역 상태 (global state)

전역 상태는 프로젝트 전체에 영향을 끼치는 상태다.
이 또한 props drilling 방식을 활용해서 부모에서 자식으로 데이터를 전달한다.

상태관리의 필요성

서로 다른 두 컴포넌트에 같은 데이터가 필요한 경우 각 컴포넌트가 부모 자식 관계로 되어 있지 않은 이상, 각 컴포넌트 간의 직접적인 데이터 전달이 어렵다.
데이터를 부모 컴포넌트로 보내고 다시 그 데이터가 필요한 컴포넌트로 전달해야 하는데, 이러한 props drilling이 많아지면 props를 추적하기 어려워진다.
따라서 각 어플리케이션에 알맞은 상태관리 툴을 선택해 상태를 잘 관리하는 것이 중요하다.

상태관리 툴

1. Context API

Context API란?

Context API는 React 컴포넌트 트리 안에서 전역 상태를 공유할 수 있도록 만들어진 방법이다.
Context API는 종속성을 주입하기 위한 도구이기 때문에 전역 상태관리 툴이라기엔 다소 애매한 면이 있다.
Context API는 이미 존재하는 상태를 다른 컴포넌트들과 쉽게 공유할 수 있게 해주는 역할을 한다.

Context API 구성

  • Context
    전역 상태를 저장하는 곳이다.
    Context 내부에 Provider와 Consumer가 정의되어 있고, Consumer는 Context를 통해 상태에 접근이 가능하다.
  • Provider
    전역 상태를 제공하는 역할을 한다.
    Context에 상태를 제공해서 다른 컴포넌트가 상태에 접근할 수 있도록 도와준다.
    제공된 상태에 접근하이 위해서는 Provider 하위에 컴포넌트가 포함되어 있어야 한다.
    따라서 모든 컴포넌트에 접근 가능하도록 Root component (index.js / app.js) 에서 Provider를 정의한다.
  • Consumer
    제공받은 전역상태를 받아서 사용하는 역할을 한다.
    Context는 Consumer 사이에 있는 첫 객체를 Context에 인자로 전달하기 떄문에 빈 객체 작성 후 JSX를 작성해야 한다.

Context API 사용법

import {createContext} from 'react';

const MyContext = createContext();

createContext 함수를 불러와서 Context를 만든다.
기본 값을 설정하고 싶은 경우 createContext 함수 안에 인자로 기본 값을 넣어주면 된다.

function App() {
  return (
    <MyContext.Provider value="Hello World">
      <GrandParend />
    </MyContext.Provider>
    );
}

Context 객체 안에 Provider라는 컴포넌트가 있다.
컴포넌트 간에 공유하고자 하는 값을 value라는 props로 설정하면 자식 컴포넌트들에서 해당 값에 접근할 수 있다.

import {useContext} from 'react';

function Message() {
  const value = useContext(MyContext);
  return <div>Received: {value}</div>;
}

useContext를 사용하여 Context에 넣은 값에 바로 접근할 수 있다.

2. Redux

Redux란?

Redux는 전역 상태관리를 위한 도구로, 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너다.

  • Single Source of truth
    동일한 데이터는 항상 같은 곳에서 가지고 온다. 즉 스토어라는 하나뿐인 데이터 공간이 있다는 의미다.
  • State is read-only
    액션이라는 객체를 통해서만 상태를 변경할 수 있다.
  • Changes are made with pure functions
    변경은 순수 함수로만 가능하다.

Redux 구성

Store
Store는 상태가 관리되는 오직 하나의 공간이다.
컴포넌트와는 별개로 스토어라는 공간이 있어 그 스토어 안에 앱에서 필요한 상태를 담는다.
컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.

Action
Action은 앱에서 스토어에 운반할 데이터를 말한다.
Action은 자바스크립트 객체 형식으로 되어 있다.

Reducer
Action을 Reducer에 전달해야 Store에 저장할 수 있다.
Reducer가 Action을 보고 Store의 상태를 업데이트 한다.
Action을 Reducer에 전달하기 위해서는 dispatch() 메소드를 사용해야 한다.

  1. Action 객체가 dispatch() 메소드에 전달된다.
  2. dispatch(Action)를 통해 Reducer를 호출한다.
  3. Reducer는 새로운 Store를 생성한다.

Reudx의 데이터 흐름은 동일하게 단방향으로, view(컴포넌트)에서 Dispatch()라는 함수를 통해 Action이 발동되고 Reducer에 정의된 로직에 따라 Store의 State가 변화하고 그 State를 쓰는 view(컴포넌트)가 변하는 흐름을 따른다.

Redux 사용법

[reducers / index.js]

import {combineReducers} from 'redux';
import counter from './counter';

// 여러 reducer를 사용할 경우 reducer를 하나로 묶어주는 메소드다.
// store에 저장되는 redcuer는 한 개다.
const rootReducer = combineRudecers({
  counter
});

export default rootReducer;

rootReducer를 정의한다.

[reducers / counter.js]

export const INCRESE = "COUNT/INCRESE";

export const increaseCount = count => ({ type: INCRESE, count});

const initalState = {
  count: 0;
};

const counter = (state = initalState, action) => {
  switch (action.type) {
    case INCRESE:
      return {
        ...state,
        count: action.count
      };
      
    // default를 쓰지 않으면 맨 처음 state에 count값이 undefined가 나오므로 default문을 꼭 넣어야 한다.
    default:
      return state;
  }
};

세부 reducer를 정의한다.

[index.js]

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import App from './App';
import rootReducer from './reducers';

// 위에서 만든 reducer를 스토어를 만들 때 넣어준다.
const store = createStore(rootReducer)

ReactDOM.render(
  //만든 store를 앱 상위에 넣어준다.
  <Provider store = {store}>
    <App />
  </Provider>
  document.getElementById('root'),
);

App에 sotre를 넣고, 만든 reducer를 반영한다.

import {useSelector, useDispatch} from 'react-redux';
import {increseCount} from 'reducers/count';

const dispatch = useDispatch();

const {count} = useSelector(state => state.counter);

const increase = () => {
  return (
    <div>
      {count}
      <button onCLick={increase}>증가</button>
    </div>
  );
};

export default Counter;

Store에서 useDispatch, useSelector로 state와 함수를 가져와서 컴포넌트에서 redux를 사용한다.

3. Zustand

Zustand란?

Zustand는 독일어로 '상태'라는 뜻을 가진 라이브러리이며 Jotai를 만든 카토 다이시가 제작에 참여하고 적극적으로 관리하는 라이브러리다. 다음과 같은 특징을 가지고 있다.

  • 특정 라이브러리에 엮이지 않는다.(그래도 React와 함께 쓸 수 있는 API는 기본적으로 제공한다.)
  • 한 개의 중앙에 집중된 형식의 스토어 구조를 활용하면서, 상태를 정의하고 사용하는 방법이 단순하다.
  • Context API를 사용할 때와 달리 상태 변경 시 불필요한 리렌더링을 일으키지 않도록 제어하기 쉽다.
  • React에 직접적으로 의존하지 안히 때문에 자주 바뀌는 상태를 직접 제어할 수 있는 방법도 제공한다.
  • 동작을 이해하기 위해 알아야 하는 코드 양이 아주 적다.

Zustand 사용법

[store.js]
1. Store 생성

import create from 'zustand';

const useStore = create(set => ({
  bears: 0,
  increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 })
}));

export default useStore

bears라는 초깃값을 선언하고 값을 조작하는 increasePopulation과 removeAllBears를 선언한다.
이 때 set을 활용한다.

  1. Store에서 생성한 useStore 불러와서 사용하기
    [App.js]
import useStore from '../store.js';

const App = () => {
  const { bears, increasePopulation, removeAllBears } = useStore(state => state);
  
  return (
    <>
      <h1>{bears} around here ...</h1>
      <button onClick={increasePopulation}>one up</button>
      <button onClick={removeAllBears}>remove all</button>
    </>
  )
};

Store에 선언한 값과 메서드를 useStore를 통해 불러와서 아주 간단하게 사용할 수 있다.

참고

profile
김재훈

0개의 댓글