React Redux

April 10, 2023

    React

리덕스는 상태관리툴로 전역에 걸쳐 접근해야 하는 상태를 관리한다. 리덕스는 자바스크립트 환경에서 작동하기 때문에 리액트는 물론이고 vue, react native, angular와 같은 기타 프레임워크에서도 작동한다. 일단 자바스크립트에서의 리덕스의 기본 동작은 간단하다.

Javascript와 Redux

import {createStore} from 'redux' const reducer = (state = 0) =>{ return state } const store = createStore(reducer)

리덕스를 불러와 컨테이너(store)를 하나 만든다. 저장소를 만들때 함수를 하나 전달해 주는데 이 함수를 reducer 라고 부른다. 이 함수는 코드 시작과 함께 store에 저장된 state를 초기화 하면서 시작하고 이후에 state는 함수 reducer를 통해 수정(modify)된다.

수정하기

만들어진 컨테이너의 state를 변경하는 방법은 action이라는 객체를이용하는 것인데 이 객체를 사용하기 위에서는 action 객체를 ruducer함수에 넘겨주어야한다. action 객체는 일어날 일을 서술하고 있는 객체로, 함수가 아님에 주의해야한다. 아래애서 공식문서에 나오는 예제를 소개한다.

import {createStore} from 'redux' const reducer = (state = 0, action) =>{ if (action.type === 'INCREMENT') return state+1 return state } const store = createStore(reducer) store.dispatch({ type: 'INCREMENT' })

이제 우리의 첫번째 예제의 reducer함수에 action이라는 객체가 전달인자로 추가되었다. 이 action이라고 불리우는 객체는 위에서 설명 했듯이 무엇이 일어날지에 대한 서술을 담은 객체이다. action객체는 컨테이너 내장함수인 dispatch에 의해 reducer함수로 전달될 수 있다. 이제 reducer함수는 전달인자로 받은 객체의 속성을 토대로 reducer함 수 안에서 state를 변경할 수 있다면 기본적인 redux의 사용법을 익힌것이나 다름 없다.

state 적용하기

import {createStore} from 'redux' const num = document.getElementById("number") const reducer = (state = 0, action) =>{ if (action.type === 'INCREMENT') return state+1 return state } const store = createStore(reducer) store.dispatch({ type: 'INCREMENT' }) store.subscribe(()=> num.innerText = store.getState());

state는 위와 같이 store의 내장함수 subscripbe에 의해 state가 변경 될때마다 DOM으로 반영 될 수 있다. 여기까지 자바스크립트에서 리덕스가 어떻게 작동 되는지 보았다. 요약하자면

  1. 전역적으로 관리하고 싶은 state를 하나 만들어서
  2. reducer를 통해 관리하는데
  3. action이라는 객체에는 reducerstate관리하는 방법이 담겨있다.
  4. 그리고 이 모든 것은 store에 담겨 있다.

자바스크립트에서는 위와 같은 일들이 피교적 간단하게 해결되었지만 리엑트에선 리액트의 컴포넌트데이터 흐름이 단방향이라는 특징 때문에 간단하게 진행되지만은 않는다. 당장 생각해 봐도 리덕스가 리액트에서 어떻게 props drilling을 해결할지에 대한 이미지가 떠오르지 않는다. 당장 storereducer만이라도 각 컴포넌트에 전달되어야 할것 처럼 보이기 때문에 상태 끌어올리기만도 못해 보이기 때문이다.

React와 Redux

그럼에도 원리는 같다.

하지만 기본적인 구동방식은 동일하다. stateaction객체가 명명한 대로 reducer에 의해 수정되며 이 수정과정 또한 dispatch를 통해 수행된다. 또한 이런 상태, 객체, 함수들은 slice라는 함수에 묶여있어 slice내의 상태는 같은 slice내에 있는 reducer에 의해서만 변경되는 베타적인 성격을 가진다. 또한 이 sliceconfigureStore()통해 생성된 store객체에 의해 관리될 수 있는데, configureStore()를 통해 생성된 store는 위의 상태,객체,함수등을 담는 용도 외에 리덕스에서 제공하는 여러 기능들을 담고있기도 하여 configureStore()이용해 리덕스 스토어를 관리하는 것이 공식문서에서 권장되고 있다. 요약하자면 리액트에서도 리덕스가 작동하는 방식은 결국 같다.

  1. 전역적으로 관리하고 싶은 state를 하나 만들고
  2. reducer를 통해 관리하는데
  3. action이라는 객체가 관리하는 명세서 같은 역할을 한다. 3-1. 이 모든것은 slice함수에 베타적으로 작동한다.
  4. 그리고 이 모든 것은 (configureStore()에 의해 만들어진)store에 의해 묶여있다.

