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으로 반영 될 수 있다.
여기까지 자바스크립트에서 리덕스가 어떻게 작동 되는지 보았다. 요약하자면
- 전역적으로 관리하고 싶은
state
를 하나 만들어서 reducer
를 통해 관리하는데action
이라는 객체에는reducer
가state
관리하는 방법이 담겨있다.- 그리고 이 모든 것은
store
에 담겨 있다.
자바스크립트에서는 위와 같은 일들이 피교적 간단하게 해결되었지만 리엑트에선 리액트의 컴포넌트데이터 흐름이 단방향이라는 특징 때문에 간단하게 진행되지만은 않는다. 당장 생각해 봐도 리덕스가 리액트에서 어떻게 props drilling을 해결할지에 대한 이미지가 떠오르지 않는다. 당장 store
와 reducer
만이라도 각 컴포넌트에 전달되어야 할것 처럼 보이기 때문에 상태 끌어올리기만도 못해 보이기 때문이다.
React와 Redux
그럼에도 원리는 같다.
하지만 기본적인 구동방식은 동일하다. state
는 action
객체가 명명한 대로 reducer
에 의해 수정되며 이 수정과정 또한 dispatch
를 통해 수행된다. 또한 이런 상태, 객체, 함수들은 slice
라는 함수에 묶여있어 slice
내의 상태는 같은 slice
내에 있는 reducer
에 의해서만 변경되는 베타적인 성격을 가진다. 또한 이 slice
는 configureStore()
통해 생성된 store
객체에 의해 관리될 수 있는데, configureStore()
를 통해 생성된 store
는 위의 상태,객체,함수등을 담는 용도 외에 리덕스에서 제공하는 여러 기능들을 담고있기도 하여 configureStore()
이용해 리덕스 스토어를 관리하는 것이 공식문서에서 권장되고 있다. 요약하자면 리액트에서도 리덕스가 작동하는 방식은 결국 같다.
- 전역적으로 관리하고 싶은
state
를 하나 만들고 reducer
를 통해 관리하는데action
이라는 객체가 관리하는 명세서 같은 역할을 한다. 3-1. 이 모든것은slice
함수에 베타적으로 작동한다.- 그리고 이 모든 것은 (
configureStore()
에 의해 만들어진)store
에 의해 묶여있다.
Redux Toolkit을 이용한 React환경에서 Redux구현
이제부터 React에서 Redux를 쓰는 방법을 소개하고자 한다. 단계별로 진행 되는 과정에서 반드시 아래의 방법을 따르지 않아도 된다. 하지만 결국 reducer
를 만들어 state
를 관리한다는 점에서는 같다.
configureStore
를 이용해 빈sotre
를 만든다.createSlice
에 초기상태와action
을 작성해reducer
에 넣어준다.reducer
를 1번에서 만든store
에 넣어준다.- 이제 리액트 컴포넌트에서
useSelector
를 이용해state
에 접근하고,useDispatch
를 이용해 변경할 수있다.
상태관리 툴이 항상 그렇듯이 리덕스도 provider로 리덕스 컨테이너가 적용될 범위를 감싸줘야한다. 한번 만들어진 컨테이너(store
)는 Provider
를 통해 적정 컴포넌트에 접근 가능하게 만들어 줄 수 있다. 먼저 store
를 만든다.
import { configureStore } from '@reduxjs/toolkit' export defualt configureStore({ reducer:{} })
이 store
에는 state
와 action
을 담은 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
를 담고있고 reducer
는 action
객체와 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
안에 state
와 action
을 담아준것 처럼 보이지만 사실 slice
는increment
와 같은 코드를 action
으로 만들고 "초기"state
를 받아 reducer
를 "만들어"주는 역할을 한다. 이후에 state
가 reducer
에 의해 변경된다고 하더라도 slice
내의 initialState
객체는 변하지 않는다.
Store에 Reducer추가하기
이제 우리의 비어있는 컨테이너에 슬라이스를 추가하기 위해 처음에 만들었던 빈 store
에 slice
를 불러 넣는다.
import { configureStore } from '@reduxjs/toolkit' import coounterReducer from '../features/counter/counterSlice' export defualt configureStore({ reducer:{ counter: counterReducer //추가 } })
추가한 reducer
함수에는 state
와 action
모두를 담고으므로 store
가 필요한 정보들은 모두 넘겨준 셈이다. Provider
로 공급중인 store
에 state
와 reducer
가 있으니 이제 컴포넌트에서 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> ) }