[React] Redux 란?
리덕스란?
Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너!
공식 문서에서 리덕스 툴킷을 사용할 것을 추천한다.(RTK)
여러분의 앱의 상태 전부는 하나의 저장소(store)안에 있는 객체 트리에 저장됩니다. 상태 트리를 변경하는 유일한 방법은 무엇이 일어날지 서술하는 객체인 액션(action)을 보내는 것 뿐입니다. 액션이 상태 트리를 어떻게 변경할지 명시하기 위해 여러분은 리듀서(reducers)를 작성해야 합니다.
- 액션 객체를 생성하는 액션 생성자
- 부수 효과를 가능하게 하는 미들웨어
- 부수 효과를 가진 동기 또는 비동기 로직을 포함하는 Thunk 함수
- ID로 항목 조회를 가능하게 하는 정규화된 상태
- Reselect 라이브러리를 사용하여 파생된 데이터를 최적화하는 메모이제이션된 셀렉터 함수
- 액션의 이력과 상태 변경을 확인할 수 있는 Redux DevTools 확장 프로그램
- 액션, 상태 및 기타 함수에 대한 TypeScript 타입
들을 제공하기 때문에 state를 중앙에서 관리할 수 있게 되는 것이다.
리덕스는 언제 쓸까?
- 앱이 크고 관리될 컴포넌트가 많아질 때(ex> 쇼핑몰)
- 반복되지만 여기 저기 쓰이는 state를 관리해야 할 때
- 컴포넌트 간 전달해야 할 props가 많아질 때
- 유저정보, 쇼핑몰 상품 정보 등 확장성이 필요할 때
등 자주, 전역 상태의 필요성, 업데이트 로직 복잡, 업뎃 시점 관리와 같은 상황에서 유용하게 이용 가능하다고 한다.
그냥 useState 쓰면 안되나요?
"정답" 은 없다!
- 다른 부분에서 이 데이터를 사용할 경우
- 이 데이터를 기반으로 파생 데이터를 생성하는 경우
- 여러 컴포넌트에서 동일한 데이터를 사용하는 경우
- 특정 시점으로 데이터를 복원해야 하는 경우
- 데이터 캐싱이 필요한 경우
- UI 구성요소 핫리로딩 중 데이터 일관성이 유지되어야 하는 경우
에 리덕스를 사용하게 끔 추천하고 있다. 예를들어, 로그인된 사용자의 유저 정보나 상태를 업데이트 하면 즉시 반영 되어야 하는 값들을 리덕스에 담으면 된다.
반대로 useState를 사용하는 경우는 단일 컴포넌트 내부에서 사용하는 값이나, 로컬 상태 관리 등에 쓸 수 있고 예를 들어, 입력 form과 같은 인풋 값들은 useState를 사용하는 것이 적절할 것이다.
그럼 리덕스는 어떻게 쓰죠?
0. 리덕스 관련 라이브러리 설치
npm install redux
공식문서에서는 코어 리덕스 대신
보일러 플레이트를 제공하는 리덕스 툴킷을 사용할 것을 추천한다.
아래 예시에서는 React.js에서 리덕스 툴킷으로 상태관리를 하는 방법을 안내할 예정이다.
npm install @reduxjs/toolkit
보조 패키지
npm install react-redux
npm install --save-dev redux-devtools
1. 리덕스 스토어 생성(configureStore)
파일 위치는 가장 root 폴더에 생성한다.(index와 동일 선상)
configureStore 란?
리덕스 스토어를 생성하는 함수(RTK 에서 사용)
(코어 리덕스 에서는 createStore 함수로 생성해야 함)
src/app/store.js
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: {}
})
이렇게 하면 빈 리덕스 스토어(저장소)를 만들고 내보낼 수 있다.
저장소가 생성될 뿐만 아니라 저장소를 검사할 수 있도록 Redux DevTools 확장도 자동으로 구성된다.
2. 리엑트에 리덕스 스토어 제공(Provider)
Provider 이란?
저장소가 생성되면 전체 애플리케이션을 감싸는 React-Redux Provider을 배치해서
리엑트 컴포넌트에서 사용할 수 있도록 해야한다.
라이브러리에 내장되어있는 컴포넌트로 store.js와 값을 prop으로 전달해 연동 시켜준다.
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './app/store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
3. 리덕스 상태 슬라이스 생성(createSlice)
createSlice, 슬라이스?
조각.. 문법..
리듀서와 관련된 로직을 간단하게 작성할 수 있게 도와주는 함수
리듀서 함수와 액션 생성자 함수가 함께 생성되어 반복적인 코드를 최소화
playload란?
배달이라는 뜻으로 액션을 전달해 준다.
데이터에 있는 id 값으로 해당하는 고유의 state에 액션을 실행하게 도와준다.
initialState란?
초기 값을 넣어 줄 수 있다.
예를들어, 카운터를 만든다고 했을때 이에 대한 기능을 추가한다고 한다면
카운터 이름, 초기 값(initialState),
증가/감소/원하는 값만큼 증가 등의 상태 업데이트 함수들을 정의(reducers)할 수 있을 것이고,
이를 전역적으로 사용가능하게 내보내 주어야 한다.
액션은 state를 변경해 주는 함수
src/features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
4. 슬라이스 리듀서 스토어에 추가
리듀서 변수로 정의되어있는 필드 이름으로 저장소에있는 슬라이스 리듀서의 기능들을 핸들링 가능하다!
app/store.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export default configureStore({
reducer: {
counter: counterReducer
}
})
5. 컴포넌트에서 상태와 액션 사용하기(useSelector, useDispatch)
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>
)
}
디스패치는 알려주다 라는 뜻. 실행시킬 액션을 전달해 주는 매개 변수
useSelector의 리턴값이 다를 경우 자동으로 리렌더링 처리를 한다.
그렇기 때문에 하나씩 state 값을 받아와야 렌더링을 최적화할 수 있다.
잘못된 useSelector 사용 : 구조분해 할당의 경우
const { count, name } = useSelector(state => state.counter)
최적화 된 useSelector 사용 : 여러번 사용하기
const count = useSelector(state => state.counter.value);
const name = useSelector(state => state.counter.nameValue);
마지막으로, Redux 와 Context API 차이점은?
Redux와 Context API는 모두 React에서 전역 상태 관리를 위해 사용되는 방법이다. Redux는 상태 변화를 예측하고, 액션 및 리듀서를 작성해야 하지만, Context API는 간단하게 전역 데이터를 공유할 수 있다.
Context API는 state를 변경할 때 컴포넌트를 전부다 재 렌더링하고
필요한 컴포넌트마다 import 하는 과정을 필요로 한다.
리덕스는 재렌더링 없이 값을 업데이트 시킨다.
리덕스는 제공하는 기능이 많은데 이 중 Redux-Persist를 이용한다면 테스트 할 때 백엔드와 연동되지 않은 값들은 새로고침 시 자꾸 초기값으로 바뀌어 불편했던 경험이 있는데 이를 개선할 수 있을 것.
또 리덕스는 타입스크립트로 엄격하게 코드를 짤 수 있다.
참고 자료 : https://ko.redux.js.org/introduction/getting-started/
+ 멘티님의 강의