type
status
date
slug
summary
tags
category
icon
password

Redux

入门Redux

Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。

安装

创建一个 React Redux 应用

官方推荐的使用 React 和 Redux 创建新应用的方式是使用 官方 Redux+JS 模版 (opens new window)Redux+TS 模板 (opens new window),它基于 Create React App (opens new window),利用了 Redux Toolkit (opens new window)和 Redux 与 React 组件的集成.
或者在项目中添加依赖:

Redux Toolkit

Redux Toolkit (opens new window)是我们官方推荐的编写 Redux 逻辑的方法。它围绕 Redux 核心,并包含我们认为对于构建 Redux 应用必不可少的软件包和功能,简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序更加容易。

Redux 核心库

基础示例(opens new window)

应用的整体全局状态以对象树的方式存放于单个 store
唯一改变状态树(state tree)的方法是创建 action,一个描述发生了什么的对象,并将其 dispatch 给 store。 要指定状态树如何响应 action 来进行更新,你可以编写纯 reducer 函数,这些函数根据旧 state 和 action 来计算新 state。

Redux Toolkit 示例(opens new window)

Redux Toolkit 简化了编写 Redux 逻辑和设置 store 的过程。 使用 Redux Toolkit,相同的逻辑如下所示:
Redux Toolkit 使我们可以编写更精短且更易于阅读的代码,同时仍然遵循同样的 Redux 规范和数据流。

在应用程序中加入Redux

在这里使用了一个名为 <Provider> 的组件在幕后传递 Redux store,以便他们可以访问 useSelector 等相关 hook。

Redux基础

Reduce 术语

Action

action 是一个具有 type 字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件.
一个典型的 action 对象可能如下所示:

Action Creator(opens new window)

action creator 是一个创建并返回一个 action 对象的函数。它的作用是让你不必每次都手动编写 action 对象:

Reducer

reducer 是一个函数,接收当前的 state 和一个 action 对象。
函数签名是:(state, action) => newState可以将 reducer 视为一个事件监听器,它根据接收到的 action(事件)类型处理事件。
Reducer 必需符合以下规则:
  • 仅使用 stateaction 参数计算新的状态值。
  • 禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做 不可变更新(immutable updates)
  • 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码。
下面是 reducer 的小例子,展示了每个 reducer 应该遵循的步骤:

Store

当前 Redux 应用的 state 存在于一个名为 store 的对象中。
store 是通过传入一个 reducer 来创建的,并且有一个名为 getState 的方法,它返回当前状态值:

Dispatch

Redux store 有一个方法叫 dispatch更新 state 的唯一方法是调用 store.dispatch() 并传入一个 action 对象。 store 将执行所有 reducer 函数并计算出更新后的 state,调用 getState() 可以获取新 state。
dispatch 一个 action 可以形象的理解为 "触发一个事件"
我们通常调用 action creator 来调用 action:

Selector

Selector 函数可以从 store 状态树中提取指定的片段。随着应用变得越来越大,会遇到应用程序的不同部分需要读取相同的数据,selector 可以避免重复这样的读取逻辑:
笔记
类似于 vuex 的计算属性

创建 Redux Store

一个可能的配置可能是这样的:
app/store.js
其中 counterReducer 模块可能是这样的:
features/counter/counterSlice.js

基于 proxy 实现的更新不可变逻辑

常见的手动编写不可变的更新逻辑可能是这样的:
所以,上面的代码可以变成这样:
警告!
你只能在 Redux Toolkit 的 createSlicecreateReducer 中编写 “mutation” 逻辑,因为它们在内部使用 Immer!如果你在没有 Immer 的 reducer 中编写 mutation 逻辑,它将改变状态并导致错误!

用 Thunk 编写异步逻辑

目前我们的一系列流程都是同步的:dispatch action,store 调用 reducer 来计算新状态,然后 dispatch 函数完成并结束。
但实际上也有一些 API 请求数据之类的异步逻辑可能需要我们去处理。
thunk 是一种特定类型的 Redux 函数,可以包含异步逻辑。Thunk 是使用两个函数编写的:
  • 一个内部 thunk 函数,它以 dispatchgetState 作为参数
  • 外部创建者函数,它创建并返回 thunk 函数
features/counter/counterSlice.js
并像使用普通 Redux action creator 一样使用它们:
AJAX 调用以从服务器获取数据时,你可以将该调用放入 thunk 中。

使用createAsyncThunk 生成 Thunk

