Skip to content

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.reducer

src/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)