从Dva到Redux ToolKit,现代Redux的演进

前言

2015年发布的Redux至今仍是React生态中最常用的状态管理库,2022年4月19日发布的Redux [v4.2.0](https://github.com/reduxjs/redux/releases/tag/v4.2.0)中正式将createStore方法标记为“已弃用”(@deprecated),并推荐用户使用Redux-Toolkit(下面简称为RTK)的configureStore方法。

此次发布只是增加了额外的@deprecate提示,并不影响任何存量的代码,也不会有运行时的错误提示

可能在此之前很多Redux用户并没有了解过RTK(2021年Redux的占有率在45 - 50%,而同时用到RTK的仅有4.5%),而是用了其他基于Redux的封装来简化Redux的配置和使用,比如曾经一度流行的Redux封装Dva,现在周下载量仍有2w6+,还有相当多的存量应用。不过Dva上一次的正式版发布已经是三年多前了,事实上处于不维护状态。 image.png 这期间2019年React 16.8推出了Hook,整个React生态尤其是状态管理库也随之开始转向,大量库都设计了易用性更高的Hook API。因为啰嗦的模板代码写法被饱受诟病的Redux在React-Redux v7.1也推出了Hook API useSelector/useDispatch,随后正式推出了社区期盼已久的开箱即用Redux解决方案:Redux ToolKit(前身是Redux-Starter-Kit)。

为什么现在Redux要强推RTK和其代表的“现代Redux”?借此次发布的契机,来聊一聊现代Redux的一些演进 image.png

经典Redux

Redux是什么?

Redux是Flux架构的一种扩展或者实现,具有同样的单向数据流,最初是2015年Dan Abramov想要在Flux应用上新增热替换和时间旅行功能而开发的,为了让状态修改“可预测”。 image.png 下面来看一看Redux核心createStore的一个最小实现:

function createStore(reducer) {
  let state;
  let listeners = [];
  let currentReducer = reducer;

  function getState() {
    return state;
  }

  function subscribe(listener) {
    listeners.push(listener);

    return function unsubscribe() {
      const idx = listeners.indexOf(listener);
      listeners.splice(idx, 1);
    };
  }

  function dispatch(action) {
    state = currentReducer(state, action);
    listeners.forEach((listener) => listener());
  }

  function replaceReducer(nextReducer) {
    currentReducer = nextReducer;
    dispatch({ type: "replace" });
    return store;
  }

  // 初始化各个reducer的状态树
  dispatch({ type: "init" });

  const store = { getState, subscribe, dispatch, replaceReducer };
  return store;
}

  • 除去进阶的enhancers等功能,Redux核心就是一个简单的发布订阅模式,当action被dispatch时通知各个listener,只是限制了action必须是普通的对象。
  • 为了实现热替换,Redux将Flux Store中的状态和状态更新逻辑(Reducer)分离,更新Reducer时只需要replaceReducer,而不会丢失当前状态。

Redux有著名的三个原则

  • 单一数据源:全局状态都存放在一个单个Store的对象树。
  • 只读的State,需要通过触发一个Action对象来修改,Action描述了要发生的修改。
  • 使用纯函数修改State,reducer纯函数每次都会返回新的状态对象。

但这些原则并不是强制性的,比如大的应用可能拆分了多个Store、state可能在其他地方被直接修改、reducers触发了副作用等等。Redux实现本身并没有在Store或者Reducers上做任何检查或者限制,Redux Core被设计成最小API以及高度可扩展。三原则只是描述了Redux范式应该是怎么样的,具体实现和约束完全交给用户实现,因此Redux的生态非常繁荣,各种中间价百花齐放。

  • 为了实现可预测的状态修改和时间旅行,引入了immutability和serializability ,这样每次状态对象变化不直接修改状态,而是生成新的对象,因此能够保存旧的状态,通过Redux DevTool就能追踪每一次的Action的带来的修改。
  • 因为所有的状态都放在一个Store里,为了方便维护,可以将大的Reducer拆分多个子Reducer,每个子Reducer只管理对应的状态切片(State Slice),最后通过combineReducers()组合到一起。
  • 因为Reducer都应该是纯函数进行同步操作,为了实现副作用和异步逻辑,社区上出现了如redux-thunk (async/await写法),redux-saga (generator写法,dva内置)等中间件实现。
  • 为了方便维护和阅读,Action对象的Type一般是String,并引入了Action Creator(返回Action的函数)
  • ...

Redux的中间件其实就是改写了Redux Store里的dispatch方法,官方文档

const logger = (store) => (next) => (action) => {
  console.log("dispatching", action);
  let result = next(action);
  console.log("next state", store.getState());
  return result;
};


function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  let dispatch = store.dispatch
  middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
  return { ...store, dispatch }
}

