: 컴포넌트 코드로부터 상태관리 코드를 분리 가능
: 미들웨어 활용한 다양한 기능 추가 가능
- 강력한 미들웨어 라이브러리 (redux-saga)
- 로컬 스토리지에 데이터 저장하기 및 불러오기
: SSR시 데이터 전달 간편
: 리액트 콘텍스트보다 효율적인 렌더링 가능
import React, { userContext, createContext, useReducer } from 'react';
// 컨텍스트
const AppContext = createContext({});
const DispatchContext = createContext(() => {});
// 루트 컴포넌트
export default function App() {
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
return(
<>
<AppContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
<User />
<Product />
</DispatchContext.Provider>
</AppContext.Provider>
</>
);
}
// 리듀서
const INITIAL_STATE = {
user: {name: 'mike'},
produce: {name: 'iphone'},
}
function reducer(state, action) {
switch (action.type) {
case 'setUserName':
return {
...state,
user: {...state.user, name: action.name}};
default:
return state;
}
}
📍 뷰 → 액션 → 미들웨어 → 리듀서 → 스토어 → 뷰
//일반적이지 않은 예
store.dispatch({ type: 'todo/ADD', title: '영화 보기', priority: 'high' });
store.dispatch({ type: 'todo/REMOVE', id: 123 });
store.dispatch({ type: 'todo/REMOVE_ALL' });
//일반적인 예
function addTodo({ title, priority }) {
return { type: 'todo/ADD', title, priority };
}
store.dispatch(addTodo({ title: '영화 보기', priority: 'high' }));
✔️ type 속성값 : 액션 구분해줌 → 유니크 해야함
- 상수 변수로 만드는 것이 좋음
✔️ action creator : 각 액션 구조를 일관성 있게 만들기 위함
const myMiddleware = store => next => action => next(action);
⇒ 미들웨어의 모양
📍 상태값을 가져오는 코드
const printLog = store => next => action => {
console.log(`prev state = ${JSON.stringify(store.getState())}`);
const result = next(action);
console.log(`next state = ${JSON.stringify(store.getState())}`);
return result;
}
📍딜레이 후에 리듀서가 실행되게 하는 코드
const delayAction = store => next => action => {
const delay = action.meta?.delay;
if(!delay) {
return next(action);
}
const timoutId = setTimeout(() => next(action), delay);
return function cancel() {
clearTimout(timeoutId);
};
};
const myReducer - [state = {name: 'mike' }, action} => {
console.log('myReducer');
if(action.type === 'someAction') {
return { name: 'mike2' };
}
return state;
};
const store = createStore(myReducer, applyMiddleware(delayAction)};
const cancle = store.dispatch({type: 'someAction', meta: {delay: 3000 }});
cancel();
export default function App() {
return <div>실전 리액트</div>;
}
📍 로컬 스토리지에 저장해주는 코드
const saveToLocalStorage = store => next => action => {
if (action.meta?.localStorageKey) {
localStorage.setItem(action.meta?.loaclStorageKey, JSON.stringify(action));
}
return next(action);
}
const myReducer = (state = { name: 'mike' }, action) => {
console.log('myReducer');
switch(action.type) {
case 'someAction':
return {...state, name:'mike2'}
default:
return state;
}
return state;
}
const store = createStore(myReducer, applyMiddleware(saveToLocalStorage));
store.dispatch({
type: 'someAction',
title: 'asdf',
meta: { localStorageKey: 'myKey' }
});
: 액션이 발생했을 때 새로운 상태값을 만드는 함수
상태값 변경 → 액션 객체와 함께 dispatch 메소드 호출
function reducer(state = INITIAL_STATE, action) {
//...
switch (action.type) {
case REMOVE_ALL:
return {
...state,
todos: [],
};
case REMOVE:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.id),
};
default:
return state;
}
}
const INITIAL_STATE = { todos: [] }
⚠️ 반환되는 state 값은 불변객체로 관리 해야함
⇒ 이전 상태랑 다음 상태를 ===
로 단순 비교가 가능함
📌 immer패키지
: 전개 연산자 대신 불변객체로 관리해주는 라이브러리
import produce from 'immer';
const person = { name: 'mike', age: 22 };
const newPerson = produce(person, draft => {
draft.age = 32
})
//다른 파일내의 코드
function reducer(state = INITIAL_STATE, action) {
return produce(state, draft => {
switch (action.type) {
case SET_SELECTED_PEOPLE:
draft.selectedPeople = draft.peopleList.find(
item => item.id === action.id,
);
break;
case EDIT_PEOPLE_NAME:
const people = draft.peopleList.find(
item => item.id === action.id,
);
people.name = action.name;
break;
}
})
}
⚠️ 리듀서 코드 작성시 주의해야할 점
📍createReducer사용
import produce from 'immer';
function createReducer(initialState, handlerMap) {
return function(state = initialState, action) {
return produce(state, draft => {
const handler = handlerMap[action.type];
if (handler) {
handler(draft, action);
}
})
}
}
const reducer = createReducer(INITIAL_STATE, {
[ADD]: (state, action) => state.todos.push(action.todo),
[REMOVE_ALL]: state => (state.todos = []),
[REMOVE]: (state, action) => state.todos.filter(todo => todo.id !== action.id),
});
📍createStore사용
const store = createStore(reducer);
let prevState;
store.subscribe(() => {
const state = store.getState();
if (state === prevState) {
console.log('상태값 같음');
} else {
console.log('상태값 변경됨');
}
prevState = state;
});