npx create-react-app my-app --template redux
(state, action) => nextState
순수함수: 같은 인수에 대해 항상 같은 값을 반환하고, 부수 효과를 발생시키지 않는 함수.
액션 -> 미들웨어 -> 리듀서 -> 스토어 -> 뷰(리액트) -> 액션
type 속성값을 가진 자바스크립트 객체. dispatch 메서드에 넣어 호출하면 리덕스는 상태값을 변경한다.
export const ADD = 'product/ADD';
export const REMOVE = 'product/REMOVE';
export const REMOVE_ALL = 'product/REMOVE_ALL';
export const addProduct = ({ id }) = { type: ADD, id };
export const removeProduct = ({ id }) = { type: REMOVE, id };
export const removeAllProduct = () = { type: REMOVE_ALL };
type 속성값은 리듀서에서 액션 객체를 구분할 때도 사용되기 때문에 상수 변수로 만드는게 좋다.
리듀서가 액션을 처리하기 전에 실행되는 함수. ex) 상탯값 변경 시 로그 출력, 예외 처리
기본구조: const Middleware = store => next => action => next(action)
// 로그를 출력해 주는 미들웨어 (디버깅)
const pringLog = store => next => action => {
console.log(`prev state = ${store.getState()}`);
const result = next(action);
console.log(`next state = ${store.getState()}`);
return result;
}
// 에러 정보를 전송해 주는 미들웨어
const reportCrash = store => next => action => {
try {
return next(action);
} catch (err) {
console.log(err)
}
};
// 로컬 스토리지에 값을 저장하는 미들웨어
const saveToLoaclStorage = store => next => action => {
if (action.type === "SET_NAME") {
localStorage.setItem('name', action.name);
}
return next(action);
};
액션이 발생했을 때 새로운 상탯값을 만드는 함수.
const INITIAL_STATE = { cartList: [] }
}
// 매개변수의 기본값 설정
const reducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case "ADD":
return {
...state,
cartList: [...state.cartList, action.id],
};
case "REMOVE":
return {
...state,
cartList: state.cartList.filter(list => list.id !== action.id),
};
case "REMOVE_ALL":
return {
...state,
cartList: [],
};
defult:
return state;
}
}
- 스토어를 생성할 때 상탯값이 없는 상태로 리듀서를 호출하므로 매개변수의 기본값을 사용해서 초기 상탯값을 정의한다.
- 상탯값은 불변 객체로 관리해야 하므로 수정할 때마다 새로운 객체를 생성한다.
전개 연산자를 이용해서 깊은 곳의 값을 수정할 때 코드의 가독성을 높이기 위해 사용하는 패키지. 불변 객체를 관리한다.
import produce from 'immer';
const cat = { name: 'tomy', age: 10 };
// draft 객체를 수정하면 produce 함수가 새로운 객체를 반환한다.
const newCat = produce(cat, draft => {
draft.age = 11;
});
const INITIAL_STATE = { cartList: [] }
// 매개변수의 기본값 설정
const reducer = (state = INITIAL_STATE, action) => {
return produce(state, draft => {
switch (action.type) {
case "ADD":
draft.cartList.push(action.id);
break;
case "REMOVE":
draft.cartList.filter(list => list.id !== action.id);
break;
case "REMOVE_ALL":
draft.cartList = [],
break;
defult:
break;
}
}
}
const reducer = createReducer(INITIAL_STATE, {
[ADD]: (state, action) => state.cartList.push(action.id),
[REMOVE]: (state, action) => (state.cartList = []),
[REMOVE_ALL]: (state, action) => state.cartList.filter(list => list.id !== action.id),
리덕스의 상탯값을 가지는 객체. 액션의 발생은 스토어의 dispatch 메서드로 시작된다.
// createReducer.js
import produce from "immer";
export default function createReducer(initialState, handelrMap) {
return function(state = initialState, action) {
return produce(state, draft => {
const handler = handelrMap[action.type];
if (handler) {
handler(draft, action);
}
})
}
}
// cartList/state.js
import createReducer from '../common/createReducer";
export const ADD = 'product/ADD';
export const REMOVE = 'product/REMOVE';
export const REMOVE_ALL = 'product/REMOVE_ALL';
export const addProduct = ({ id }) = { type: ADD, id };
export const removeProduct = ({ id }) = { type: REMOVE, id };
export const removeAllProduct = () = { type: REMOVE_ALL };
const INITIAL_STATE = { cartList: [] };
const reducer = createReducer(INITIAL_STATE, {
[ADD]: (state, action) => state.cartList.push(action.id),
[REMOVE]: (state, action) => (state.cartList = []),
[REMOVE_ALL]: (state, action) => state.cartList.filter(list => list.id !== action.id)
});
export default reducer;
여러 개의 리듀서를 하나로 합치는 함수.
import { createStore, combineReducers } from 'redux';
import cartListReducer, {addProduct, removeProduct, removeAllProduct} from './cartList/state';
import likeListReducer, {addLikeList, removeLikeList, removeAllLikeList} from './likeList/state';
const reducer = combineReducers({
cartList: cartListReducer,
likeList: likeListReducer
});
const store = createStore(reducer);
store.subscribe(() => {
const state = store.getState();
cosnole.log(state);
});
store.dispatch(addProduct({ id: 1}));
store.dispatch(removeAllProduct());
/*
state = {
{ cartList: [] },
{ likeList: [] },
}
*/
최상위 컴포넌트로 정의하여 리덕스의 상탯값이 변경되면 하위 컴포넌트에서 자동으로 컴포넌트 함수가 호출되도록 한다.
contextAPI를 사용해 하위 컴포넌트로 상탯값을 전달한다.
리덕스의 상탯값이 변경되면 이전 반환값과 새로운 반환값을 비교한다.
두 번째 매개변수는 컴포넌트 렌더링 여부를 판단하는 역할을 한다.
const [cartItem, totalPrice] = useSelector(state => [state?.cart, state?.cart.totalPrice], shallowEqual);
액션을 발생시키기 위해 필요한 dispatch 함수를 반환한다.