React-Redux

The History and Implementation of React-Redux

以上只是Redux核心库,要用于React还需要引入React-Redux库做UI Binding,通过高阶组件connectmapState/mapDispatch来订阅Redux Store并通知React更新UI。具体来说就是将组件需要的state和dispath注入到props里,因为性能优化和React API变更等原因,React-Redux的迭代远比Redux核心库频繁。

  • v4及之前connectcomponentDidMount里订阅Store,每一个父组件和其子组件的connect都会独立订阅。并且每次对Store的修改总会触发connectre-render,mapState等逻辑都是放在render阶段。
  • v5.x重写了connect的逻辑为自顶向下订阅,因为componentDidMount的执行顺序是从子组件到父组件,因此之前版本里子组件先订阅到了Redux Store,可能导致和父组件传递的Props不一致的问题。同时将状态派生等逻辑从React render中移除,只有mapState结果不一样才会触发re-render,因此v5的性能比之前版本提升很多。
  • v6发布在React 16.3推出新的createContext() API 取代legacy context之后,为了和未来的“Concurrent React”兼容,直接将原本在Store实例内部的State放在了createContext()的context里,只有Provider组件真正订阅了Store,其他子组件都依赖context本身来触发re-render(自顶向下)。因为React context需要遍历整个组件树来找到consumer来触发re-render,导致v6在几乎所有场景都比v5慢,越复杂的场景越慢。

Redux多年发展下来积攒了大量的“最佳实践”,几乎很少会只使用Redux Core进行开发,为了实现各种特性都有大量的中间件可以选择。开发一个Redux应用需要的配置和模版代码也就越来越多,比如大家熟悉的经典Redux全家桶:Webpack + React + React-Redux + Redux + Redux DevTools + Saga/Thunk + reselect + actionCreator+normalizr,reducer纯函数的写法在修改深对象和数组时也需要许多额外逻辑。

现代Redux

Why Redux Toolkit is How To Use Redux Today

因为Redux的推广非常成功,以至于几乎和React绑定到一起,大量的用户从接触React起就开始使用Redux,配置和中间件等都照搬的模板,实际上Redux本身的灵活性反而成了普通用户的累赘,使得上手成本非常高。这个阶段许多像Dva的框架都开始尝试提供开箱即用的Redux,只暴露出少量的API 。 同时,

  • TypeScript开始兴起,给Redux Reducer/Store/Action/connect添加类型定义变得麻烦起来,几乎都需要用户自己手写。

  • React 16.8发布了Hook API,提供了useContextuseReudcer来实现类Redux的状态管理

Redux也积极响应变化,先是React-Redux v7使用Hook来重写connect,并在v7.1提供了useSelector/useDispatch Hook,Redux Starter Kit开始用TypeScript重写,然后改写了所有文档和实践,推荐用户直接使用Hook API而不是connect。直到现在,Redux和RTK中会经常提到“现代”这个词,可以简单概况为以下实践:

  • 不需要手写冗长的Redux模板和配置代码(比如ActionCreator)
  • React-Redux Hook API取代麻烦的connectmapState
  • Reducer中使用Immer来更新Immutable数据
  • 更简单的TypeScript集成
  • 内置安全检查
    • Immutability
    • Serializability
  • 按feature组织Redux逻辑
  • 抽象异步数据的获取、变更和缓存逻辑
  • 包含了必要的中间件

把以上这些最佳实践组合到一起就是如今的RTK,仍然是经典的Redux,仍然有灵活性,但易用且低门槛。

快速上手

