Appearance
reduxjs/toolkit https://github.com/reduxjs/redux-toolkit
https://www.npmtrends.com/@rematch/core-vs-@reduxjs/toolkit-vs-dva
其中 dva、rematch、reduxjs/toolkit 是同一类的,都是基于 redux 封装的库,发布时间由早到晚。reduxjs/toolkit 发布最晚,有借鉴前两个的一些思路,ts 类型支持是最好的,这三个就选 reduxjs/toolkit,以下简称 RTK
yarn add @reduxjs/toolkit react-redux @types/react-redux
通常对于一个大型应用,需要将全局状态进行拆分模块,在 RTK 里叫 "切片" slice,通过 combineReducers 将多个 slice 的 reducer 进行合并
src/index.tsx
tsx
import { Provider } from 'react-redux'
import store from 'src/store/index.ts'
import App from './App'
;<Provider store={store}>
<App />
</Provider>src / store / index.ts
tsx
import { configureStore, combineReducers, Action } from '@reduxjs/toolkit'
import { useDispatch } from 'react-redux'
import { ThunkAction } from 'redux-thunk'
import dolphins from './dolphins'
import sharks from './sharks'
// 由多个 reducer 合并的根 reducer
const rootReducer = combineReducers({
dolphins,
sharks,
})
const store = configureStore({
reducer: rootReducer,
})
// 获取全局 state 的类型,在 FC 组件里 useSelector 时会用到,在 class 组件里 mapState 时会用到
export type RootState = ReturnType<typeof store.getState>
// 获取 dispatch 类型,useDispatch 会用到
export type AppDispatch = typeof store.dispatch
// 使用异步 action 会用到,RTK 默认集成了 redux-thunk
export type RootThunk = ThunkAction<void, RootState, null, Action<string>>
// 导出封装 useAppDispatch (有类型提示)
export const useAppDispatch = () => useDispatch<AppDispatch>()
export default store使用 createSlice 创建 切片
src/store/sharks.ts
tsx
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { delay } from 'src/utils'
// 异步 action: 使用 createAsyncThunk 创建异步 actionCreater,返回值有三个属性,对应异步的三种状态:{ pending, fulfilled, rejected }
export const incrementAsync = createAsyncThunk('async/+num', async (num: number) => {
await delay(1000)
return num
})
// 使用 createSlice 创建切片(可以看作是对全局 state 的拆分)
const sharks = createSlice({
name: 'sharks',
initialState: {
number: 0,
},
// 根据 reducers 的 key 自动生成 action
reducers: {
// 都是同步操作,ts 里需指定 payload 的类型
increment(state, action: PayloadAction<number>) {
state.number = state.number + action.payload
},
},
// 用来订阅独立的/外部的 action,有两种写法
extraReducers: {
// 对象形式的 extraReducers 的 key 是一个 string,无法感知 actionCreater 里 action 的类型,需要再次手动指定
// 订阅异步请求的成功状态(fulfilled)
[incrementAsync.fulfilled.type]: (state, action: PayloadAction<number>) => {
state.number = state.number + action.payload
},
},
})
export const { increment } = sharks.actions
export default sharks.reducersrc/store/dolphins.ts
tsx
import { createSlice, PayloadAction, current } from '@reduxjs/toolkit'
import { delay } from 'src/utils'
import * as sharks from './sharks'
import { RootThunk } from '.'
const dolphins = createSlice({
name: 'dolphins',
initialState: {
number: 0,
},
reducers: {
increment(state, action: PayloadAction<number>) {
// reducer 里默认引入了 immer,这里的 state 实际是一个副本,current 是重 immer 里直接导出的方法,用来拿到对象的值而不是 Proxy
console.log(`TTT: state`, current(state))
state.number = state.number + action.payload
},
},
// 第二种写法,在 ts 里能自动推导出 action 的类型
extraReducers: (builder) => {
// 添加 reducer,订阅独立的/外部的 actions
builder.addCase(sharks.increment, (state, action) => {
state.number = state.number + action.payload
})
// builder 里有三种方法 addCase addMatcher addDefault
// addMatcher 可用于匹配多个 action,来进行一些统一处理
},
})
export const { increment } = dolphins.actions
// 另一种异步 action 的写法,与 createAsyncThunk 不同的是:这里不再是单纯的用来获取异步数据,里面还需要手动 dispatch 同步 actions,是一种命令式的思维,而不是声明式的。不建议使用这种会导致程序难以阅读
export function incrementAsync(num: number): RootThunk {
return async (dispatch, getState) => {
await delay(1000)
// 可以拿到全局 state 任用
const rootState = getState()
console.log(`TTT: functionincrementAsync -> rootState`, rootState)
dispatch(increment(num))
// 触发其他切片的 actions
dispatch(sharks.increment(num))
}
}
export default dolphins.reducer在 FC 组件中使用
src/App.tsx
tsx
import React from 'react'
import { useSelector } from 'react-redux'
import { RootState, useAppDispatch } from 'src/store'
import { incrementAsync } from 'src/store/dolphins'
import Counter from 'src/pages/Counter'
const ReApp = () => {
// 全局 state 任取,并且有类型提示
const storeState = useSelector((store: RootState) => store)
// dispatch 任何 actions,并且有类型提示(参数里可以进行选择 actionCreater)
const dispatch = useAppDispatch()
return (
<div>
<span> sharks: {storeState.sharks.number}</span>
<span> dolphins: {storeState.dolphins.number}</span>
<button onClick={() => dispatch(incrementAsync(1))}>(async) dolphins、sharks +1</button>
<Counter />
</div>
)
}
export default ReApp在 class 组件中使用
src/page/Counter
tsx
import React from 'react'
import { connect, ConnectedProps } from 'react-redux'
import { RootState } from 'src/store'
import { increment, incrementAsync } from 'src/store/sharks'
const mapState = (state: RootState) => ({
sharks: state.sharks,
})
const mapDispatch = {
increment,
incrementAsync,
}
const connector = connect(mapState, mapDispatch)
// 使用 ConnectedProps 推导注入的 props 类型
interface Props extends ConnectedProps<typeof connector> {}
class ReCounter extends React.Component<Props> {
render() {
const { sharks, increment, incrementAsync } = this.props
return (
<div>
<span>sharks:{sharks.number}</span>
<button onClick={() => increment(2)}>sharks +2</button>
<button onClick={() => incrementAsync(4)}>(async) dolphins、sharks +4</button>
</div>
)
}
}
export default connector(ReCounter)