programming language/react

[React] Redux 소개 및 간단 사용하기 / redux-tookit 적용

공대키메라 2022. 6. 20. 00:20

이전 시간에는  CSR과 SSR에 대해서 간단히 알아보았다.

(궁금하면 클릭!)

 

금일은 Redux에 대해서 알아보고 이를 적용하는 법을 공부하려고 한다. 

 

다음은 참고한 내용이다.

 

출처 : 

https://react-redux.js.org/

https://redux.js.org/understanding/thinking-in-redux/motivation

https://react-redux.js.org/tutorials/quick-start

1. Redux란 무엇인가?

Redux에 대한 설명을 Redux 공식 사이트에서 확인할 수 있었다.

 

React Redux 홈페이지 화면

 

React Redux는 Redux를 위한 공식적인 React UI binding layer다. 

당신의 React component들이 Redux store로 부터 데이터를 읽고, 상태를 업테이트하기 위해 store에 action을 dispatch하도록 한다. 

 

여기서 dispatch하는 것은 reducer를 사용할 때 우리가 알아본 내용이다. 

 

Redux를 사용하면 Redux Store에 있는 데이터를 React의 컴포넌트들이 읽을 수 있다는 것인데 이말은 한곳에서 state와 action을 관리하고 언제든지 React Component에서 접근이 가능하다는 같다.

2. Redux는 왜 써야하냐?

Redux는 Redux 자체로 다른 UI layer나 프레임워크와 함께 사용될 수 있는 standalone libary입니다. (React, vue, Ember, and vanilla JS를 포함해서)

Redux 와 React가 주로 함께 사용되지만, 또한 이 둘은 독립적이다. 

어떤 프레임워크에서 Redux를 사용더라도 직접적으로 UI code로 store와 상호작용 하는 대신에, 일반적으로 UI 프레임워크와 Redux를 함께 묶기 위해 UI binding 라이브러리를 사용할 것이다. 

React Redux는 공식적으로 React를 위한 Redux UI binding 라이브러리다. 

Redux와 React를 함께 사용한다면, React Redux를 이 두개의 라이브러리를 묶기 위해 또한 사용해야 한다. 

 

공식 사이트에서 다음과 같이 Redux의 출현 동기(Motivation)에 대해 설명하고 있다.

 

이것을 해석해서 적으려 하니 너무 길어서 귀찮았다. (정말이다!)

 

그래서 내가 사용 이유를 정리하면 "많은 state를 한번에 관리하기 위해서" 이다. 

 

많은 상태가 component가 분리되면서 흩어지는데 이것을 한번에 관리하기 위해 탄생한 것이다.

 

예를 들어, 로그인한 사용자 정보에 따른 데이터를 여러 component에서 사용하고 싶을텐데 그러면 일일이 전달해주고 그것을 뽑아오고 해야 하는 아~주 번거로운 일이 생기고 만다. 

 

그것을 우리는 Redux를 통해서 한번에 관리할 수 있다.

 

이러한 기능을 제공하면서 성능 최적화를 또한 지원하고 있다. 

 

또, Community가 크기 때문에 이슈에 대해 대응이 쉽다!

 

궁금한 분은 MotivationWhy Use React Redux를 읽으면 좋다. 

3.  Redux사용하기

현재 필자 키메라가 정리하고 있는 내용을 전부 사이트(클릭!)에 나와있는 것이다. 

 

그러면 Redux를 다운받아서 적용을 해보도록 하겠다.


기존에는 공식 사이트의 내용을 잘 이해하지 못했었기에 2022.07.07 기준 내용을 보완하려고 한다. 

 

 

시작 전에 기존에 redux를 사용하면 이런 문제가 생긴다. 

 

 

createStore를 기존에는 redux로만 사용하면 사용 할 수 있는데, 보는바와 같이 현재 deprecated되어버렸다. 

 

현재 시간 기준...

 

그래서... 필자가 redux-toolkit을 사용하고 적용한 곳이 있으니 궁금하면 참고하길 바란다.

(링크 달 예정... ㄱㄷㄱㄷ ㅠㅠ)

 


 

React boilder plate 로 (npm i create-react-app redux-study) 다운을 받은 후 redux관련 library를 다운받도록 하겠다.

 

