리덕스 팀에서 공식적으로 만든 라이브러리
리덕스 내부에 thunk, immer가 내장되어있음. 리덕스의 총집합이라고 생각해도 됨.
Reducers and Actions에서 createSlice, createAsyncThunk만 사용해도 충분함.
스토어 만들고 리액트 코드에서 디스패치해줌.
그러면 디스패치해준 데이터가 이미 생성해 놓은 createAsyncThunk에 들어옴. 그럼 여기서 리듀서이자 action인 slice를 부르는거지. immer를 사용하지 않아도 immer가 포함되어있으므로 편함.
동기인 작업은 actions(slices 내부에 적은)에서 불러오면 되고 비동기인 작업은 thunk로 생성하고 slices에서 불러오면 됨.
store.js
const { configureSotre } = require('@reduxjs/toolkit');
const reducer = require('./reducers');
const { addPost } = require('./actions/post');
const { logIn, logOut } = require('./actions/user');
const firstMiddleware = (store) => (next) => (action) => {
console.log('로깅', action);
next(action);
};
const store = configureSotre({
reducer,
middleware: [firstMiddleware, ...getDefaultMiddleware()],
devTools: true,
});
module.exports = store;
const store = configureSotre({
reducer,
preloadState:
});
slice는 단순히 reducer뿐만 아니라 다양한 개념을 합친 것. reducer, initialState, action도 합친 것.
로그인 action은 user만 바꾸는 경우가 많음.
대부분의 action은 하나의 reducer에 종속되므로 하나로 합쳐버리자..! 하고 나온 것이 slice.
slice의 reducers는 동기적이라고 보면 되고, extraReducers는 비동기적이라 보면 됨. reducer에는 내부적인 action들을 넣고 extraReducers에는 외부에서 사용하는 action들을 넣음.
reducers/index.js
const { combineReducers } = require('redux');
const userSlice = require('./user');
const postSlice = require('./post');
module.exports = combineReducers({
user: userSlice.reducer,
posts: postSlice.reducer,
});
reducers/user.js
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
logOut(state, action) {
state.data = null;
},
},
});
App.jsx
const onLogout = useCallback(() => {
dispatch(userSlice.actions.logOut());
}, []);
userSlice로부터 actions 꺼내서 logOut()을 해주면 툴킷이 알아서 logOut action을 만들어줌.
동기액션들은 거의 slice의 reducer내부에 들어있으므로 rtk를 사용하면 actions에는 비동기 액션의 공간이 될 것.
actions/user.js
const logIn = createAsyncThunk('user/logIn', async(data, thunkAPI)=>{
const state=thunkAPI.getState()
});
exports.logIn = createAsyncThunk('user/logIn', async (data, thunkAPI) => {
// throw new Error('비밀번호가 틀렸습니다.');
return await delay(500,{
userId: 1,
nickname: 'chaaerim'
});
});
reducers/user.js
const userSlice = createSlice({
name: 'user',
initialState,
//동기 액션
reducers: {
logOut(state, action) {
state.data = null;
},
},
extraReducer: {
[logIn.pending](state, action) { //user/logIn/pending
state.isLoggingIn = true;
},
[logIn.fulfilled](state, action) {
//action에 들어있는 데이터는 이제 payload라고 부름.
state.date = action.payload;
state.isLoggingIn = false;
},
[logIn.rejected](state, action) {
state.data = null;
state.isLoggingIn = false;
},
});
reducers/post.js
const { createSlice } = require('@reduxjs/toolkit');
const { addPost } = require('../actions/post');
const initialState = {
list: [],
};
const postSlice = createSlice({
name: 'post',
initialState,
reducers: {},
extraReducers: (builder) =>
builder
.addCase(addPost.pending, (state, action) => {})
.addCase(addPost.fulfilled, (state, action) => {
state.list.push(action.payload);
})
.addCase(addPost.rejected, (state, action) => {}),
});
module.exports = postSlice;
builder의 addMatcher
.addMatcher(
(action) => {
return action.type.include('/pending');
},
(state, action) => {
state.isLoading = true;
}
),
하나의 컴포넌트 내부에서 데이터를 사용할 때 언제 리덕스를 쓰고 언제 쓰면 안될까..?
인풋에서는 리덕스를 안쓰는 것이 좋음.
인풋창에 리덕스를 사용하게 되는 경우 글자 하나를 칠 때마다 action이 발생하게 됨. 마지막에 받은 결과에만 리덕스를 적용하는 것이 좋음.
App.js
const email=useSelector((state)=>state.user.email);
const password=useSelector((state)>state.user.password)
const onSubmit=useCallback((e)=>{
e.preventDefault();
dispatchEvent(userSlice.actions.setLoginForm({
email, password
}))
}, [dispatch, email, password])
axios는 서버에 요청을 보내는 라이브러리.
→ 그러나 axios만 사용해도 리덕스와 비슷하게 구현하는 것이 좋음.
axios 예제 코드
const onClick = useCallback(async () => {
setLoading(true);
setDone(false);
setError(false);
try {
const response = await axios.post('/login');
setDone(done);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, []);