Redux Toolkit 的 createAsyncThunk API 生成 thunk,并能够自动 dispatch 那些 "start/success/failure" action。
createAsyncThunk 接收 2 个参数:
  • 将用作生成的 action 类型的前缀的字符串
  • 一个 “payload creator” 回调函数,它应该返回一个包含一些数据的 Promise,或者一个被拒绝的带有错误的 Promise
features/posts/postsSlice
createAsyncThunk,只能传递一个参数,无论我们传入的是什么,它都将成为 payload creation 回调的第一个参数。
payload creator 的第二个参数是一个' thunkAPI '对象,包含几个有用的函数和信息:
  • dispatchgetStatedispatchgetState 方法由 Redux store 提供。你可以在 thunk 中使用这些来发起 action,或者从最新的 Redux store 中获取 state (例如在发起 另一个 action 后获取更新后的值)。
  • extra:当创建 store 时,用于传递给 thunk 中间件的“额外参数”。这通常时某种 API 的包装器,比如一组知道如何对应用程序的服务器进行 API 调用并返回数据的函数,这样你的 thunk 就不必直接包含所有的 URL 和查询逻辑。
  • requestId:该 thunk 调用的唯一随机 ID ,用于跟踪单个请求的状态。
  • signal:一个AbortController.signal 函数,可用于取消正在进行的请求。
  • rejectWithValue:一个用于当 thunk 收到一个错误时帮助自定义 rejected action 内容的工具。
(如果你要手写 thunk 而不是使用 createAsyncThunk,则 thunk 函数将获取 (dispatch, getState) 作为单独的参数,而不是将他们放在一个对象中。)

在应用程序中加入Redux

在这里使用了一个名为 <Provider> 的组件在幕后传递 Redux store,以便他们可以访问 useSelector 等相关 hook。

组件中使用 Redux

features/counter/Counter.js

数据范式化和性能优化

提升渲染性能

关于 redux 的渲染行为

我们每当发起一个 action 时,都会运行 useSelector,如果返回的是一个新的值,组件将会强制渲染。
如果我们在 useSelector 钩子中使用了 filter() 等函数,将会导致 useSelector 总会返回一个新的数组,那么每次调用 action 时,组件都将重新渲染,无论返回的数据有没有发生变化。

使用记忆化的 Selector

要解决上面的问题,我们就需要当数据未改变时获取的是上次数组的引用。
即“记忆”:保存之前的一组输入和计算的结果,如果输入相同,则返回之前的结果,而不是重新计算。

createSelector 函数

features/posts/postsSlice.js
createSelector 参数
  • 将一个或多个“输入 selector ”函数作为第一个参数;
  • 将一个 “输出 selector” 函数作为第二个参数,并且该函数的参数来自于 createSelector 的第一个参数。
我们尝试多次调用 selectPostsByUser,它只会在 postsuserId 发生变化时重新执行输出 selector
记忆化的 selector 是提高 React + Redux 应用程序性能的宝贵工具,因为它们可以帮助我们避免不必要的重新渲染,并且如果输入数据没有更改,还可以避免执行潜在的复杂或昂贵的计算。

范式化数据

React 的默认行为是当父组件渲染时,React 会递归渲染其中的所有子组件!
所以当重新渲染一个组件时会导致其组件树的重新渲染。
一种方法是使用 React 提供的 memo 来缓存数据:
这确保了组件只有在 props 真正更改时才会重新渲染。
另一种方法是使用 redux Toolkit 提供的 createEntityAdapter 函数管理范式化数据

什么是范式化?

我们一般将数据储存在数组中,当想要对数据通过 ID 字段来查找时,不得不使用遍历所有数据来获取 ID 对应项,当数据量较大时就比较耗时了。而一种无需检查所有其他项,直接根据其 ID 查找单个项的方法过程被称为“范式化”。
以下是一组“用户”对象的范式化 state 可能,如下所示:
说明
类似于 map 结构

createEntityAdapter

createEntityAdapter API 提供了一种将数据储存在 slice 中的标准方法,方法是获取项目集合并将它们放入 { ids: [], entities: {} } 的结构中。除了这个预定义的 state 结构,它还会生成一组知道如何处理该数据的 reducer 函数和 selector。
createEntityAdapter 接受一个选项对象,该对象可能包含一个 sortComparer 函数,该函数将用于通过比较两个项目来保持项目 id 数组的排序(工作方式与 array.sort() 相同)。
它返回一个对象,该对象包含 一组生成的 reducer 函数,用于操作实体 state 对象。这些 reducer 函数既可以用作特定 action 类型的 reducer,也可以用作 createSlice 中另一个 reducer 中的 "mutating" 实用函数。其中:

getSelectors

