[React] 상태 관리

sywoo0109·2023년 7월 19일
0

React

목록 보기
6/7
post-thumbnail

1. 상태 관리

  리액트에서 상태란 컴포넌트의 렌더링 결과나 동작에 영향을 주는 컴포넌트 내부에서 관리되는 데이터라고 했었다. 상태가 하나의 컴포넌트 내부에서만 쓰일 때도 있지만 다른 컴포넌트도 이 상태의 영향을 받아야만 할 때도 있다. 이전 포스트에서 설명한 상태 끌어올리기 역시 그런 상황에서 만들어진 해결법이다. 이렇게 특정 컴포넌트 안에서만 관리되는 상태를 로컬 상태라고 하며 여러 컴포넌트 혹은 프로젝트 전반에 걸쳐서 관리되야 하는 상태는 전역 상태라고 부른다. 문제는 서로 다른 컴포넌트가 하나의 상태를 사용하고 있다면 이 출처는 오직 한 곳이어야 한다는 점이다. 상태 뿐만 아니라 어떤 데이터의 무결성을 위해서 동일한 데이터는 항상 같은 곳에서 가져와야 한다는 원칙을 Single source of truth(신뢰할 수 있는 단일 출처)이라고 한다. 상태 관리는 이런 원칙 속에서 여러 컴포넌트들이 공유하고 관리하는 상태를 어디에서 정의하고 어떻게 사용할지에 대한 전략을 담고 있는 개념이다.

2. Props Drilling

  Props Drilling은 기본 리액트에서 상위 컴포넌트에서 하위 컴포넌트까지 속성을 전달하는 과정에서 여러 계층의 중간 컴포넌트를 거쳐가는 현상을 의미한다. 문제는 사실 중간에 위치한 컴포넌트들은 이 데이터가 필요 없음에도 하위 컴포넌트에게 전달하기 위해 속성을 받아서 다시 전달하는 과정을 거쳐야한다는 것이다. 웹 어플리케이션이 복잡해지고 컴포넌트 계층 구조가 깊어질수록 이런 현상은 불가피하게 발생해서 가독성을 떨어트리며 만약 해당 데이터가 변경될 수 있는 값이라면 관여된 컴포넌트들이 매번 전부 리렌더링 될 것임으로 성능 저하로도 이어진다. 이런 상황에서 등장한 상태 관리 라이브러리들은 전역 상태 저장소를 제공해서 전역 상태를 효율적으로 관리할 수 있으며 더불어 소위 Props Drilling이라고 부르는 문제를 해결할 수 있다.

3. Redux

  이 포스트에서 소개할 Redux는 많은 개발자들이 사용하고 있는 상태 관리 라이브러리로 리액트와 마찬가지로 단방향 데이터 흐름을 따르는 아키텍처를 가지고 있어 상태 값의 예측 가능성이 높고 중장 집중식 상태 저장소를 제공해서 어플리케이션의 규모와 상관없이 효율적인 상태 관리가 가능하다는 평가를 받고 있다. 우선 외부 라이브러리이기 때문에 패키지 매니저를 통해 설치하고 시작할 수 있다.

  Redux가 실제로 작동되는 로직을 우선 간단하게 설명하자면

1. 상태가 변경되어야 하는 이벤트가 발생하면, 변경될 상태에 대한 정보가 담긴 Action 객체가 생성
2. Action 객체를 Dispatch 함수의 인자로 전달
3. Dispatch 함수는 Action 객체를 Reducer 함수로 전달
4. Reducer 함수는 Action 객체의 값을 확인하고, 그 값에 따라 전역 상태 저장소 Store의 상태를 변경
5. 상태가 변경되면, React는 화면을 다시 렌더링

  즉, Redux에서는 Action → Dispatch → Reducer → Store 순서로 데이터가 단방향으로 흐르고 있다. 그렇다면 본격적인 구현은 어떻게 되는지 살펴보자. 포스팅에서 설명하는 순서는 큰 의미가 있는 것은 아니고 위에서 설명한 로직에 등장하는 순서대로 설명하고 있다. 우선 상태를 변경하기 위해 발생하는 이벤트를 나타내는 Action 객체부터 구현해보자.

const incrementAction = {
  type: 'INCREMENT',
  payload: 1
};

  Action 객체는 위처럼 일반적으로 type 속성과 필요한 추가 데이터를 가지고 있다. 위의 예시에는 어떤 상태를 1 증가시킬 때 사용할 수 있을법한 Action 객체를 하나 구현했다. 그 다음으로는 Reducer 함수를 작성해야 한다.

