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 必需符合以下规则:
- 仅使用
state
和action
参数计算新的状态值。
- 禁止直接修改
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 的createSlice
和createReducer
中编写 “mutation” 逻辑,因为它们在内部使用 Immer!如果你在没有 Immer 的 reducer 中编写 mutation 逻辑,它将改变状态并导致错误!
用 Thunk 编写异步逻辑
目前我们的一系列流程都是同步的:dispatch action,store 调用 reducer 来计算新状态,然后 dispatch 函数完成并结束。
但实际上也有一些 API 请求数据之类的异步逻辑可能需要我们去处理。
thunk 是一种特定类型的 Redux 函数,可以包含异步逻辑。Thunk 是使用两个函数编写的:
- 一个内部 thunk 函数,它以
dispatch
和getState
作为参数
- 外部创建者函数,它创建并返回 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 '对象,包含几个有用的函数和信息:
dispatch
和getState
:dispatch
和getState
方法由 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
,它只会在 posts
或 userId
发生变化时重新执行输出 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,它会生成类似于
selectAll
和 selectById
的选择器。getInitialState
生成一个空的
{ids: [], entities: {}}
对象。也可以传递更多字段给 getInitialState
,这些字段将会被合并。upsertMany
当收到
fetchPosts.fulfilled
action 时,我们通过将现有 state
和 action.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 的
- 通过传入
sortComparer
选项,可以按排序顺序保持项目 ID - adapter 对象包括:
adapter.getInitialState
,它可以接受额外的 state 字段,如加载 state- 预先创建通用 reducer,例如
setAll
、addMany
、upsertOne
和removeMany
adapter.getSelectors
,生成类似于selectAll
和selectById
的 selector
API 帮助管理slice中的范式化数据
Redux Toolkit详细示例
关于 Redux Toolkit
官方推荐的标准的 Redux 逻辑开发模式
Redux Toolkit 包含:
configureStore()
(opens new window):创建一个顶层仓库,其封装了createStore
,简化配置项。
createReducer()
(opens new window)将 action type 映射到 reducer 函数,而不是编写 switch...case 语句。另外,它带有 proxy 更新,例如state.todos[3].completed = true
,并且它会自动调用。
createAction()
(opens new window)生成给定 action type 字符串的 action creator 函数。该函数本身已定义了toString()
,因此可以代替常量类型使用。
createSlice()
(opens new window)创建初始化状态 initial state,并自动生成 reducer。
createAsyncThunk
(opens new window): 接收一个 action type 字符串和一个返回值为 promise 的函数, 并生成一个 thunk 函数,这个 thunk 函数可以基于之前那个 promise ,dispatch 一组 type 为pending/fulfilled/rejected
的 action。
createEntityAdapter
(opens new window): 生成一系列可复用的 reducer 和 selector,从而管理 store 中的规范化数据。
createSelector
(opens new window)来源于 Reselect (opens new window)库,(记忆化数据) 并重新 export 出来以方便使用。
具体示例
src/features/todos/todosSlice.js
总结
- Redux Toolkit (RTK) 是编写 Redux 逻辑的标准方式
- RTK 包含用于简化大多数 Redux 代码的 API
- RTK 围绕 Redux 核心,并包含其他有用的包
configureStore
- 自动组合 slice reducers 来创建根 reducer
- 自动设置 Redux DevTools 扩展和调试 middleware
用来设置一个具有良好默认值的 Redux store
createSlice
- 根据 slice/reducer 名称自动生成 action creators
- Reducers 可以使用 Immer 在
createSlice
中“改变”(mutate)state
简化了 Redux actions 和 reducers 的编写
createAsyncThunk
- 自动生成一个 thunk +
pending/fulfilled/rejected
action creators - dispatch thunk 运行 payload creator 并 dispatch actions
- 可以在
createSlice.extraReducers
中处理 thunk actions
为异步调用生成 thunk
createEntityAdapter
- 包括用于常见任务的 reducer 功能,例如添加/更新/删除 items
- 为
selectAll
和selectById
生成记忆化 selectors
为标准化 state 提供了 reducers + selectors
- 作者:慕雨
- 链接:https://www.axin.work/article/b6997f01-e3c0-4cf0-b660-12f958573d35
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。