Redux(리덕스)란 자바스크립트 상태관리 라이브러리이다.
우리는 useState를 사용할 경우 컴포넌트 내부에 state를 만들고, 함수로 state를 바꾼다. 그렇기 때문에 state는 컴포넌트에 종속되는 것은 당연한 결과이다. redux는 컴포넌트에 종속되지 않고, 상태관리를 컴포넌트 바깥에서 한다. 프로젝트 루트 레벨에서 store라는 곳에 state를 저장하고, 모든 컴포넌트는 store에 구독을 하면서 state와 그 state를 바꾸는 함수를 전달 받게 된다. 함수를 바꿈으로 state가 바뀌면 해당 state를 바라보고 있는 컴포넌트는 모두 리렌더링 된다.
우리는 useState를 사용할 경우 컴포넌트 내부에 state를 만들고, 함수로 state를 바꾼다. 그렇기 때문에 state는 컴포넌트에 종속되는 것은 당연한 결과이다. redux는 컴포넌트에 종속되지 않고, 상태관리를 컴포넌트 바깥에서 한다. 프로젝트 루트 레벨에서 store라는 곳에 state를 저장하고, 모든 컴포넌트는 store에 구독을 하면서 state와 그 state를 바꾸는 함수를 전달 받게 된다. 함수를 바꿈으로 state가 바뀌면 해당 state를 바라보고 있는 컴포넌트는 모두 리렌더링 된다.
우리가 원하는 state가 자식의 자식의 자식에서 사용한다면 props를 내리고 또 내리고 또 내려야한다. 또 그 state를 바꾸기 위한 함수를 또 내리고 내린다. 이렇게 되면 코딩을 실수하기 쉽고 잘하고 있는 것인지 의문이 들것이다. redux의 store는 프로젝트 루트레벨에 위치하고, 해당 store를 구독하는 컴포넌트는 모두 state와 state를 바꾸는 함수를 받을 수 있게 된다. 어느 위치에 있든 상관 없이 단 한번에 상태를 받을 수 있게 된다.
redux는 기본적으로 flux 패턴을 따른다.
Action -> Dispatch -> Store -> View
redux의 데이터 흐름은 동일하게 단방향으로 view(컴포넌트) 에서 Dispatch(store에서 주는 state를 바꾸는 함수) 라는 함수를 통해 action(디스 패치 함수 이름) 이 발동되고 reducer에 정의된 로직에 따라 store의 state가 변화하고 그 state를 쓰는 view(컴포넌트) 가 변하는 흐름을 따른다.
yarn add redux react-redux
//또는
npm install redux react-redux
Store는 상태가 관리되는 오직 하나의 공간이다.
Action(액션)은 앱에서 스토어에 운반할 데이터를 말한다.(주문서) Action(액션)은 자바스크립트 객체 형식으로 되어있다.
{
type: 'ACTION_CHANGE_USER', // 필수
payload: { // 옵션
name: '유정',
age: 30
}
}
액션을 전달하는 함수.
불변성을 지켜야하는 이유는 redux는 이전 state와 바뀐 state를 구분하는 방법이 참조값이 바뀌었는지 학인하고, 참조값이 바뀌면, state가 바뀌었다고 redux가 인식하여, 해당 state를 사용하는 컴포넌트에게 리렌더링을 요청하기 때문이다. 그렇기 때문에, state.test = action.test와 같이 직접 state를 변경하면 참조값이 변하지 않아서 redux는 값이 바뀌었다고 인식하지 않고 리렌더링 되지 않는다. 불변성을 유지 하여야한다. 따라서 state.test={ …test, action.test }
또는 immer 라는 라이브러리를 사용하여 불변성을 유지할 수 있다.
import { combineReducers } from "redux"
import counter from "./counter"
const rootReducer = combineReducers({
counter,
})
export default rootReducer
2-1 .세부 reducer 정의
import { INCREASE, DECREASE, CounterActionTypes } from "src/types/actions"
export const increaseCount = (): CounterActionTypes => ({ type: INCREASE })
export const decreaseCount = (): CounterActionTypes => ({ type: DECREASE })
const initialState = {
count: 0,
}
const counter = (state = initialState, action: CounterActionTypes) => {
switch (action.type) {
case INCREASE:
return {
...state,
count: state.count + 1,
}
case DECREASE:
return {
...state,
count: state.count - 1,
}
default:
return state
}
}
export default counter
2-2. type 정의
interface CounterState {
counter: {
count: number
}
}
export default CounterState
2-3. action정의
export const INCREASE = "COUNT/INCREASE"
export const DECREASE = "COUNT/DECREASE"
interface IncreaseAction {
type: typeof INCREASE
}
interface DecreaseAction {
type: typeof DECREASE
}
export type CounterActionTypes = IncreaseAction | DecreaseAction
Provider란 ? react-redux 라이브러리 안에 있는 컴포넌트. 리액트 앱에 스토어를 쉽게 연결하기 위한 컴포넌트이다.
import { Provider } from "react-redux"
import store from "../store"
import { AppProps } from "next/app"
function MyApp({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
export default MyApp
import React from "react"
import { useDispatch, useSelector } from "react-redux"
import { increaseCount, decreaseCount } from "../reducers/counter"
import CounterState from "src/types/state"
const Counter = () => {
//component
const dispatch = useDispatch()
const count = useSelector((state: CounterState) => state.counter.count)
const increment = () => {
dispatch(increaseCount())
}
const decrement = () => {
dispatch(decreaseCount())
}
return (
<div>
Count: {count}
<button onClick={increment}>Increase Count</button>
<button onClick={decrement}>Decrease Count</button>
</div>
)
}
export default Counter
다음과 같이 store에서 useDispatch, useSelector 로 state와 함수 가져와서 필요시 호출해주면 된다.
useSelector란?
-redux의 state 조회 (즉, 스토어에 있는 데이터들 조회)
useDispatch란?
-생성한 action 실행