背景
之前一直用dva进行状态管理,dva异步处理是基于redux-saga,Generators 语法写起来不方便,ts支持性不好。Redux Toolkit 现在是Redux官方推荐的工具包,集成了redux-thunk、immer等插件,拿来在项目中试用下,
"dependencies": {
"immer": "^9.0.7",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"reselect": "^4.1.5"
},
安装
yarn add @reduxjs/toolkit
基本使用
- 注册store
// src/store/index.ts
import { combineReducers, configureStore, createListenerMiddleware } from '@reduxjs/toolkit'
import pageSlice from './features/pageSlice'
export const store = configureStore({
reducer: {
user: userSlice,
},
// 启用Redux DevTools,默认true
devTools: false,
// 默认中间件 [thunk, immutableStateInvariant, serializableStateInvariant] 生产只包含thunk
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
})
})
// src/app.tsx
import { Provider } from 'react-redux'
import { store } from '@/store/index'
class App extends Component {
// ... ... 省略生命周期
// 在 App 类中的 render() 函数没有实际作用
// 请勿修改此函数
render() {
return <Provider store={store}>{this.props.children}</Provider>
}
}
- 注册一个用户状态切片
// slice/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface UserState {
count: number
entities: Array<any>
}
const initialState: UserState = {
count: 0,
entities: [],
}
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
// 导出为 actions
increment(state) {
state.count += 1
},
incrementByStep(state, action: PayloadAction<number>) {
state.count += action.payload
},
},
})
export const { increment, incrementByStep } = userSlice.actions
export default userSlice.reducer // 导出 reducer
这里的reducer和之前使用基本一致,就是更新状态值可以直接通过 state.count = 1 这种方式赋值,当然也可以用之前解构的方式
return { ...state, ...action.payload}
- 在页面引入
在这之前为了更好的类型支持,参考官方文档重新定义disptach、selector
// store/index.ts
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// store/hook.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
import React from 'react'
import { View } from '@tarojs/components'
import { fetchUserById, increment, incrementByStep } from '@/store/features/useSlice'
import { useAppDispatch, useAppSelector } from '@/store/hook'
import './index.scss'
const Index = () => {
const count = useAppSelector((state) => state.user.count)
const entities = useAppSelector((state) => state.user.entities)
const source = useAppSelector((state) => state.page.source)
const dispatch = useAppDispatch()
// 相当于dispatch({ type: 'counter/increment' })
const onInc = () => dispatch(increment())
const onInc2 = () => dispatch(incrementByStep(2))
console.log('渲染了Index', count)
return (
<View className='wrapper'>
<View className='wrapper' onClick={() => onInc()}>
{count}
</View>
<View className='wrapper' onClick={() => onInc2()}>
{count}
</View>
</View>
)
}
export default Index
异步请求
redux-toolkit提供了creatAsyncThunk方法处理状态的异步处理
这里也处理下state类型问题
// store/index.ts
export type TypedCreateAsyncThunk<ThunkApiConfig> = <Returned, ThunkArg = void>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>,
options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>
) => AsyncThunk<Returned, ThunkArg, ThunkApiConfig>
// store/hook.ts
/**
* 注:如果返回函数的参数第一个是可选参数,类型使用void
*/
export const createAppAsyncThunk: TypedCreateAsyncThunk<{ state: RootState }> = createAsyncThunk
createAsyncThunk方法返回三种异步状态pending、fulfilled、rejected
redux-toolkit提供了extraReducers去处理这些异步回调
//
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
// ...
},
// 在extraReducer中可以定义reducer来响应Slice外部的action
// 比如这里的fetchUserById就是外部定义的Thunk Action
extraReducers: (builder) => {
// 异步 actions 中触发与其他 slice 中数据的关联改变
// 包含本身定义的用户逻辑,pending, fulfilled, rejected 4种action
builder
.addCase(fetchUserById.pending, (state) => {
state.loading = 'loading'
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.entities.push(action.payload)
})
.addCase(fetchUserById.rejected, (state, err) => {
console.log(state, err)
})
},
})
当前也可以不使用extraReducers,通常我们期望的是,在异步方法里可以返回Promise,异步方法间可以相互调用。
官方也提供了unwrap 的方式,用来提取动作payload 返回的数据
import { unwrapResult } from '@reduxjs/toolkit'
// ...
// 第一种使用方式
dispatch(fetchUserById(userId)).unwrap()
// 第二种使用方式
dispatch(fetchUserById(userId))
.then(unwrapResult)
.then((originalPromiseResult) => {
// handle result here
})
看了源码,unwrap(){ return promise.then<any>(unwrapResult) } unwrap还是调用的工具的unwrapResult方法
这里我们使用下
// slice/userSlice.ts
// ...
export const checkCount = createAsyncThunk('user/checkCount', async(_, thunkApi) => {
const count = thunkApi.getState().user.count
return count > 0
})
export const fetchUserById = createAsyncThunk('user/fetchUserById', async (userId: number, thunkApi) => {
const hasCount = await dispatch(checkCount()).unwrap()
console.log('hasCount', hasCount)
// hasCount false
const response: any = await fetchById(userId)
return response.data
})
日志中间件
除了使用ReduxDevTool工具,有时需要日志定位问题。可以自定义中间件或者redux-logger
const logger = (store) => (next) => (action) => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
// yarn add redux-logger
// import logger from 'redux-logger'
export const store = configureStore({
reducer: { user: userSlice, },
devTools: false,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
})
本地化处理
以使用 redux-presist 对 store 的数据进行持久化
// store/index.ts
// 微信小程序持久化api
const storage = {
async getItem(key) {
const res = await Taro.getStorage({ key })
return res.data
},
setItem(key, data) {
return Taro.setStorage({ key, data })
},
removeItem(key) {
return Taro.removeStorage({ key })
},
clear: Taro.clearStorage,
}
const userPersistConfig = {
key: 'user', // 别名
storage,
whitelist: ['count'], // 白名单指定需要缓存的键
}
const rootReducer = {
user: persistReducer(userPersistConfig, userSlice),
}
export const store = configureStore({
reducer: rootReducer,
devTools: false,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
})
export const persistor = persistStore(store)
// ...
// app.tsx
import { store, persistor } from '@/store/index'
import { PersistGate } from 'redux-persist/lib/integration/react'
class App extends Component {
// ...
render() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{this.props.children}
</PersistGate>
</Provider>
)
}
}
export default App
查看调试工具查看下