[LIKELION] React JS 상태관리 라이브러리 - 추가스터디

 

상태관리 라이브러리

📌 상태관리 라이브러리란?

상태관리 라이브러리는 리액트 어플리케이션의 상태를 중앙에서 관리하는 도구로서, 다양한 컴포넌트 간에 상태를 공유하고 관리할 수 있게 해준다. 이를 통해 어플리케이션의 복잡성을 줄이고, 상태 관리에 대한 편의성과 효율성을 제공한다.

 

우선 상태(state)는 리액트에서 컴포넌트 내에 관리되는 변수이다.

리액트에서 컴포넌트들은 이런 서로의 상태를 공유해야 하고, 이때 props의 형태로 상태를 공유한다.

 

🎯 props drilling

props drilling은 리액트에서 발생하는 상태 관리의 한 종류로, 컴포넌트 계층 구조가 깊어질수록 상위 컴포넌트로부터 하위 컴포넌트로 데이터를 전달하는 것을 말한다.

일반적으로 리액트에서 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하기 위해 props를 사용한다.

props는 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 메커니즘을 제공한다.

하지만 컴포넌트 계층 구조가 깊어질수록, 중간에 위치한 여러 컴포넌트를 거쳐 데이터를 전달해야 하는 경우가 발생하고, 자식 컴포넌트간에는 상태 공유가 불가능한 문제가 발생한다. 심지어 중간 단계의 컴포넌트들은 사용하지도 않는 props를 들고 있어야 하는 문제가 생긴다.

이런 문제점들을 props drilling이라고 부르며, 프로젝트의 규모가 커질수록 상태 관리가 어려워 지는 문제가 발생할 것이다.

 

 

📌 상태관리 라이브러리를 사용하는 이유

앞선 props drilling 문제 이외에도 여러 문제점을 해결 및 장점으로 인해 상태관리 라이브러리를 사용한다.

  • 중앙화된 상태 관리 : 어플리케이션의 모든 상태를 중앙 집중화하여 관리할 수 있다. 상태 변화를 예측 가능하기 만들어 버그를 줄이고 유지 보수에도 용이하다.
  • 컴포넌트 간 상태 공유 : 컴포넌트들 간에 쉽게 상태를 공유할 수 있다.
  • 비동기 처리 : 어플리케이션에서 비동기적인 상태 업데이트가 필요한 경우, 상태 관리 라이브러리를 통해 쉽게 구현할 수 있다.
  • 타입 안정성 : 타입 시스템과 통합되어 타입 안정성을 높여준다.
  • 상태 기록과 디버깅 : 상태의 이력을 기록하거나 디버깅하기 쉽다.

 

📌 상태관리 라이브러리 종류

