중앙저장소(store)에는 데이터들이있다. 이 데이터를 수정 추가 등의 행동을 하고싶으면,
바꾸고싶은것을 쓴 주문서(action)를 만들어서 dispatch해주면 바꿀수있는데, 그냥 dispatch를 해주는게아니라 reducer의 양식에 맞춰 바꾼다.
즉 store의 데이터를 바꾸고싶으면, action을 만들고, dispacth를 해줄때 reducer의 양식에 맞춰 dispatch해준다.
그럼 데이터를 바꿀때마다 action과 action에 따른 reducer를 만들어줘야하냐?
그렇다.
코드량이 많아지지만 대신 좋은점은 action 하나하나 다 reducer에 기록이 되기 때문에, 지금까지 데이터를 어떻게 바꿔왔는지 추적되기때문에 테스트나 버그잡을때 편하다.
리덕스는 불변성이 있다. 불변성을 지켜야 모든 action이 기록되어 히스토리를 유지할수있다.
객체는 새로만들면 항상 다 다르다. {} === {} // false
리듀서에서 ...state로 앞거는 그대로 복사하고 name:action.data로 name부분만 바꿔서 저장한다.
그럼 맨 오른쪽에 보이는것처럼 객체는 새로 만들면 다 다르기때문에, 이전데이터, 이번데이터 이렇게 따로 저장된다.
다 다르게 저장하는 이유는 앞에서 말했던것처럼 전의 action을 추적해 테스트, 버그 등에 이용하기 떄문이다.
그럼 왜 통째로 저장안하고 ...state이렇게 앞에거를 복사할까?
// 이렇게저장안하고 통째로 저장해도 되지않나?
const prev={name: 'zerocho'} =>? const prev={
name: 'zerocho',
age:27,
password:'babo'
}
const next={name: 'boogicho'} =>? const next={
name: 'boogicho',
age:27,
password:'babo'
}
코드가 길어지는것도 있지만, 메모리를 아끼기위해서다.
...state를 하면 안바뀌는것들은 참조를 해서 유지를하지만, action할때마다 새로운 객체를 계속 생성하면 메모리를 많이 잡아먹는다.(action할때마다 age:27,password:'babo'가 계속생성됨)
리덕스 미들웨어는 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행한다.
1. 로깅 및 디버깅
미들웨어를 사용하면 로깅 및 디버깅을 용이하게 할 수 있.
Redux 미들웨어를 사용하면 액션과 상태를 가로채서 로깅하거나 디버깅하기 쉽게 할 수 있어서, 액션과 상태를 로깅하면 앱의 문제점을 찾아내기가 쉬워집니다.
2. 액션을 가로채어 추가 작업
미들웨어를 사용하면 dispatch에서 보낸 액션이 reducer로 가기 전에 가로채어 추가 작업을 할 수 있다.
(예를 들어, 리듀서를 처리하기전에 개발용에는 devtools를 연결하고 배포용에는 devtools를 연결하지 않게 해 조금이라도 더 최적화를 할수있다.)
또, 사전에 액션을 가로채지않고 리듀서 안에서 가로채서 작업을 해주는경우도있다.
(nodebird사용시 그랬음 코드참고)
[액션] => [미들웨어] => [리듀서] => [스토어]
자, 이제 써보자.
로그인,로그아웃을 반복해서 실행하다보면 state가 바뀌었으니 react-devtools에서 action이 보여야하는데 안보인다. (히스토리가 안보임)
히스토리를 보기위해 미들웨어를 붙여주자.
npm i redux-devtools-extension
을 깐다. const configureStore = (data) => {
const middlewares = [];
const enhancer =
process.env.NODE_ENV === "production"
? compose(applyMiddleware(...middlewares)) // 배포용 => devtools 연결 x
: composeWithDevTools(applyMiddleware(...middlewares)); // 개발용 => devtools 연결 o
const store = createStore(reducer, enhancer);
이제 action이 잘뜬다.
그럼 데이터를 바꿀때마다 action과 action에 따른 reducer를 만들어줘야하냐? 그렇다.
지금과 같은 방법으로 하면 switch문의 case와 action을 계속 생성해줘야한다.
복잡하니깐 쪼개보자.
import { HYDRATE } from "next-redux-wrapper";
const initialState = {
user: {
isLoggedIn: false,
user: null,
signUpData: {},
loginData: {},
},
post: {
mainPosts: [],
},
};
export const loginAction = (data) => {
return {
type: "LOG_IN",
data,
};
};
export const logoutAction = (data) => {
return {
type: "LOG_OUT",
data,
};
};
// (이전상태, 액션) => 다음상태
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case HYDRATE:
return { ...state, ...action.payload };
case "LOG_IN":
return {
...state,
user: {
...state.user,
isLoggedIn: true,
user: action.data,
},
};
case "LOG_OUT":
return {
...state,
user: {
...state.user,
isLoggedIn: false,
user: null,
},
};
default:
return state;
}
};
export default rootReducer;
const initialState = {
};
const reducer = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default reducer;
user.js
export const initialState = {
isLoggedIn: false,
user: null,
signUpData: {},
loginData: {},
};
export const loginAction = (data) => {
return {
type: "LOG_IN",
data,
};
};
export const logoutAction = (data) => {
return {
type: "LOG_OUT",
data,
};
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "LOG_IN":
return {
...state,
isLoggedIn: true,
user: action.data,
};
case "LOG_OUT":
return {
...state,
isLoggedIn: false,
user: null,
};
default:
return state;
}
};
export default reducer;
post.js
export const initialState = { mainPosts: [] };
const reducer = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default reducer;
import { HYDRATE } from "next-redux-wrapper";
import user from "./user";
import post from "./post";
import { combineReducers } from "redux";
// (이전상태, 액션) => 다음상태
const rootReducer = combineReducers({
index: (state = {}, action) => {
switch (action.type) {
case HYDRATE:
console.log("hydrate", action);
return { ...state, ...action.payload };
default:
return state;
}
},
user,
post,
});
export default rootReducer;
리듀서를 쓰려면 useSelector로 받아서쓰고 dispatch로 액션을사용한다.