Redux Toolkit 是官方强烈推荐,开箱即用的一个高效的Redux 开发工具集, 本文将介绍如何在nextjs项目中集成redux来共享全局状态
同步
1.创建一个slice(可以理解为redux的一个数据模块)
- 根目录下创建
store文件夹 - 在
store文件夹中创建一个counter文件夹, 这个couter作为我们一个store的模块 - 在counter中创建
counterSlice.ts文件, 因为在操作过程中会创建其他的文件或文件夹, 所以我把文件结构放在本文最后, 有需要可以拉到最下面查看;counterSlice.ts文件的位置应该是在/store/counter/counterSlice.ts - 接下来我们在
counterSlice.ts文件中创建一个slice
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface CounterState {
value: number;
status: "idle" | "loading" | "failed";
}
const initialState: CounterState = {
value: 0,
status: "idle",
};
// 重点看这一部分
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
可以看到, 我们的slice主要由3部分组成
- name: 模块名
- initialState: 初始状态
- reducers: reducers是一个对象, 对象的key是字符串(方法名), value是一个方法(可以理解为vuex中的一个action), 这个方法接受一个state参数, 改变这个参数的值会更新我们所有用到这个state的地方的view
2.将slice上的reducer暴露给出来, 用于在store总模块中集成
export default counterSlice.reducer;
3.创建store
创建store其实就是就是把所有的reducer像是拼积木一样组合到一起的过程
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {
counter: counterReducer // 当然, 这个key值你是可以自定义的, 这影响到你调用时的写法会不一样, 但是为了避免歧义, 一般我们会写成模块名
... // 如果你有多个模块的话, 都是写在这里, 比如这样
auth: authReducer
},
});
4.为store添加类型支持
export type AppState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
所以我们/store/index.ts文件应该是这样的:
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counter/counterSlice";
const store = configureStore({
reducer: { counter: counterReducer },
});
export type AppState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
到这里, 我们一个最小化的store就算继承完了,
5.在项目中去使用state
const count = useSelector<AppState, number>((state) => {
state.counter.value;
});
然后我们就可以在项目中{count}去是使用这个state啦
6.在项目中改变state
改变state我们需要用到useDispatch这个hook
const dispatch = useDispatch();
const handleCountPlus = () => dispatch(incrementByAmount(10));
7.为useSelector增强类型
不知道大家有没有发现, 我们在使用useSelector的时候, 每次都要传入2个泛型, 即一个AppState, 一个number, 这样编辑器才知道我们返回的count是什么类型, 非常的麻烦, 为此, 我看到react-redux为我们提供了一个泛型工具可以完美的解决这个问题, 我们在store文件夹下新建一个hooks.ts文件
import { TypedUseSelectorHook, useSelector } from "react-redux";
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;
然后我们就可以这样使用
const count = useAppSelector((state) => state.counter.value);
当然, 如果涉及到一些复杂的运算, 我们也可以吧(state) => state.counter.value写成一个函数放在slice中(类比于vue的getter)
export const selectCount = (state: AppState) => state.counter.value;
然后我们就可以这样使用它
const count = useAppSelector(selectCount);
到这里, 我们redux的的集成的基础部分就算做完啦, 但是, 目前我们还不具备异步更新state的能力, 我们需要用到redux的一个中间件, reudx-thunk, 当然还有一个基于generetor函数写的redux-saga, reduc-thunk相对来说更加容易上手, 下面我们来用rendux-thunk来处理异步更新state
异步
1.创建一个asyncThunk(就是一个异步函数)
const fetchNewCount = (amount: number) =>
request.post("/count", {
data: { amount },
});
export const incrementAsync = createAsyncThunk("counter/fetchCount", async (amount: number) => {
const response = await fetchNewCount(amount);Ï
return response.data;
});
2.将创建的syncThunk添加到我们的slice(store 模块)中
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
// 重点看这里
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state) => {
state.status = "loading";
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = "idle";
state.value += action.payload;
})
.addCase(incrementAsync.rejected, (state, err) => {
state.status = "failed";
});
},
});
这就是redux处理异步的办法啦
补充知识
1.手写一个thunk
现在我们手写一个thunk, 这个thunk可以包含同步或者异步的逻辑
// 这个类型是从官方copy的
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, AppState, unknown, Action<string>>;
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState());
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount));
}
};
上面的手写thunk还是比较容易理解的
- 其实就是一个函数接受一个任意的参数
- 然后返回一个AppThunk类型
- AppThunk也是一个函数, 这个函数接收, dispatch用于调用同步方法更新state, getState是一个函数, 可以用于获取最新的state
- 通过selectCount可以获取state上最新的value值
- 也和普通的reducer一样调用即可
dispatch(incrementIfOdd(incrementValue))
2.为redux天假数据自动持久化
redux persiter为我们提供了自动管理全局数据与持久化数据同步的能力, 除此之外, 我们可以对持久化数据的位置进行配置, 还可以配置需要持久化的模块的白名单, 为我们开发带来非常大的便捷性, 当然, 如果你的应用程序足够小, 这不是一个必选项, 当你感觉需要频繁的去操作localStorage的时候, 也许你应该考虑redux-persist了, 现在, 让我们开始来学习它吧
- 组合reduces
import { combineReducers } from "@reduxjs/toolkit";
const rootReducer = combineReducers({
counter: counterReducer,
});
- 对reducer进行persist
import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, PersistConfig } from "redux-persist";
const persistConfig: PersistConfig<any> = {
key: "root",
version: 1,
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
当然, 如果有需要, 我们也可以对我们的任意一个reducer进行persist配置, 比如counterReducer, 但是目前来说我还没有发现这个功能的使用场景在哪里, 比如
const persistCounterConfig: PersistConfig<any> = {
key: "counter",
version: 1,
storage,
whiteList: [...]
};
const persistedCounterReducer = persistReducer(persistCounterConfig, counterReducer);
3.创建store和persistStore, 并导出
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
const persister = persistStore(store);
export default {
store,
persister,
};
4.最后, 让我们在root component中去使用它
<Provider store={store.store}>
<PersistGate loading={null} persistor={store.persister}>
<Component {...pageProps} />
</PersistGate>
</Provider>