프로젝트에서 rtk를 사용하기 위해 리덕스를 처음부터 공부해보면서 작성한 글입니다.....
Redux: 상태관리 툴
React는 상태관리가 없기 때문에 상태관리를 위한 선택지가 생김. → 이것이 Redux.
리덕스의 특징: 단방향이다.
페이스북이 양방향의 단점을 해결하기 위해 flux구조가 나옴.
state를 하나에 몰아넣고 Redux로 관리하는 것 - 컴포넌트 간의 state 사용을 안할 수 있게 됨.
컴포넌트 간의 state를 넘겨줄 때 힘들기 때문에 Redux로 관리했음.
그러면 그냥 하나의 컴포넌트 안에서만 사용하는 state은 얼마든지 사용 가능.
store에 들어있는 데이터를 state라 부름.
단방향을 유지하기 위해 action을 만듦.
action은 state을 어떻게 바꿀지에 대한 행동을 적어 놓은 것.
실행은 dispatch라 부름.
action을 미리 만들어놓고 dispatch를 이용해서 state를 바꿈.
action은 왠만하면 미리 만들어 놓아야하고 state의 불변성을 지켜주어야 타임머신 기능을 사용할 수 있음.
state 객체를 매번 새로 만들어야 함. → Reducer action이 실행되면 Reducer에서 새로운 객체를 만들어내서 state를 대체함.
즉 Reducer는 새로운 state를 만들어 주는 애..!
dispatch와 Reducer 사이에 있는 것이 미들웨어
dispatch(action) → 함수 따라서 기록이 다 남음.
history가 다 남기 때문에 디버깅이 매우 쉬움. 밑에서 위로 거슬러 올라가면서 에러를 찾을 수 있음.
//action
const changeCompA={
type: 'CHANGE_COMP_A',
data: 'b',
}
const changeCompA=(data)=>{
return{
type: 'CHANGE_COMP_A'
data,
}
}
Redux를 사용하면 화면은 알아서 바뀜.
store.subscribe()라는 이벤트 리스너가 있긴 하지만 얘는 react-redux 내부에 들어있음.
subscribe() 내부에 화면 바꿔주는 코드가 들어간다고 생각하면 됨.
내가 기존의 state를 어떻게 바꿀 것인가를 생각해서 action을 만들면 됨.
action이 있으면 그에 맞는 reducer도 있어야함.
action과 reducer를 짝이라고 생각하라.
store의 state는 불변성을 지킨다고 했음 → 즉 대체된다.
따라서
const initialState={
user: null,
posts: [],
}
const nextState={
...initialState,
posts: [action.data],
}
const nextState={
...initialState,
posts: [...initialStats.posts,action.data]
}
리덕스 스토어 state에서 이런 방식으로 배열에 추가됨을 알아둬라..
example.js
const { createStore } = require('redux');
const reducer = (prevState, action) => {
//액션은 객체이므로 action.type으로 씀!!!!!!
switch (action.type) {
case 'LOG_IN':
return {
...prevState,
user: action.data,
};
case 'LOG_OUT':
return {
...prevState,
user: null,
};
case 'ADD_POST':
return {
...prevState,
posts: [...prevState.posts, action.data],
};
default:
return prevState;
}
};
const initialState = {
user: null,
posts: [],
};
//store만들고 state초기화
const store = createStore(reducer, initialState);
console.log(store.getState());
//action은 객체 acton은 추상적으로 만드는 것이 좋음.
//action들은 미리 만들어 놓는 것
const login = (data) => {
return {
type: 'LOG_IN',
data,
};
};
const logout = () => {
return {
type: 'LOG_OUT',
};
};
const addPost = (data) => {
return {
type: 'ADD_POST',
data,
};
};
//이 밑에부분은 리액트에서 사용하는 코드.
store.dispatch(
login({
id: 1,
name: 'chaaerim',
admin: true,
})
);
console.log(store.getState());
store.dispatch(
addPost({
userId: 1,
id: 1,
content: 'hi',
})
);
console.log(store.getState());
store.dispatch(logout());
console.log(store.getState());
thunk가 비동기 제어에 부족한 면이 있다면 saga를 사용하면 됨.
간단한 프로젝트에서는 thunk가 더 편하다고 함.
리듀서는 함수. 따라서 리듀서는 나눠 쓸 수가 없음
리듀서를 나눠쓰기 위해서 리덕스에서는 combineReducer를 제공.
reducers/index.js
const { combineReducers } = require('redux');
const userReducer = (prevState, action) => {
//액션은 객체이므로 action.type으로 씀!!!!!!
switch (action.type) {
case 'LOG_IN':
return {
...prevState,
user: action.data,
};
case 'LOG_OUT':
return {
...prevState,
user: null,
};
default:
return prevState;
}
};
const postReducer = (prevState, action) => {
switch (action.type) {
case 'ADD_POST':
return {
...prevState,
posts: [...prevState.posts, action.data],
};
default:
return prevState;
}
};
// 모듈로 만들어주면 됨
module.exports = combineReducers({
user: userReducer,
posts: postReducer(),
});
reducers/post.js
const postReducer = (prevState, action) => {
switch (action.type) {
case 'ADD_POST':
return {
...prevState,
posts: [...prevState.posts, action.data],
};
default:
return prevState;
}
};
module.exports = postReducer;
reducers/user.js
const userReducer = (prevState, action) => {
//액션은 객체이므로 action.type으로 씀!!!!!!
switch (action.type) {
case 'LOG_IN':
return {
...prevState,
user: action.data,
};
case 'LOG_OUT':
return {
...prevState,
user: null,
};
default:
return prevState;
}
};
module.exports = userReducer;
const { combineReducers } = require('redux');
const userReducer=require('./user')
const postReducer=require('./post')
// 모듈로 만들어주면 됨
module.exports = combineReducers({
user: userReducer,
posts: postReducer(),
});
reducers/post.js
//리듀서를 쪼개면서 initialState도 같이 기본값을 넣어주어야함.
const initialState = [];
const postReducer = (prevState = initialState, action) => {
switch (action.type) {
case 'ADD_POST':
return [...prevState, action.data];
default:
return prevState;
}
};
module.exports = postReducer;
action은 객체 dispatch는 그냥 action을 디스패치. 이 사이에 비동기가 들어갈 틈이 없음.
디스패치와 리듀서 사이에서 동작하는 것이 리덕스 미들웨어. → redux-thunk, redux-saga를 많이 사용.
미들웨어가 없으면 기본적으로 dispatch(action)을 해줌.
dispatch(action)이 reducer로 전달되는 것이 기본 동작.
const firstMiddleWare = (store) => (next) => (action) => {
console.log(action);
dispatch(action);
};
redux-thunk는 비동기 처리를 할 때 액션을 함수로 넣어줌.
비동기 action creator를 미들웨어를 통해서 추가를 한 다음에 동기 한 번 부르고 비동기 작업 끝낼 때 동기 한번 부르고, 동기 액션들간의 실행 순서를 조작하는 정도.
로그인, 회원가입과 같이 서버를 한번 거쳐야하는 비동기 작업을 하기 위해 redux와 약속→ 앞으로 비동기일 때 action은 함수를 넣어 dispatch를 해주면 함수를 thunk보고 실행해달라고 하는 것.
const thunkMiddleWare = (stroe) => (dispatch) => (action) => {
if (typeof action === 'function') {
//비동기인 경우에는 액션을 함수로 넣어주겠다는 것.
return action(store.dispatch, store.getState);
}
return dispatch(action);
};
actions/user.js
const logIn = () => {
//async action creator
return (dispatch, getState) => {
// 비동기 액션
dispatch(logInRequest(data));
try {
setTimeout(() => {
dispatch(
loginSuccess({
userID: 1,
nickname: 'zerocho',
})
);
}, 2000);
} catch (e) {
dispatch(logInFailure(e));
}
};
};
const logInRequest = (data) => {
return {
type: 'LOG_IN_REQUEST',
data: {
id: 'zerocho',
password: 'babo',
},
};
};
const loginSuccess = (data) => {
return {
type: 'LOG_IN_SUCCESS',
data,
};
};
immer의 기본 형태
nextState=produce(prevState, (draft)=>{})
return produce(prevState, (draft) => {
switch (action.type) {
case 'LOG_IN_REQUEST':
draft.data = null;
draft.isLoggingIn = true;
case 'LOG_IN_SUCCCESS':
draft.data = null;
draft.isLoggingIn = false;
default:
return prevState;
}
});
};