TIL.13 | 리덕스(redux)와 리덕스 툴킷(redux-toolkit)

원용현·2023년 4월 5일
1

TIL

목록 보기
13/18

상태관리

모든 언어는 변수를 통해서 값을 저장하며 해당 변수에 새로운 값을 저장하거나 다른 값으로 초기화를 하며 변수의 상태를 관리한다. 자바스크립트에서는 주로 let, const를 통해서 상태를 저장하고 해당 값에 바로 저장하는 등으로 상태를 관리하고, 더 나아가 react에서는 state를 통해서 값을 저장하고 setState로 값을 변경하는 등으로 상태를 관리한다.

이렇게 만들어진 변수들을 통해서 하는 상태의 관리는 해당 값이 선언되어있는 scope 안에서만 사용할 수 있다는 특징을 가진다. 스코프 내에서만 사용할 수 있다는 점은 사용 범위를 특정지어 같은 이름의 상태변수도 다른 스코프에서 새로 선언하여 사용할 수 있게된다.

이런 상태변수들을 다른 js 파일 내부에서 사용하고 싶다면 해당 값은 export, import를 통해서 값을 가져와서 사용을 해야하는데 만약 이것이 함수 내부에 선언되어 사용된다면 해당 값을 사용하기도 복잡해지고 component 내부에서도 사용된다면 다른 component에서 사용하기 위해서 props를 이곳저곳으로 뻗어나가는 등 관리하기 복잡해지는 문제가 있다.

이것을 보완하기 위해서 나온 것이 전역상태관리이며 react에서는 주로 redux, recoil, context-api 등으로 관리를 하는데 이 글에서는 redux에 대해서 알아보려고 한다.

전역상태관리

다른 component에서 선언된 변수를 또 다른 component에서 사용하기 위해서는 위에서부터 props를 내리면서 해당 변수를 위에서부터 아래로 끌어내려서 사용해야한다. 이렇게 할 경우에 중간에 거쳐가는 모든 component에 props를 작성해야하기 때문에 중간에 하나가 꼬이면 모두 수정해야하며 type 지정 등의 귀찮음이 많이 발생한다.

하지만 전역상태관리 방법을 통해서 변수를 따로 공간을 지정해놓고 component에서 바로 해당 공간에 접근을 한다면 props를 내릴 필요없이 바로바로 가져다 사용하는 것이 가능해진다.

이것을 적용한 것이 redux 등의 전역상태관리 라이브러리들이다.

그 중 가장 많이 사용되는 라이브러리로 redux가 있는데 redux는 다음과 같이 사용된다.

redux

redux의 3가지 원칙

  1. Single source of truth
    모든 전역 state는 하나의 store라는 데이터 공간에 존재함을 의미한다. 가변 데이터인 state의 출처를 하나로 통일하여 유지보수를 쉽게 만든다.

  2. State is read-only
    state를 바꾸는 유일한 방법은 바로 action 객체를 이용하는 방법 뿐이다. 이 방법을 제외하고 state는 읽기 전용이어야 한다.
    react에서 setState 메소드를 통해서 상태를 변경하듯이 redux에서도 action이라는 객체를 통해서 상태를 변경한다.

  3. Changes are made with pure functions
    state를 변경하는 유일한 방법이 action 객체를 전달하는 것이라면, 실제로 바뀐 내용을 받아 반영하는 것은 reducer라는 순수함수이다.

Store, Action, Reducer

Store(스토어)

스토어는 상태가 관리되는 공간으로 단 하나만 존재한다.
컴포넌트와는 별개로 스토어라는 공간이 존재하여 해당 공간에서 상태들을 모두 관리한다. 만약 컴포넌트에서 상태 정보가 필요하다면 스토어에 접근하여 상태를 얻어간다.

Action(액션)

앱에서 바로 스토어에 저장하지 못하기 때문에 액션을 통해서 데이터를 전달하는데, 액션이란 앱에서 스토어로 전달할 데이터를 말한다. 기본 형태는 type만을 가진 객체 형태이며 그 외에 여러 값을 넣어서 스토어에 저장한다.

{
  type: 'type', // 필수
  payload: { // 옵션
    data: "default"
  }
}

Reducer(리듀서)

액션을 바로 스토어에 전달하지 않고 액션을 먼저 리듀서로 전달한다. 리듀서는 액션의 주문을 보고 스토어의 상태를 업데이트한다. 액션을 리듀서에 전달하기 위해서는 dispatch() 메소드를 사용한다.

데이터의 방향성

redux에서 스토어, 액션, 리듀서를 사용하는 이유에 대해서는 데이터를 한 방향으로 흐르게 하기 위해서이다. 스토어와 액션의 중간에 리듀서를 넣음으로서, 앱 - 스토어 - 액션 - 리듀서끼리의 데이터 흐름이 한방향으로 흐를 수 있도록 유지할 수 있다.