安装

因为RTK只是处理Redux的核心逻辑,Store和React之间的通讯还需要React-Redux(useSelecotr, useDispatch

gzip大小

  • @reduxjs/toolkit:12.7k
  • react-redux:4.7k
  • jotai: 3.7k
npm install @reduxjs/toolkit react-redux

创建并连接Store

通过configureStore API配置一个Redux Store,简化了以往繁杂的组合reducer,middleware,devTool, enhancer等流程,默认配置基本上保证了开箱即用。

// store.ts
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
  // Root Reducer或者RTK的Slice Reducer组成的Map
  reducer: {
    // TODO 
  },
  // middleware: [],
  // 启用Redux DevTools,默认true
  // devTools: true,
})

其中middleware默认情况下在开发环境是[thunk, immutableStateInvariant, serializableStateInvariant],在生产环境仅保留thunk

通过React-Redux的Provider组件包裹React App来传递Redux Store

// index.ts
import { createRoot } from 'react-dom/client'
import { store } from './store'
import { Provider } from 'react-redux'
import App from './App'

const root = createRoot(document.getElementById('root'))
root.render(
  <Provider store={store}>
    <App />
  </Provider>
)

创建Redux Slice

通过createSlice API来创建一个“状态切片”,即包含了namespace,initialState,reducers,action的集合体,也是官方推荐的标准Redux写法。

createSlice封装了slice reducer,selector,immer,action creator等逻辑。

// slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

export interface CounterState {
  value: number
}

const initialState: CounterState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment(state) {
      // 内置immer,可以直接更改状态
      state.value += 1
    },
    incrementByAmount(state, action: PayloadAction<number>) {
      state.value += action.payload
    },
  },
})

export const { increment, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer

createSlice的返回值

{
    name : string,
    reducer : ReducerFunction,
    actions : Record<string, ActionCreator>,
    caseReducers: Record<string, CaseReducer>.
    getInitialState: () => State
}

然后将返回的reducer添加到configureStore中的reducer字段里

// store.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './slice'

export const store = configureStore({
  reducer: {
    counter: counterReducer
  },

作为对比,下面是Dva中的一个典型Model例子,两者在约定的写法上没有多少出入。

// 1. Initialize
const app = dva();

// 2. Model
const model = {
  namespace: 'count',
  state: 0,
  reducers: {
    add  (count) { return count + 1 },
    minus(count) { return count - 1 },
    reset() { return 0 },
  },
}

// 3. 绑定
app.model(model);

在React组件中使用State

  • 通过useSelector来读取Store中的状态
  • 通过useDispatch来派生状态,这里可以用createSlice返回的action来简化。
// Counter.ts
import { useSelector, useDispatch } from 'react-redux'
import { increment } from './slice'

// counter就是添加Slice时用的key
const selectCount = (state) => state.counter.value

const Counter() {
  const count = useSelector(selectCount)
  const dispatch = useDispatch()
  // 相当于dispatch({ type: 'counter/increment' })
  const onInc = () => dispatch(increment())
  
  return (
    <div>
      <button onClick={onInc}>+</button>
      <span>{count}</span>
    </div>
  )
}

TypeScript支持

这里只简单介绍基础用法,实际情况下TypeScript类型定义会复杂很多,这也是Redux一直以来的痛点,详见官方文档

// store.ts

/// store = configureStore(....)
export type RootState = ReturnType<typeof store.getState>
                                   
export type AppDispatch = typeof store.dispatch                                    

为了避免在每次使用useSelectoruseDispatch都带上这两个类型,可以重新定义这两个Hook

// hook.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

副作用/异步逻辑

因为Redux Store本身只有同步Dispatch Action的能力,reducer也不应该包含任何副作用,副作用通常需要第三方中间件,如redux-thunk (async/await写法),redux-saga (generator写法,dva内置)。

在RTK中默认启用了redux-thunk作为异步逻辑中间件,Thunk在Redux中指返回值为函数的action 生成器。

const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {
    usersLoading(state, action) {
      state.loading = 'loading'
    },
    usersFetched(state, action) {
      state.loading = 'idle'
      state.entities = action.payload
    }
  },
})

const { usersLoading, usersFetched } = usersSlice.actions

const fetchUserById = (userId) => async (dispatch, getState) => {
  dispatch(usersLoading())
  const response = await userAPI.fetchById(userId)
  dispatch(usersFetched(response.data))
}

同样的,RTK提供了createAsyncThunk来简化Thunk Action的定义流程:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'

// 创建thunk action创建函数
// 包含本身定义的用户逻辑,pending, fulfilled, rejected 4种action
const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {},
  // 在extraReducer中可以定义reducer来响应Slice外部的action
  // 比如这里的fetchUserById就是外部定义的Thunk Action
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserById.pending, (state) => {
        state.loading = 'loading'
      })
      .addCase(fetchUserById.fulfilled, (state, action) => {
        state.entities.push(action.payload)
    })
  },
})