Redux Toolkit을 이용한 React환경에서 Redux구현

이제부터 React에서 Redux를 쓰는 방법을 소개하고자 한다. 단계별로 진행 되는 과정에서 반드시 아래의 방법을 따르지 않아도 된다. 하지만 결국 reducer를 만들어 state를 관리한다는 점에서는 같다.

  1. configureStore를 이용해 빈 sotre를 만든다.
  2. createSlice에 초기상태와 action을 작성해 reducer에 넣어준다.
  3. reducer를 1번에서 만든 store에 넣어준다.
  4. 이제 리액트 컴포넌트에서 useSelector를 이용해 state에 접근하고, useDispatch를 이용해 변경할 수있다.

상태관리 툴이 항상 그렇듯이 리덕스도 provider로 리덕스 컨테이너가 적용될 범위를 감싸줘야한다. 한번 만들어진 컨테이너(store)는 Provider를 통해 적정 컴포넌트에 접근 가능하게 만들어 줄 수 있다. 먼저 store를 만든다.

import { configureStore } from '@reduxjs/toolkit' export defualt configureStore({ reducer:{} })

store에는 stateaction을 담은 reducer가 추가되어야 하지만 일단은 빈 컨테이너로 놔두고 차차 진행해보기로 한다. 빈 컨테이너를 만들었다면 비록 비었지만 적당한 컴포넌트들이 이 컨테이너에 접근할 수 있게 하기위해 Provider를 이용해 적당한 컴포넌트들을 감싸준다.

import React from 'react' import ReactDOM from 'react-dom' import './index.css' import App from './App' //./app/store 파일에 리덕스 store를 만들어둠 import store from './app/store' import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )

이렇게 앱을 감싸주게 되면 App과 그 하위 컴포넌트들은 이제 리덕스가 관리하는 상태에 접근하고 변경할 수 있게 된다. 컨테이너를 만들었으니, 이제 컨테이너를 채워보자. 위에서 설명했듯이 컨테이너는reducer를 담고있고 reduceraction객체와 state를 담고있다.

Redux State Slice

Slice를 작성할 파일 하나를 추가하자.

import { createSlice } from '@reduxjs/toolkit' export cont conterSlice = createSlice({ //state를 정의한다. 이제 counter라는 이름의 state는 state.value=0 으로 초기화 되었다. name: 'counter' initialState:{ value:0 //qty:0 와같이 추가로 속성을 더해주면 2개의 state를 하나의 slice로 관리하게 된다. //이와같은 방법으로 여러개의 state를 하나의 slice에 묶어 관리할 수 있다. }, reducer{ increment: state => { state.value+=1 }, decrement: state => { state.value-=1 } incrementByAmount: (state, action) => { state.value += action.payload } } }) // createSlice()의 인스턴스인 couterSlice는 action을 자동으로 생성한다. export const { increment, decrement, incrementByAmount } = counterSlice.actions export default counterSlice.reducer

이제 언뜻 보면 slice안에 stateaction을 담아준것 처럼 보이지만 사실 sliceincrement와 같은 코드를 action으로 만들고 "초기"state를 받아 reducer를 "만들어"주는 역할을 한다. 이후에 statereducer에 의해 변경된다고 하더라도 slice내의 initialState객체는 변하지 않는다.

Store에 Reducer추가하기

이제 우리의 비어있는 컨테이너에 슬라이스를 추가하기 위해 처음에 만들었던 빈 storeslice를 불러 넣는다.

import { configureStore } from '@reduxjs/toolkit' import coounterReducer from '../features/counter/counterSlice' export defualt configureStore({ reducer:{ counter: counterReducer //추가 } })

추가한 reducer함수에는 stateaction모두를 담고으므로 store가 필요한 정보들은 모두 넘겨준 셈이다. Provider로 공급중인 storestatereducer가 있으니 이제 컴포넌트에서 state에 접근할 수있고 변경할 수 있는 조건들을 모두 충족했다.

React Components에서 Redux State 사용하기

이제 useSelector를 이용해 state에 접근할 수 있으며, useDispatch를 통해 state를 변경할 수 있다.

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())} // state 변경 > Increment </button> <span>{count}</span> <button aria-label="Decrement value" onClick={() => dispatch(decrement())} // state 변경 > Decrement </button> </div> </div> ) }