redux-toolkit

원래 redux를 앱에 적용하기 위해서는 복잡하게 여러가지를 구현하고 적용해야해서 실제 사용에 있어서 복잡함이 있었는데 redux-toolkit를 통해 간단하게 구현하는 것이 가능해졌다.

아래에서 user 로그인을 통해서 정보를 스토어에 넣고 띄우는 과정을 진행해 볼 것이다.

구현

설치

yarn add react-redux @reduxjs/toolkit

react에서 redux를 사용하기 위해서 react-redux를 설치하고, redux-toolkit을 사용하기 위해서 @reduxjs/toolkit를 함께 설치한다.

Store, slice 생성

전역 변수들을 저장해 줄 store를 먼저 생성한다.

경로는 /src/redux/store.js로 경로를 설정해주었다.

/src/redux/store.js

import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./user";

export default configureStore({
  reducer: { user: userReducer },
});

다음으로는 slice를 생성한다. redux-toolkit의 createSlice를 활용해서 코드를 작성하는데 이전에는 createAction이나 createReducer 등을 통해서 설정해줄 내용들이 많았는데 toolkit을 통해 쉽게 store를 만들어 낼 수 있게 되었다. createSlice 함수는 선언한 slice의 name에 따라서 액션 생성자, 액션 타입, 리듀서를 자동으로 생성해준다.

/src/redux/user.js

import { createSlice } from "@reduxjs/toolkit";

export const initialStateUser = {
  value: {
    name: "",
    age: 0,
    email: "",
  },
};

export const userSlice = createSlice({
  name: "user",
  initialState: { value: initialStateUser },
  reducers: {
    login: (state, action) => {
      state.value = action.payload;
    },
  },
});

export const { login } = userSlice.actions;

export default userSlice.reducer;

useSelector, useDispatch

useSelector, useDispatch를 사용해서 실제 앱에서 데이터를 관리한다.

useSelector를 통해서 store안의 데이터를 조회하는 것이 가능하고, useDispatch를 통해서 action 객체를 전달하여 store 안의 데이터를 업데이트한다.

redux의 store는 바로 import 하는 방식으로 사용하는 것이 아니라 전체 앱에 대해서 store를 설정해야한다. 해당 코드는 앱이 실행되면 가장 먼저 접근되는 index.js에서 react-redux의 Provider를 import하여 store를 prop으로 내려준다.

/src/index.js

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

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

reportWebVitals();

App.js에서는 생성해놓은 컴포넌트들을 라우팅 할 수 있도록 코드를 작성한다.

/src/App.js

import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Login from "./component/Login/Login";
import Profile from "./component/profile/Profile";

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Profile />}></Route>
          <Route path="/login" element={<Login />}></Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

login.js에서는 버튼을 누르면 원하는 데이터를 store에 넣는 코드를 작성한다. react-redux의 useDispatch를 import하여 dispatch() 메소드로 액션객체를 넘겨 store를 업데이트한다. 동시에 해당 내용을 확인할 수 있도록 페이지를 이동하도록 navigate를 함께 작성한다.

dispatch()에는 user.js에서 export const { login } = userSlice.actions;로 작성했으므로 login() 안에 데이터를 넣어서 값을 보낸다.

주의할 점으로는 dispatch로 보낸 데이터는 action 객체의 payload 안에 들어있으므로 해당 내용을 잘 확인하고 store에서 조작하도록한다.

/src/component/Login/login.js

import React from "react";
import { useDispatch } from "react-redux";
import { login } from "../../redux/user";
import { useNavigate } from "react-router-dom";

function Login() {
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const onClick = () => {
    dispatch(login({ name: "내 이름", age: 20, email: "email@gmail.com" }));
    navigate("/");
  };

  return (
    <div>
      <button onClick={onClick}>Login</button>
    </div>
  );
}

export default Login;

데이터를 확인하기 위해서 react-redux의 useSelector를 import하여 사용한다.

/src/component/Profile/profile.js

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

function Profile() {
  const user = useSelector((state) => state.user.value);

  return (
    <div>
      <h1>Profile Page</h1>
      <p> Name : {user.name}</p>
      <p> Age : {user.age}</p>
      <p> Email : {user.email}</p>
    </div>
  );
}

export default Profile;

해당 앱을 실행시켜보면 두 컴포넌트 사이에 prop등을 보내지 않고도 redux를 통해서 Login 컴포넌트에서 저장한 내용이 Profile 컴포넌트에서 바로 조회되는 모습을 볼 수 있다.

0개의 댓글