// dispatch thunk, 相当于
// 1. dispatch pending
// 2. dispatch 用户逻辑
// 3. dispatch fulfilled 或 rejected
dispatch(fetchUserById(123))

简单实现createSlice

可以看出createSlice是简化模板代码最多的reducer和action的关键API,其实核心实现也比较的简单: 包含了createActioncreateReducer两个工具API

这里的简单实现忽略了immer的集成和Action Matcher相关逻辑

createAction:返回一个action创建器

function createAction(type) {
  function actionCreator(...args) {
    return { type, payload: args[0] }
  }

  actionCreator.toString = () => `${type}`
  actionCreator.type = type
  actionCreator.match = (action) => action.type === type

  return actionCreator
}

createReducer:构建action type的映射Map

function createReducer(initialState, actionsMap) {
  function reducer(state = initialState, action) {
    const caseReducer = actionsMap[action.type];
    if (caseReducer) {
      return caseReducer(state, action)
    }
    return previousState
  }

  reducer.getInitialState = () => initialState;
  return reducer
}

createSlice

function getType(slice, actionKey) {
  return `${slice}/${actionKey}`
}

function createSlice(options) {
  if (!options.name) {
    throw new Error('`name` is a required option for createSlice')
  }

  const { name, initialState, reducers = {} } = options
  const reducerNames = Object.keys(reducers)

  const sliceCaseReducersByName = {}
  const sliceCaseReducersByType = {}
  const actionCreators = {}

  reducerNames.forEach((reducerName) => {
    const caseReducer = reducers[reducerName]
    const type = getType(name, reducerName)

    sliceCaseReducersByName[reducerName] = caseReducer
    sliceCaseReducersByType[type] = caseReducer
    actionCreators[reducerName] = createAction(type)
  })

  function buildReducer() {
    // 这里还会有extraReducer的处理,这里省略
    const finalCaseReducers = { ...sliceCaseReducersByType }
    return createReducer(initialState, finalCaseReducers)
  }

  let _reducer;

  return {
    name,
    reducer(state, action) {
      if (!_reducer) _reducer = buildReducer()

      return _reducer(state, action)
    },
    actions: actionCreators,
    caseReducers: sliceCaseReducersByName,
    getInitialState() {
      if (!_reducer) _reducer = buildReducer()

      return _reducer.getInitialState()
    },
  }
}

更进一步 - RTK Query

在实际的应用场景中,除了一般的客户端状态管理,还常常充斥着异步数据获取与缓存的复杂状态逻辑。尽管RTK中已经提供了createSlicecreateAsyncThunk来简化异步数据的流程,但仍然有大量类似的逻辑需要开发者处理。

因此基于React社区经验(react-query/useSWR)和RTK本身,Redux官方推出了RTK Query来解决异步数据的问题。

虽然下面的例子都是React的,但和RTK一样,RTK Query的实现也是和具体UI框架无关

快速入门

社区其他实现习惯是将每个API分开定义,每一个都是单独的自定义hook,而在RTK Query需要通过createApi定义一个API服务,所有的API endpoint都集中在一块,产物是一个API Slice(类似于Redux Slice)