실행 명령어

npm i @reduxjs/toolkit react-redux

index.js - React에  Redux Store 제공하기

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
import store from './app/store'
import { Provider } from 'react-redux'

// As of React 18
const root = ReactDOM.createRoot(document.getElementById('root'))

root.render(
  <Provider store={store}>
    <App />
  </Provider>
)

 

React Redux는 <Provider/> 컴포넌트를 포함하는데 당신의 앱에서 나머지에서 Redux Store 를 사용할 수 있게 한다. 

 

즉, Provider나에 props로 현재 store={"우리가 유지할 store"}를 전달해주는데 이렇게 해주면 <App />에서 전부 이 store에 접근이 가능하다는 말이다. 전에 봤던 reducer와 같은 느낌이 스물스물 난다. 

counterSlice.js

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

export const counterSlice = createSlice({
  name: "counter",
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched
export const incrementAsync = (amount) => (dispatch) => {
  setTimeout(() => {
    dispatch(incrementByAmount(amount));
  }, 1000);
};

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state) => state.counter.value)`
export const selectCount = (state) => state.counter.value;

export default counterSlice.reducer;

 

공식 사이트에 위에 대한 설명을 읽으면 다음과 같다. 

 

counterSlice.js를 생성했다. 이 파일에서, Redux Toolkit에 있는 createSlice API 를 import한다. 

createSlice API를 사용하려면 slice, inisital state value, 그리고 어떻게 state를 update할 수 있는지에 대한 reducer function에 대한 정의가 필요하다. slice 가 생성되면, 생성된 Redux action 생성자와 전체의 slice에서 reducer를 export할 수 있다. 

Redux는 우리가 모든 상태를 불변적(Immutably)으로 update하기를 요구한다. - data의 복사본을 만들고 그 복사본을 update함으로!

하지만, Redux Toolkit의 createSlice와 createReducer API는 Immer를 내부적으로 사용하는데, 이게 정확히 불변하는 update가 되는 변형하는 update 로직을 쓰도록 도와준다.

 

코드를 보면 더

 

그리고 src/app/store.js에 파일을 생성한다.

store.js - Redux Store생성하기

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

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
});

 

reducers parameter안에 필드를 정의해서,  그 store가 이 slice reducer 함수를 상태 업데이트시에 다룰 수 있도록 한다. 

 

여기서 counterSlice를 다시 본다면 그것을 reducer로 선언한 것이다. 이러한 reducer들을 때에 맞게 잘 나눈 것을 여기 공식 문서에서는 slice reducer라고 표현하고 있다. 대강 해석하면 잘린 reducer인가? 그런 느낌이다. 

Count.js - useSelector, useDispatch : React Redux의 Hook 사용

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
import styles from './Counter.module.css'

export function Counter() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

 

React Redux는 React 컴포넌트에서 Redux Store와 같이 사용 가능한 한쌍의 커스텀 React Hook을 제공한다.

useSelector는 store 상태로 부터 값을 읽고 update를 감지한다. useDispatch는 store의 dispatch method로 action을 dispatch할 수 있게 한다. 

 

여기서 "Increment", "Decrement"버튼을 클릭하면 다음과 같은 일이 벌어진다. 

 

  • 응답하는 Redux action이 store를 dispatch한다. 
  • counter slice reducer가 action을 확인하고 상태를 update한다. 
  • <Counter> 컴포넌트가 store에서 새로운 상태 값을 보고 새로운 데이터를 가진 그 자체를 리렌더링한다. 

실행을 하면 다음과 같은 화면이 나온다. 

 

실행 전에 이 CSS 파일을 넣어주면 보기 좋다.

 

더보기

Counter.module.css

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

 

 


이렇게 공식 문서 사이트에서 알려주는 Redux 사용법을 통해서 어떻게 사용하는지 알아보았다. 

 

자세한 내용을 공식 사이트를 참고하며 공부하는 것이 좋다. 이것으로 살짝 필자는 부족하다는 판단에 다른 분들이 정리해 놓은 것을 더 볼 예정이다. 그래서 간단 정리이기에 다음 시간도 기대해달라!(두둥!)