0. Redux를 배우게 된 계기
요즘 회사에서 Redux 기술을 사용하고 있기 때문에 나도 이 기술과 진득하게 대면하게 되었다.
기왕 배울 거 기본부터 제대로 배우기 위해 생활코딩 님의 무료 Redux 강의를 들었으며,
이를 통해 배운 내용들을 정리해보려고 한다.
1. Redux
개요
A predictable state container for JavaScript app
- JS로 만든 애플리케이션을 위한 예측 가능한 상태들의 연속
- 애플리케이션의 복잡성을 낮추고, 우리의 코드가 어떤 결과를 가져올지 예측 가능하게!
Single Source of Truth
- Redux는 하나의 상태이며, 상태는 그냥 객체
- Redux는 하나의 객체에 애플리케이션의 모든 데이터를 중앙 집중적으로 모아서 관리
- 단 하나의 state를 유지하는 것을 통해 애플리케이션의 복잡성을 낮추는 것이 Redux의 첫번째 컨셉
undo / redo
- 데이터 수정 시 원본을 복제하고, 복제본을 수정해서 그것을 원본으로 만듦
- 각각의 상태들은 변화해도 서로에게 영향을 주지 않게 됨
- 과거 시점의 상태도 꼼꼼히 레코딩하기 때문에 문제 파악에도 용이
Redux 구성 요소
store
- Redux의 핵심
- 모든 정보가 저장되는 곳
state
- store 안의 실제 정보
- state에 직접 접근하는 것은 금지되어 있으며, 인가된 함수를 통해서 접근해야 함
reducer
- store를 만드려면 우선 reducer 함수를 만들어서 공급해줘야 함
- Redux에서 가장 어려운 친구이며, reducer 함수 작성이 곧 Redux를 만드는 과정이라고 해도 과언이 아님
- dispatch 함수로부터 현재 state 와 action 을 받아서 새로운 state 값을 만들어내고 반환
render
- 현재 state를 반영하는 UI
store 창고 직원들 (함수들)
- getState: store에서 값을 가져와서 render 함수에게 전해줌
- subscribe: store의 state 값이 바뀔 때마다 render 함수를 호출하게 해줌
- dispatch: 받아낸 action 타입에 따라 작업 후, reducer 및 subscribe 함수와 연동
❖ 코드 예시
2. React Redux
개요
- React와 Redux를 연결해주는 라이브러리
- 상태에 대한 변경사항이 필요한 컴포넌트들에게만 변경사항을 전달해줌
- 소문을 전파하듯 작동하던 React 사회에 Redux 라는 언론사를 세우게 될 것
❖ 공식 홈페이지
Provider 설정 (feat. index.js)
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
Redux의 store를 앱 전체에 공급함으로써 모든 컴포넌트가 store를 사용할 수 있도록 설정
connect()() 함수
connect(mapStateToProps, mapDispatchToProps)(WrappedComponent)
mapStateToProps
- Redux의 state를 React의 props로 매핑
- Redux에서 store 내부의 값이 변경될 때마다 호출되도록 약속된 함수
- 호출될 때마다 Redux의 state를 인자로 받을 수 있도록 약속되어 있음
mapDispatchToProps
- Redux의 dispatch를 React의 props로 매핑
- store.dispatch 라는 API가 공급됨
WrappedComponent
- connect() 함수에서 반환된 함수는 래핑된 컴포넌트에서 재실행되어야 함
- presentaional component를 인자로 전달할 것
❖ 내부적으로 컴포넌트가 렌더링될 때 subscribe 되고, 꺼질 때 unsubscribe 되어짐
❖ 후술할 함수들이 새롭게 등장함으로써 자주 사용되지는 않고 있음
useSelector & useDispatch
connect()() 메소드의 2가지 인자가 분리된 함수이며, 용도에 맞게 한 가지 씩만 활용 가능
❖ useSelector 예시
export default function WrappingDisplayNumber () {
const number = useSelector((state) => state.number);
return <DisplayNumber number={number} />
}
❖ useDispatch 사용 예시
export default function WrappingAddNumber () {
const dispatch = useDispatch();
return <AddNumber onClick={(size) => {
dispatch({ type: 'increment', size })
}} />
}
3. Redux toolkit
개요
- 기존 Redux의 문제점들(미들웨어 설치 필요, 반복되는 코드, 불변성 유지의 어려움)을 타파하기 위해 등장
- 기능별로 작은 store 들을 만들고, 이를 slice 라고 부름
createSlice
const counterSlice = createSlice({
name: 'counterSlice',
initialState: { value: 0 },
reducers: {
up: (state, action) => {
state.value += action.payload;
}
}
});
- 이름, 초기값, reducers 설정
- 각 reducer 들은 action.type 을 지정해서 함수를 연결할 수 있음
- 더이상 객체를 복사한 뒤 복제본을 수정하고 새로운 객체로서 반환할 필요가 없음!
configureStore
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
- 각 slice 들의 reducer 들을 할당해줘야 함
- configureStore 는 이러한 개별 reducer 들을 한꺼번에 관리할 수 있게 해줌
createAsyncThunk
const asyncUpfetch = createAsyncThunk(
'counterSlice/asyncUpFetch',
async () => {
const resp = await fetch('https://api.counterapi.xyz/hit/loko1124.tistory.com/visits');
const data = await resp.json();
return data.value;
}
);
const counterSlice = createSlice({
name: 'counterSlice',
initialState: {
value: 0,
status: 'Welcome'
},
reducers: {
up: (state, action) => {
state.value += action.payload;
}
},
extraReducers: (builder) => {
builder.addCase(asyncUpfetch.pending, (state) => {
state.status = 'Loading';
});
builder.addCase(asyncUpfetch.fulfilled, (state, action) => {
state.value = action.payload;
state.status = 'Complete';
});
builder.addCase(asyncUpfetch.rejected, (state) => {
state.status = 'Fail';
});
}
});
- 비동기 작업을 처리하는 action을 만들어주는 친구
- pending, fulfilled, rejected 상태를 가질 수 있음
- 이 3가지 상태를 처리하는 reducer 는 extraReducers 를 통해 설정 가능
❖ 동기 작업은 reducers / 비동기 작업은 extraReducers
❖ 동기 작업은 action create 를 내부에서 자동으로 생성하지만, 비동기 작업은 그렇게 하지 못함
4. Redux는 안 좋은 기술인가?
필자는 개발 공부를 처음 시작한 이래로 프론트엔드 기술을 깊이 공부했던 적은 많이 없었다.
하지만 그럼에도 불구하고 'Redux를 사용하는 것이 과연 올바른 방향인가?' 에 대한 의문점들을
업계 소식들을 통해서 심심치 않게 들어봤다.
Redux 대신 MobX를 사용하면 그래도 최소한 코딩하는 로직들은 덜 복잡해진다는 얘기도 들었고,
아예 전역 상태 관리가 아닌, props drilling 기법으로 개발하는 게 효율적이라는 얘기도 들었다.
생활코딩 님의 강의에서는 하나의 prop을 넘겨줘야 하는데 컴포넌트가 1억 개라면 어떨지 상상해보라고 해주셨지만,
사실 1억 개의 컴포넌트들을 다룰 일이 있을까 싶기도 하다.
그래서 필자가 생각하기에는 결국 회바회 팀바팀이라고 생각한다.
구성원들에게 익숙한 기술 스택들이 있을 것이고, 구성원들의 러닝 커브도 고려해봐야 할 것이다.
동시에 우리 서비스가 얼마나 많은 페이지와 컴포넌트들을 갖는지도 생각해봐야 할 것이다.
개인적인 관점에서는 Redux를 배워보는 경험이 나쁘지 않았다.
프론트엔드 기술들이 낯선 상황에서, 어쨌든 프론트엔드 기술의 큰 축을 담당하고 있는 한 기술을 배움으로써,
필자는 조금이나마 프론트엔드 기술 역량을 증진시킬 수 있었다.
Redux의 옳고 그름을 떠나서, 필요할 때 '선택'해서 사용할 수 있는 기술 스택이 생겼다는 점에 대해 만족한다.