endpoints按用途分为了两类:查询(query)和突变(mutation)

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({baseUrl: '/'}),
  endpoints: (builder) => ({
    // 定义查询
    getPost: builder.query({
      query: (id) => `post/${id}`,
    }),
    // 定义突变
    addPost: builder.mutation({
      query: (body) => ({
        url: `posts`,
        method: 'POST',
        body,
      }),
    }),
  })
})

// 自动生成了每个Endpoint对应的useQuery/useMutation Hook
export const { useGetPostsQuery, useAddPostMutation } = api

export const { endpoints, reducerPath, reducer, middleware } = api

// endpoints也包含hooks
api.endpoints.getPosts.useQuery // useGetPostsQuery
api.endpoints.updatePost.useMutation // useAddPostMutation

其中通过自定义baseQuery就可以实现全局的请求拦截器。如果需要单独处理某个endPoint的请求行为,可以通过onQueryStarted实现,该函数会在请求的整个生命周期(发起/成功/失败)中被调用,或者定义queryFn来绕过baseQuery进行查询。

// 函数签名
async function onQueryStarted(
  arg: QueryArg,
  {
    dispatch,
    getState,
    extra,
    requestId,
    queryFulfilled, // Promise
    getCacheEntry,
    updateCachedData,
  }: QueryLifecycleApi
): Promise<void>


// 例子
const api = createApi({
  baseQuery: fetchBaseQuery({baseUrl: '/'}),
  endpoints: (build) => ({
    getPost: build.query<Post, number>({
      query: (id) => `post/${id}`,
      async onQueryStarted(id, { dispatch, queryFulfilled }) {
        // 请求开始
        dispatch('...')
        try {
          const { data } = await queryFulfilled
          // 请求成功
          dispatch('...')
        } catch (err) {
          // 请求失败
          dispatch('...')
        }
      },
    }),
  }),
})

API Slice

主要包含了以下部分:

  • Redux Reducer:管理缓存数据的reducer
  • Redux MiddleWare:管理缓存数据的生命周期和订阅的中间件
  • Endpoints:根据用户定义的endpoints生成的Redux相关逻辑,同时也包含Hooks
  • Utils: 提供一系列的Action Creator用于手动管理缓存数据
  • Hooks:根据endpoints生成的数据获取React Hook
type Api = {
  // Redux 集成
  reducerPath: string;
  reducer: Reducer;
  middleware: Middleware;

  // Endpoint 交互
  endpoints: Record<string, EndpointDefinition>;

  util: {
    // ...
    updateQueryData: UpdateQueryDataThunk;
    patchQueryData: PatchQueryDataThunk;
    prefetch: PrefetchThunk;
    invalidateTags: ActionCreatorWithPayload<
      Array<TagTypes | FullTagDescription<TagTypes>>,
      string
    >;
    resetApiState: SliceActions["resetApiState"];
    selectInvalidatedBy: (
      state: RootState<Definitions, string, ReducerPath>,
      tags: ReadonlyArray<TagDescription<TagTypes>>
    ) => Array<{
      endpointName: string;
      originalArgs: any;
      queryCacheKey: string;
    }>;
    // ...
  };

  // 自动生成的 React hooks
  [key in GeneratedReactHooks]: GeneratedReactHooks[key];
};

查询 & 突变

useQuery用于获取服务端数据,和useSWRreact-query,ahooks的useRequest等请求Hook的用法类似。 主要包含以下特性:

  • 轮询
  • 缓存
  • 依赖刷新
  • 聚焦重新请求
  • 条件查询(ready/skip)
  const {
    data,
    error,
    isLoading,
    isError,
    isFetching,
    refetch // 强制重新请求的函数
  } = useGetPostQuery(id, {
    skip: false,
    pollingInterval: 10_000,
    refetchOnFocus: true, 
    selectFromResult: undefined, // 根据选择器,只订阅结果的一部分,其他部分改变不会引起重渲染
    refetchOnReconnect: true,
    refetchOnMountOrArgChange: true
  });

因为本质上还是订阅了Redux Store,所以useQueryselectFromResult同样遵循selector的原则,如果返回的引用发生改变(比如map/filter等操作)就会使优化失效。 如果需要组件级别来派生数据并正确缓存,则需要每一个组件有一个唯一的reselect选择器实例,可以通过组合useMemoreselect来实现:

import { createSelector } from '@reduxjs/toolkit'
import { useGetPostsQuery } from '../api/apiSlice'

// useMemo保证该选择器在当前组件的渲染期间引用不变
const selectPostsForUser = useMemo(() => {
  const emptyArray = [];
  
  // 返回记忆化的选择器实例
  return createSelector(
    [(res) => res.data, (res, name) => name],
    (data, name) =>
      data?.filter((post) => post.name.includes(name)) ?? emptyArray
  );
}, []);

const { filteredPosts } = useGetPostsQuery(undefined, {
  selectFromResult: (result) => ({
    ...result,
    filteredPosts: selectPostsForName(result, name)
  })
})

useMutation用于发送数据到服务端并更新本地缓存

const [updatePost, result] = useAddPostMutation();
const { isLoading, data } = result;

updatePost({
  // ...
})

缓存

缓存是这类异步请求库的核心特性,RTK Query将每个查询的endpoint和参数序列化为字符串当作queryCacheKeyqueryCacheKey相同的查询会共享同一个请求和缓存。当查询的引用计数为0(即没有组件用到),在一定时间后缓存将会被自动清除。除了根据queryCacheKey进行缓存,RTK Query还可以通过缓存标签中间件实现自动的缓存管理。

比如以下简单的CURD配置,给Post相关的查询标记为'Posts',而Post相关的突变使有'Posts'标签的缓存失效。因此当发生增删改后,对应Post的查询缓存就会失效并自动重新发起请求,标签还可以指定id来匹配特定查询。

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

interface Tag {
  type: string;
  id?: string | number;
}

export const postApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/' }),
  tagTypes: ['Posts'],
  endpoints: (build) => ({
    getPosts: build.query({
      query: () => 'posts',
      providesTags: [{ type: 'Posts', id: 'LIST' }],
    }),
    getPost: build.query({
      query: (id) => `post/${id}`,
      providesTags: (result, error, id) => [{ type: 'Posts', id }],
    }),
    addPost: build.mutation({
      query(body) {
        return {
          url: `post`,
          method: 'POST',
          body,
        }
      },
      invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
    }),

    deletePost: build.mutation({
      query(id) {
        return {
          url: `post/${id}`,
          method: 'DELETE',
        }
      },
      invalidatesTags: (result, error, id) => [{ type: 'Posts', id }],
    }),
  }),
})

标签中间件的部分实现:

// packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts

function invalidateTags(
    tags: readonly FullTagDescription<string>[],
    mwApi: SubMiddlewareApi
  ) {
    const rootState = mwApi.getState()
    const state = rootState[reducerPath]

    const toInvalidate = api.util.selectInvalidatedBy(rootState, tags)

    context.batch(() => {
      const valuesArray = Array.from(toInvalidate.values())
      for (const { queryCacheKey } of valuesArray) {
        const querySubState = state.queries[queryCacheKey]
        const subscriptionSubState = state.subscriptions[queryCacheKey]
        if (querySubState && subscriptionSubState) {
          if (Object.keys(subscriptionSubState).length === 0) {
            // 引用计数为0时直接清理缓存
            mwApi.dispatch(
              removeQueryResult({
                queryCacheKey: queryCacheKey as QueryCacheKey,
              })
            )
          } else if (querySubState.status !== QueryStatus.uninitialized) {
            // 存在引用且已经查询过,重新查询并更新缓存
            mwApi.dispatch(refetchQuery(querySubState, queryCacheKey))
          } else {
          }
        }
      }
    })
  }

总结

虽然近年来React社区的状态管理方案层出不穷,比如原子式的jotairecoil,基于proxy的Valtio,但Redux仍然是使用最广泛,影响力最大的,生态最繁荣的一个方案。官方这次力推的RTK和RTK Query通过大量抽象和简化已经基本上做到和其他轻量库类似的开发体验(包括TypeScript类型补全),同时兼具效率和可扩展性(虽然API数量还是不少)。如果已经在用Redux类的状态管理,不妨往前迈一步,尝试下现代Redux开发。

猜你喜欢

转载自juejin.im/post/7114120958637506591