const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + action.payload;
    case 'DECREMENT':
      return state - action.payload;
    default:
      return state;
  }
};

  Reducer 함수는 액션에 따라 상태를 업데이트하는 함수이다. 리듀서는 현재 상태와 액션을 인자로 받아서 새로운 상태를 반환하는 구조로 되어 있다. 이때 유의할 점은 Reducer 함수는 의도치 않은 외부 요인으로 인해 상태가 변경되는 것을 막기 위해 순수 함수로 작성해야 한다. 위의 예시에서는 action 인자로 전달받을 Action 객체의 type의 값에 따라 현재 상태와 Action 객체의 payload의 값과의 연산을 진행하게 구현되어 있다. 매개변수 state를 보면 여태 본 것과 다르게 할당 연산자가 붙어있는데 이는 만약 state에 값이 들어오지 않았을 경우를 대비한 초기값 설정이며 이런 문법 자체는 다른 곳에서도 똑같이 사용할 수 있다. 위에서 작성한 예시 Action 객체 incrementAction이 counterReducer에 들어왔다면 상태에 1을 더한 값을 반환할 것이다. 참고로 Reducer 함수가 여러 개라면 combineReducers 메서드를 사용해서 하나로 합칠 수도 있다. 그 다음엔 Store를 생성해야 한다.

import { createStore } from 'redux';

const store = createStore(counterReducer);

  Store란 상태와 리듀서를 연결하는 객체이자 전역 상태 저장소이다. 생성하는 방법은 redux 라이브러리에서 createStore를 사용하면 되고 인자로 앞에 작성한 리듀서를 전달해주면 된다. 그 다음에는 컴포넌트와 스토어를 연결하는 과정이 필요하다.

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

  리액트 컴포넌트에서 Redux 스토어를 사용하기 위해 react-redux 라이브러리의 Provider 컴포넌트를 사용해야 한다. Provider 컴포넌트로 감싸고 스토어를 하위 컴포넌트에 전달한다. 위의 예시에서는 루트 컴포넌트에서 작업하고 있다고 가정하고 앞서 구현한 Store는 다른 파일로 구현해서 import 한다고 가정하고 작성했다. 여태까지는 App 컴포넌트 하나만 있던 루트 컴포넌트에서 App 컴포넌트 상위에 react-redux 라이브러리에서 가져온 Provider 컴포넌트를 배치하고 store를 속성으로 전달하고 있다. 이제 설정은 전부 끝났고 컴포넌트에서 상태 및 액션을 사용하기만 하면 된다. 그 방법은 react-redux 라이브러리에서 제공하는 훅을 사용하는 것이다. 그 중에서도 크게 useSelector(), useDispatch() 2개의 메서드만 있다면 기본적인 전역 상태 관리가 가능하다.

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { incrementAction, decrementAction } from './actions.js';

export default function counter() {
  const dispatch = useDispatch();
  const state = useSelector((state) => state);

  const plusNum = () => {
    dispatch(incrementAction());
  };

  const minusNum = () => {
    dispatch(decrementAction());
  };

  return (
    <div>
	  <button onClick={plusNum}>Increment</button>
      <span>{count}</span>
      <button onClick={minusNum}>Decrement</button>
    </div> 
  );
}

  위의 예시에서는 Action 객체들은 다른 파일에 구현했다고 가정하고 작성했다. react-redux 라이브러리에서 useDispatch, useSelector 함수를 불러와서 사용하는 것을 볼 수 있다. useSelector는 Redux Store의 상태를 가져오며 따라서 이제 state라는 변수에는 Redux가 관리하고 있던 전역 상태의 값이 담기게 된다. useDispatch는 Action을 dispatch 하는데 사용하며 따라서 plusNum이나 minusNum 함수가 실행되는 이벤트인 버튼을 클릭하는 상호작용을 하면 각각 incrementAction, decrementAction이라는 Action이 발생 후 dispatch되며 Dispatch 함수에서는 Reducer 함수로 Reducer 함수는 값에 따라 Redux에서 관리하는 상태를 변경시키고 기존의 전역 상태를 사용하고 있던 컴포넌트들은 리렌더링되는 일련의 과정이 발생하는 것이다.

1개의 댓글

comment-user-thumbnail
2023년 7월 19일

소중한 정보 감사드립니다!

답글 달기

관련 채용 정보