2024년 7월 30일 화요일

ReactJS : Redux

ReactJS : Redux

Redux

Redux란 한마디로 "Javascript 상태관리 라이브러리"이다.

여기서 말하는 상태관리란 컴퍼넌트내부에서 사용하는 데이터의 상태를 관리하는 라이브러리이다.
프론트엔드에서는 주로 데이터를 화면에 표시를 하는데, 데이터를 관리함으로서 데이터를 화면에 표시하는 상태를 제어할수 있다.

Redux를 이야기하기 전에 Flux에 대해 간단히 알아보자.
MVC 모델은 View-Model-Controller간의 복잡도가 증가하고 이로인해 데이터흐름의 파악이 힘들어 지는 경우가 있었다.
특히 블록(또는 컴퍼넌트) 구조로 되어 있는 복잡한 화면구성의 웹애플리케이션의 경우 부모컴퍼넌트에서 자식,자식의 자식 컴퍼넌트에 데이터를 전달(prop)한다든지, 이벤트를 발행시켜서 데이터를 갱신할때 “누가 데이터를 보관하고, 어떻게 이벤트를 전파할것인지” 기준을 정하는게 어렵다.

Flux 는 단방향으로 데이터를 흐르도록 하여, View가 Model을 직접 참조하지 않고 단지 상태값만 가지게 함으로서 상태변화에 따른 필요한 부분만 갱신하도록 하는 구조이다.

Action → Dispatcher → Store → View (갱신) → Action → D → S → V…

Flux 패턴의 가장 큰 장점은 개발 흐름이 단방향으로 흐르기 때문에 훨씬 파악하기 쉽고 코드의 흐름이 예측 가능(Predictable)하다는 것이다.

Redux 는 Flux 와 기본개념은 같지만, Redux는 store를 한개로 유지하고 reducer라는 것으로 자료를 갱신(새로운 상태를 생성)하여 view를 업데이트하는 구조이다.

Action → Dispatch → Reducer → Store 순서로

Redux 관련 용어

Store(스토어)

상태가 관리되는 공간이다.
각 Component는 Store의 데이터를 통해 업데이트한다.

Action(액션)

View에서 상태변경을 요청하는 단계이다. 화면을 갱신하기 위해서는 액션을 통해서 주문을 한다.

Reducer(리듀서) 또는 Dispatch(디스패치)

Reducer 는 액션을 스토어에 전달하는 역활을 한다. 즉 액션이라는 주문서를 보고 스토어의 데이터를 갱신한다. Dispatch 를 통해 Reducer 를 호출한다.

Subscribe(구독)

구독은 데이터가 갱신될때 UI를 갱신할수 있다.
useSelector를 사용하면 간편하게으로 subscribe 상태를 만들수 있다.

Redux toolkit

현시점에서 reactjs 에서 redux 를 사용할때에 react-redux + @reduxjs/toolkit 의 조합으로 사용하기를 권장당하고 있다.

따라서 createStore 보다는 createSlice(아…네이밍센스 극혐)을 이용해야 한다.
좋은점은 기존의 createStore, action,dispatcher정의 등 따로따로 정의하던것을 createSlice 를 이용하여 한번에 쉽게 만들면된다.

// createSlice.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

// 상태 타입 정의
export interface AppState {
  number: number;
}

// 초기 상태
const initialState: AppState = { number: 1 };

// 슬라이스 생성
const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.number++;
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.number += action.payload;
    },
  },
});

// 컴퍼넌트에서 사용하기 위해서 생성된 슬라이스의 액션과 리듀서를 공개(export)한다
export const { increment, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

store.ts 파일을 만들어서 스토어를 설정하자.

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: counterReducer,
});

// 루트 상태 타입 정의
export type AppState = ReturnType<typeof store.getState>;

export default store;

컴퍼넌트에서 사용해 보자.
먼저 HomePage.tsx 를 만들어서 Redux 로 연동하자.

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { AppState } from '../store';
import { increment, incrementByAmount } from '../counterSlice';

const HomePage: React.FC = () => {
  const number = useSelector((state: AppState) => state.number);
  const dispatch = useDispatch();
  return (
    <div>
      <h1>{number}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>Increment by 5</button>
    </div>
  );
};

export default HomePage;

app.tsx 에서 Homepage를 불러내자.

import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import HomePage from './components/HomePage';

const App: React.FC = () => {
  return (
    <Provider store={store}>
      <div>
        <h1>Hello StackBlitz!</h1>
        <p>Start editing to see some magic happen :)</p>
        <HomePage />
      </div>
    </Provider>
  );
};

export default App;

만일 reducer 를 여러개 만들었다면, combineReducers 로 묶으면 된다.

//store.ts
import { configureStore } from '@reduxjs/toolkit';
import { combineReducers } from 'redux';
import counterReducer from './features/counterSlice';
import inputReducer from './features/inputSlice';

const rootReducer = combineReducers({
  counter: counterReducer,
  input: inputReducer,
});

const store = configureStore({
  reducer: rootReducer,
});

export type AppState = ReturnType<typeof store.getState>;
export default store;

모듈별로 한다면 아래와 같다.

src/
│
├── components/
│   ├── HomePage.tsx
│   └── About.tsx
│
├── features/
│   ├── counter/
│   │   ├── counterSlice.ts
│   │   └── Counter.tsx
│   ├── input/
│   │   ├── inputSlice.ts
│   │   └── Input.tsx
│
├── App.tsx
│
└── store.ts

0 comments:

댓글 쓰기