可以传入一个 selector,它从 Redux 根 state 返回这个特定的 state slice,它会生成类似于 selectAllselectById 的选择器。

getInitialState

生成一个空的 {ids: [], entities: {}} 对象。也可以传递更多字段给 getInitialState,这些字段将会被合并。

upsertMany

当收到 fetchPosts.fulfilled action 时,我们通过将现有 stateaction.payload 传入 postsAdapter.upsertMany 函数,从而将所有传入的数据添加到 state 中。如果 action.payload 中有任何项目已经存在于我们 state 中,upsertMany 函数将根据匹配的 ID 将它们合并在一起。
总结
  • 可用于优化性能的记忆化 selector 函数
    • Redux Toolkit 重新导出了 Reselect 中的 createSelector 函数,该函数会生成记忆化的 selector
    • 只有当输入 selector 返回新的值时,记忆 selector 才会重新计算结果
    • 记忆化可以跳过昂贵的计算,并确保返回相同的结果引用
  • 可以使用多种方式来优化使用了 Redux 的 React 组件的渲染
    • 避免在 useSelector 中创建新的对象/数组引用——这将导致不必要的重新渲染
    • 可以传递记忆化的 selector 函数给useSelector来优化渲染
    • useSelector 可以接受比较函数,例如 shallowEqual,而不是引用相等
    • 组件可以包装在 React.memo() 中,仅在它们的 prop 发生变化时重新渲染
    • 列表渲染可以通过让列表父组件仅读取每项的 ID 组成的数组、将 ID 传递给列表项子项并在子项中按 ID 检索项来实现优化
  • 范式化 state 结构是存储项的推荐方法
    • “范式化”意味着不重复数据,并通过 ID 将项目存储在查找表中
    • 范式化 state 形式通常看起来像 {ids: [], entities: {}}
  • Redux Toolkit 的
    • API 帮助管理slice中的范式化数据
    • 通过传入 sortComparer 选项,可以按排序顺序保持项目 ID
    • adapter 对象包括:
      • adapter.getInitialState,它可以接受额外的 state 字段,如加载 state
      • 预先创建通用 reducer,例如 setAlladdManyupsertOneremoveMany
      • adapter.getSelectors,生成类似于 selectAllselectById 的 selector

Redux Toolkit详细示例

关于 Redux Toolkit

官方推荐的标准的 Redux 逻辑开发模式
Redux Toolkit 包含:
  • createReducer() (opens new window)将 action type 映射到 reducer 函数,而不是编写 switch...case 语句。另外,它带有 proxy 更新,例如 state.todos[3].completed = true,并且它会自动调用。
  • createAction() (opens new window)生成给定 action type 字符串的 action creator 函数。该函数本身已定义了 toString(),因此可以代替常量类型使用。
    • createAsyncThunk (opens new window): 接收一个 action type 字符串和一个返回值为 promise 的函数, 并生成一个 thunk 函数,这个 thunk 函数可以基于之前那个 promise ,dispatch 一组 type 为 pending/fulfilled/rejected 的 action。

    具体示例

    src/features/todos/todosSlice.js
    总结
    • Redux Toolkit (RTK) 是编写 Redux 逻辑的标准方式
      • RTK 包含用于简化大多数 Redux 代码的 API
      • RTK 围绕 Redux 核心,并包含其他有用的包
    • configureStore
      • 用来设置一个具有良好默认值的 Redux store
      • 自动组合 slice reducers 来创建根 reducer
      • 自动设置 Redux DevTools 扩展和调试 middleware
    • createSlice
      • 简化了 Redux actions 和 reducers 的编写
      • 根据 slice/reducer 名称自动生成 action creators
      • Reducers 可以使用 Immer 在 createSlice 中“改变”(mutate)state
    • createAsyncThunk
      • 为异步调用生成 thunk
      • 自动生成一个 thunk + pending/fulfilled/rejected action creators
      • dispatch thunk 运行 payload creator 并 dispatch actions
      • 可以在 createSlice.extraReducers 中处理 thunk actions
    • createEntityAdapter
      • 为标准化 state 提供了 reducers + selectors
      • 包括用于常见任务的 reducer 功能,例如添加/更新/删除 items
      • selectAllselectById 生成记忆化 selectors
    React笔记-Jotai&MobxReact笔记-Router
    Loading...
    慕雨
    慕雨
    一个普通的干饭人🍚
    公告
    🎉欢迎来到慕雨的博客小站🎉
    这里是我的个人学习、生活记录
    --- 免责声明 ---
    ⚠️ 本站内容仅代表个人观点
    ⚠️ 本站内容仅供学习参考使用