원래 redux를 처음에 공부하다가
부스트캠프에서 프로젝트할때 recoil를 도입해서 사용했었는데
recoil이 나온지 얼마 안되다보니
redux의 기본적인 틀에 대한 코드만 메모해두려고 한다.
redux의 핵심 개념 - flex 패턴
사용자와 상호작용을 하다보며 view가 가끔씩 state을 업데이트해나갈때가 있는데,
그 영향이 다른 view나 state를 연쇄적으로 업데이트해서 서로 상호작용을 일으켜 의도치 않은
버그나 결과를 내놓는 경우가 있었다
즉, 공을 튀기면 어느곳으로 튕겨나갈지 모르는 연쇄적인 반응이 일어났던것이다.
그래서 연쇄적 업데이트를 방지하고 단방향 데이터 흐름인(undirectional data flow) 인 flex 패턴이 탄생했다.
redux는 flex 패턴을 활용한 유명한 라이브러리이다.
Flux는 단방향으로 데이터가 흐르게 하는 구조이다.
redux는 크게 4가지 파트로 이루어져 있다.
상태 변경 정보인 Action
Action을 Store 에 반영하는 Dispatcher
Action을 처리해서 상태를 변경시키는 reducer
상태를 저장하는 Store
view layer에서 상태를 변경하고 싶다면 정해진 Action을 dispatch에 담아 호출한다.
그럼 Reducer에서 그 액션을 처리하고 상태를 변경하여 store에 반영한다.
이제 연쇄적인 양방향 데이터 흐름을 단방향 데이터 흐름으로 바꾸었고, side effect가 없는
순수함수적인 개념으로 동작한다.
그럼 버튼을 누르면 숫자가 늘어나거나 줄어드는 간단한 화면을
코드로 구현해보자.
Action
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
Action 명은 그냥 unique한 식별자를 가진 이름으로 구현하면 된다.
옛날에 수학 시간에 배운 함수를 떠올려보자.
함수 F 에 x 를 넣으면 y가 나온다는 수학 공식을 떠올리면
항상 같은 x를 넣으면 같은 y가 나온다.
F(x)=y
정해진 Action과 파라미터를 dispatcher에 넣으면 (상태가 같다면) 항상 같은 결과가 나오는것을 기대할 수 있어야 한다.
즉, 함수형 패러다임 개념을 일부 가져왔다.
강제된 법칙은 아닐테지만 의도치않은 버그를 보고싶지 않다면 따르는것이 좋다.
function increment(value: number) {
return { type: INCREMENT, payload: { value } };
}
function decrement(value: number) {
return { type: DECREMENT, payload: { value } };
}
해당 방식으로 action과 payload를 미리 정의할수도 있다.
payload는 action 이외에 view에서 받고 싶은 파라미터이다.
간단한 수학공식으로 예를 들면
F(x, a) = x + a 라고 볼 수 있다.
reducer
const initState = {
value: 0,
};
const reducer = (state = initState, action) => {
const { type, payload } = action;
const { value } = state;
switch (type) {
case INCREMENT:
return { ...state, value: value + payload.value };
case DECREMENT:
return { ...state, value: value - payload.value };
default:
return state;
}
};
reducer는 state와 action을 받아서
알맞은 action에 따라 연산을 한 후 값을 반환한다.
아까 설명한 F(x)=y 의 함수(F) 역할을 하는 셈이다.
state의 초기값은 매개변수로 넣은 객체값이고, 이후 리둑스 라이브러리에서 자체적으로 관리와 갱신을 해주게 된다.
그리고 switch문 내부 가독성을 위해
case문 하나 하나 함수를 구현하는것을 추천한다.
store
const store = createStore(
combineReducers({
reducer,
}),
);
store는 리엑트의 '전역' 상태 state라 보면 이해가 편하다.
store는 한개만 있어야 한다. (a single source of truth)
만약 reducer를 너무 크게 만들지 않고 기능별로 쪼개고 싶다면 combineReducer를 활용해서 합친다.
dispatcher
import { Provider, useDispatch, useSelector } from 'react-redux';
function Test() {
const { value } = useSelector((state) => state.reducer);
const dispatch = useDispatch();
return (
<div>
<button
onClick={() => dispatch({ type: INCREMENT, payload: { value: 10 } })}
>
up 1
</button>
<button onClick={() => dispatch(increment(10))}>up 10</button>
<button
onClick={() => dispatch({ type: DECREMENT, payload: { value: 10 } })}
>
down
</button>
{value}
</div>
);
}
ReactDOM.render(
<Provider store={store}>
<Test />
</Provider>,
document.getElementById('root'),
);
dispatcher는 미리 action을 받아서 reducer를 통해 상태를 갱신한다.
함수형 컴포넌트에서 react-redux 의 useSelector, useDispatch hook을 사용하면 쉽게 구현 가능하다.
<button onClick={() => dispatch(increment(10))}>up 10</button>
<button onClick={() => dispatch({ type: DECREMENT, payload: { value: 10 } })}>
down
</button>
위 코드 중간에 해당 방식처럼 dispatch에 넣는 방식으로는 함수를 쓸 수도 있고, 아니면 객체 리터럴을 정해진 형식대로 그대로 넣어도 된다.
(무슨 방식이든 내가 기존에 구현했던 reducer와 매칭이 잘되야 한다.)
나는 미리 구현된 함수를 넣는 방식을 추천한다.
남이 봤을때 dispatch만 봐서 무슨 역할을 하는지 이해가 어려울 수 있는데,
잘 명시된 함수 명으로 힌트를 주는 방식으로 구현하는게 유지보수및 협업 측면에서 좋다고 생각된다.
(위 코드는 예시 코드이니 함수명이 매우 간결하다 😂)
그외
개인적으로
action, state, reducer 등을 한 파일에 몰아넣지 말고
각각 모듈화해서 분리(action은 action끼리, reducer는 reducer 끼리)해서 파일구조를 짜는걸 추천한다.
그리고 redux-toolkit 이라는 redux를 보다 쉽게 사용 가능하게 도와주는 라이브러리도 있다.
다른 라이브러리는 redux 기반으로 설명 되어 있는 글이 많아서 처음 배우고 있을때 보면 엄청 헷갈린다.
redux가 어느정도 익숙해지면 나중 생산성 향상을 위해 도입해보는거도 나쁘지 않는거 같다.
같이 읽어보면 좋을 글)
전역 상태를 왜 쓰는지 -> props drilling 해결
https://medium.com/front-end-weekly/props-drilling-in-react-js-723be80a08e5
기존 react는 (model이 없는) VVM 혹은 VC 패턴이라고 보는게 정확하다는 의견도 있다.
https://stackoverflow.com/questions/51506440/mvvm-architectural-pattern-for-a-reactjs-application
reference
https://redux.js.org/tutorials/fundamentals/part-1-overview
https://yoonho-devlog.tistory.com/171
'Front-end > React' 카테고리의 다른 글
react 17(개인 공부용) (1) | 2022.06.09 |
---|---|
React18(개인 공부 메모용) (0) | 2022.06.09 |
react TMI (0) | 2022.04.13 |
drag and drop으로 창 크기 조절 (0) | 2022.03.18 |
redux, redux-saga로 로딩, 에러 페이지 구현 (0) | 2022.02.12 |