상태관리 라이브러리의 선택은 프로젝트의 크기, 요구 사항 및 개발자의 선호도에 따라 달라질 수 있다.

  1. React Context API : 리액트 자체에서 제공하는 기능. 외부 라이브러리를 사용하지 않고도 상태 관리를 할 수 있다. 다만, 대규모 어플리케이션에서 별도의 라이브러리를 설치하여 사용하는 것 보다는 복잡성 때문에 Redux or Mobx등이 선호된다.
  2. Redux : 가장 인기있는 상태관리 라이브러리 중 하나이다. 예측 가능하고 중앙 집중화된 상태 관리를 제공한다. 액션(Action), 상태(State), 리듀서(Reducer0로 구성되어 있다.
  3. Recoil : 페이스북에서 개발한 상태 관리 라이브러리이다. 컴포넌트 트리 안에서 비동기적으로 대규모의 상태를 공유하는 것을 쉽게 만들어준다.
  4. MobX : 반응형 상태 관리 패턴을 사용한다.

 

 

 

React Context API

리액트에서 자체적으로 제공해주는 기능으로 사용할 수 있으며, 외부 라이브러리를 사용하지 않고도 상태를 관리할 수 있다.

리액트 프로젝트에서 import하여 사용하면 된다.

 

📌 사용법

React Context API의 자세한 사용 방법은 아래 링크의 useContext 참고

https://itguswjd.tistory.com/149

 

[LIKELION] React JS Hooks - 추가 스터디

Hook Hook은 React 버전 16.8부터 React 요소로 새로 추가되었다. Hook을 사용하여 기존 클래스형 바탕의 코도를 작성할 필요 없이 상태 값과 여러 리액트의 기능을 사용할 수 있게 되었다.(클래스형 컴

itguswjd.tistory.com

 

 

📌 단점

React Context API를 사용하기 위해서는 리액트 앱 최상단(or 해당 Context를 사용하는 컴포넌트의 최상단)에 Provider를 배치한다.

이는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할이다.

이때 context를 구독하는 컴포넌트들은 이 provider의 props의 value 값이 변경될 때마다 다시 렌더링된다.

 

이때 문제점이 발생하는데, context가 변경될 때마다 useContext() 훅(Hook)을 사용한 모든 컴포넌트가 리렌더링 된다.

이 문제점은 성능 상으로 매우 안좋으며 비효율적이다.

 

또한 Redux나 Mobx와 같은 상태 관리 라이브러리보다 상태 관리 기능이 제한적이고, 컴포넌트 간의 종속성을 높이기 때문에 프로젝트의 규모가 클수록 복잡성이 증가할 수 있다.

 

따라서 Context API는 단순한 전역 상태 관리, 간단한 공유 데이터에 주로 사용하고, 대규모 프로젝트(어플리케이션)의 경우에서는 Redux 등의 상태 관리 라이브러리를 사용하는 것이 더 적합하다. 혹은 자주 변화가 일어나지 않는(수정이 많이 되지 않는) 변수나 데이터의 경우에만 사용하는 것이 좋다.

 

 

Redux

리덕스는 리액트 어플리케이션의 상태를 효율적으로 관리하기 위한 상태관리 라이브러리이다.

Redux는 예측 가능하고 변화를 추적하기 쉬운 단방향 데이터 흐름을 제공하여 상태 관리를 보다 효율적으로 처리할 수 있게 해준다.

리액트와 거의 주로 사용되지만, 다른 뷰 라이브러리나 프레임워크와도 함께 사용할 수 있다.

 

리덕스는 중앙 집중식 storage와 상태 업데이트를 위한 reducer를 사용하고, 단방향 데이터 흐름을 따른다.

  • Store : Redux에서 모든 상태 데이터가 저장되는 곳. 어플리케이션의 상태를 담고 있으며, 상태가 변화하면 자동으로 뷰가 업데이트 된다. 어플리케이션 전체의 상태를 담고 있기 때문에 단 하나의 전역 상태로 구성된다.
  • Action : 상태 변화를 일으키는 객체이다. Action은 어플리케이션에서 어떤 사건이 발생했음을 설명하는 정보를 가지고 있으며, 반드시 type이라는 필수적인 속성을 가져야 한다.
  • Reducer : 상태를 변화시키는 함수. 액션의 타입에 따라 상태를 어떻게 변경할 지를 정의한다. Reducer는 순수 함수로 작성되어야 한다. 이전 상태와 액션을 인자로 받아서 새로운 상태를 반환한다.
  • Dispatch : 액션을 발생시키는 메소드. 상태를 변경하려면 반드시 액션을 디스패치해야 한다. 디스패치를 통해 액션을 스토어로 보내 상태를 갱신하고 뷰를 업데이트 한다.
  • Middleware : 액션과 리듀서 사이에서 동작하는 확장 기능. 미들웨어를 사용하여 비동기 작업, 로깅, 예외 처리 등을 처리할 수 있다.

 

📌 Redux의 데이터 흐름

  1. 컴포넌트에서 액션을 디스패치한다.
  2. 디스패치된 액션은 미들웨어을 통해 처리될 수 있다.
  3. 액션은 리듀서로 전달되어 이전 상태와 함게 새로운 상태를 생성한다.
  4. 새로운 상태가 스토어에 저장된다.
  5. 스토어에 저장된 상태가 바뀌면 어플리케이션은 변경된 상태를 감지하고 새로운 뷰를 렌더링한다.

 

 

📌 사용법

1. 먼저, 리덕스를 리액트에 연결하기 위해 react-redux 패키지를 설치해야 한다. 이 패키지는 리덕스와 리액트를 연결해주는 컴포넌트를 제공한다.

npm install redux react-redux

 

2. 다음으로, 리덕스 스토어를 생성하고 리듀서를 작성한다.

// store.js

import { createStore } from "redux";

// 초기 상태 정의
const initialState = {
  members: [
    { id: 1, name: "John", age: 25, major: "Computer Science", job: "Software Engineer" },
    { id: 2, name: "Jane", age: 30, major: "Mathematics", job: "Data Scientist" },
    { id: 3, name: "Michael", age: 22, major: "Biology", job: "Research Assistant" },
  ],
};

// 리듀서 함수 정의
const memberReducer = (state = initialState, action) => {
  // 리듀서 코드는 간단한 예시를 위해 별도의 액션을 처리하지 않습니다.
  // 실제 애플리케이션에서는 적절한 액션 처리를 구현해야 합니다.
  return state;
};

// 스토어 생성
const store = createStore(memberReducer);

export default store;

 

3. 리덕스 스토어를 리액트 앱에 연결한다.

// index.js

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

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

 

4. 이제 리액트 컴포넌트에서 리덕스 스토어의 회원 정보를 사용할 수 있다.

// App.js

import React from "react";
import { connect } from "react-redux";

const App = ({ members }) => {
  return (
    <div>
      <h1>Members List</h1>
      <ul>
        {members.map((member) => (
          <li key={member.id}>
            <p>Name: {member.name}</p>
            <p>Age: {member.age}</p>
            <p>Major: {member.major}</p>
            <p>Job: {member.job}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

const mapStateToProps = (state) => ({
  members: state.members,
});

export default connect(mapStateToProps)(App);

 

위 코드에서 mapStateToProps 함수를 사용해 리덕스 스토어의 members 상태를 컴포넌트의 members props로 매핑한다. 이제 App 컴포넌트에서 members를 props로 받아와 회원 정보를 출력할 수 있다.

 

추가로 리덕스의 액션과 리듀서를 활용하여 데이터를 변경하는 로직을 추가하면 더욱 유용하게 상태 관리를 할 수 있다.

 

 

📌 액션, 리듀서, 디스패치를 이용해 리덕스 사용해보기

1. 리덕스 스토어를 생성하고 리듀서를 작성한다.

// store.js

import { createStore } from "redux";

// 초기 상태 정의
const initialState = {
  count: 0,
};

// 리듀서 함수 정의
const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };
    case "DECREMENT":
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

// 스토어 생성
const store = createStore(counterReducer);

export default store;

 

2. 리덕스 스토어를 리액트 앱에 연결한다.

// index.js

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

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

 

3. 리액트 컴포넌트에서 리덕스를 사용한다.

// App.js

import React from "react";
import { connect } from "react-redux";

const App = ({ count, increment, decrement }) => {
  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

const mapStateToProps = (state) => ({
  count: state.count,
});

const mapDispatchToProps = (dispatch) => ({
  increment: () => dispatch({ type: "INCREMENT" }),
  decrement: () => dispatch({ type: "DECREMENT" }),
});

export default connect(mapStateToProps, mapDispatchToProps)(App);

{connect}는 react-redux 라이브러리에서 제공하는 함수로, 리액트 컴포넌트를 리덕스 스토어에 연결하는 역할을 한다.

connect 함수를 사용하여 리액트 컴포넌트를 스토어에 연결하면 컴포넌트가 스토어의 상태를 읽고, 액션을 디스패치하여 상태를 변경할 수 있게 된다.

 

위 예시 코드에서 리덕스의 connect 함수는 두 개의 인자를 받는다.

  1. mapStateToProps : 스토어의 상태를 컴포넌트의 props로 매핑하는 함수이다. 이 함수를 통해 컴포넌트에서 스토어의 상태에 접근할 수 있다. mapStateProps 함수는 스토어의 상태를 인자로 받고, 해당 컴포넌트에서 필요로 하는 상태를 객체로 반환한다.
  2. mapDispatchToProps :  액션 디스패치 함수를 컴포넌트의 props로 매핑하는 함수이다. 이 함수를 통해 컴포넌트에서 액션을 디스패치하여 상태를 변경할 수 있다. mapDispatchToProps 함수는 dispatch 함수를 인자로 받고, 해당 컴포넌트에서 필요로 하는 액션 디스패치 함수를 객체로 반환한다.

 

 

const mapStateToProps = (state) => ({ count: state.count, });

스토어의 상태를 컴포넌트의 props로 매핑하는 역할을 한다. 위의 코드는 state.count가 count라는 이름의 props로 매핑하고 있다. state.count는 리덕스 스토어의 상태 중 count라는 이름의 데이터를 가져온다.

 

const mapDispatchToProps = (dispatch) => ({ 
  increment: () => dispatch({ type: "INCREMENT" }), 
  decrement: () => dispatch({ type: "DECREMENT" }), 
});

액션 디스패치 함수를 컴포넌트의 props로 매핑하는 역할을 한다. 

위 코드는 increment와 decrement라는 이름의 함수를 props로 매핑하고 있다.

이 함수들은 액션을 디스패치하여 상태를 변경하는 역할을 한다.

 

예를 들어, increment 함수는 dispatch({ type: "INCREMENT" })를 호출하여 { type: "INCREMENT" } 액션을 스토어로 전달한다.

그 다음 스토어의 리듀서에서 이 액션을 처리하여 상태를 변경한다.

 

export default connect(mapStateToProps, mapDispatchToProps)(App);

connect 함수는 두 개의 인자로 mapStateToProps와 mapDispatchToProps 함수를 받고, 리액트 컴포넌트를 리덕스 스토어에 연결하는 함수이다. connect 함수를 사용하여 mapStateToProps와 mapDispatchToProps 함수를 컴포넌트에 연결하면, 해당 컴포넌트는 스토어의 상태와 액션 디스패치 함수를 props로 받을 수 있게 된다.

 

위의 예시 코드는 App 컴포넌트가 connect(mapStateToProps, mapDispatchToProps)(App) 형태로 연결되어 있다.

App 컴포넌트는 count, increment, decrement를 props로 받게 되고, 이를 활용하여 상태를 출력하고 액션을 디스패치할 수 있게 되는 것이다.

 

 

 

 

Recoil

Recoil은 페이스북에서 개발한 상태관리 라이브러리로, redux와 다르게 리액트 전용 상태관리 라이브러리이다.

리액트 어플리케이션에서 상태를 효율적으로 관리하는데 사용되며, 기본적으로 리덕스와 비슷한 기능을 제공하지만, 일부 면에서 다른 특징을 가지고 있다.

 

📌 Recoil의 기본 특징 및 개념

  • Atoms :  Recoil의 기본적인 상태 단위. 어플리케이션의 상태를 정의하는데 사용된다. Atoms는 atom 함수를 사용하여 생성하며, atom 함수는 상태의 초기값을 받는다.
  • Selectors :  Atoms를 기반으로 파생된 상태를 정의하는데 사용된다. Selectors는 Selector 함수를 사용하여 생성하며, 파생된 상태의 값을 계산하는 함수를 정의한다.
  • RecoilRoot :  Recoil의 상태를 관리하기 위한 컴포넌트이다. 앱의 최상위 컴포넌트 내에서 RecoilRoot를 사용하여 Recoil의 상태를 관리할 수 있다.
  • useRecoilState : Atom 의 상태와 상태를 변경하는 함수를 반환한다. 이 훅(Hook)을 사용하면 컴포넌트에서 Recoil의 상태를 읽고 변경할 수 있다.
  • useRecoilValue :  Atom 또는 Selector의 값을 반환한다. 이 훅을 사용하면 컴포넌트에서 Recoil의 상태를 읽을 수 있다.
  • useSetRecoilState :  Atom의 상태를 변경하는 함수를 반환한다. 이 훅을 사용하면 컴포넌트에서 Recoil의 상태를 변경할 수 있다.

 

📌 Recoil의 기본 사용법

1.  먼저, recoil을 설치한다.

npm install recoil

 

 

2. recoil의 상태를 정의하는 컴포넌트 파일(recoil.js 파일)을 생성하여 Recoil 상태를 정의한다.

// recoil.js

import { atom } from "recoil";

// Atom 생성
export const counterState = atom({
  key: "counterState",
  default: 0,
});

 

 

3. 컴포넌트 파일(ex.App.js)에서 Recoil을 사용한다.

// App.js

import React from "react";
import { useRecoilState } from "recoil";
import { counterState } from "./recoil";

const App = () => {
  const [counter, setCounter] = useRecoilState(counterState);

  const handleIncrement = () => {
    setCounter((prevCounter) => prevCounter + 1);
  };

  const handleDecrement = () => {
    setCounter((prevCounter) => prevCounter - 1);
  };

  return (
    <div>
      <h1>Counter: {counter}</h1>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
    </div>
  );
};

export default App;

위 예시 코드는 Recoil의 useRecoilState 훅(Hook)을 사용하여 counterState Atom의 상태와 상태를 변경하는 함수를 받아온다.

이를 통해 상태를 읽고 변경할 수 있다.

 

 

4. index.js 파일에서 RecoilRoot를 추가하여 Recoil의 상태를 관리한다.

// index.js

import React from "react";
import ReactDOM from "react-dom";
import { RecoilRoot } from "recoil";
import App from "./App";

ReactDOM.render(
  <RecoilRoot>
    <App />
  </RecoilRoot>,
  document.getElementById("root")
);

RecoilRoot 컴포넌트로 Recoil 상태를 앱에 적용했다. 위와 같이 하면 Recoil의 상태를 앱 전체에서 공유하고 사용할 수 있게 된다.

 

 

반응형

'LIKELION' 카테고리의 다른 글

[LIKELION] React JS Hooks - 추가 스터디  (0) 2023.07.25