Redux概念
概念
Redux之于React,相当于vuex之于Vue
虽然我们可以通过父子组件之间的通信机制来实现状态的共享,但是在同级组件之间,状态的共享就不是那么容易了。并且,如果能将一些需要共享的数据,集中地、统一地放在某个位置,那么也可以降低模块之间的耦合度,并且提高响应速度。
Redux
用于管理“全局”的状态,Redux 提供的模式和工具使你更容易理解应用程序中的状态何时、何地、为什么、state 如何被更新,以及当这些更改发生时你的应用程序逻辑将如何表现
用/不用
虽然Redux
可以帮我们解决状态共享的问题,但是使用他的同时,也需要权衡利弊。
如果只是简单地在几个组件之间进行通信,其实也没必要引入额外的库,毕竟会导致更多的代码编写。
在以下情况下使用 Redux:
- 应用中有很多 state 在多个组件中需要使用
- 应用 state 会随着时间的推移而频繁更新
- 更新 state 的逻辑很复杂
- 中型和大型代码量的应用,很多人协同开发
Redux库和生态
Redux 是一个小型的独立 JS 库。 但是,它通常与其他几个包一起使用:React-Redux
Redux 可以集成到任何的 UI 框架中,其中最常见的是 React 。React-Redux 是我们的官方包,它可以让 React 组件访问 state 片段和 dispatch actions 更新 store,从而同 Redux 集成起来。Redux Toolkit
Redux Toolkit 是我们推荐的编写 Redux 逻辑的方法。 它包含我们认为对于构建 Redux 应用程序必不可少的包和函数。 Redux Toolkit 构建是我们建议的最佳实践中,简化了大多数 Redux 任务,预防了常见错误,并使编写 Redux 应用程序变得更加容易。
Redux术语
state管理
function Counter() {
// State: counter 值
const [counter, setCounter] = useState(0)
// Action: 当事件发生后,触发状态更新的代码
const increment = () => {
setCounter(prevCounter => prevCounter + 1)
}
// View: 视图定义
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
counter
对应于state
increment
对应于actions
<div></div>
对应于view
这是一个包含以下部分的自包含应用程序:
- state:驱动应用的真实数据源头
- view:基于当前状态的视图声明性描述
- actions:根据用户输入在应用程序中发生的事件,并触发状态更新
接下来简要介绍 “单向数据流(one-way data flow)”:
- 用 state 来描述应用程序在特定时间点的状况
- 基于 state 来渲染出 View
- 当发生某些事情时(例如用户单击按钮),state 会根据发生的事情进行更新,生成新的 state
- 基于新的 state 重新渲染 View
然而,当我们有多个组件需要共享和使用相同 state时,可能会变得很复杂,尤其是当这些组件位于应用程序的不同部分时。有时这可以通过 “提升 state” 到父组件来解决,但这并不总是有效。 解决这个问题的一种方法是从组件中提取共享 state,并将其放入组件树之外的一个集中位置。这样,我们的组件树就变成了一个大“view”,任何组件都可以访问 state 或触发 action,无论它们在树中的哪个位置! 通过定义和分离 state 管理中涉及的概念并强制执行维护 view 和 state 之间独立性的规则,代码变得更结构化和易于维护。 这就是 Redux 背后的基本思想:应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。
不可变性 Immutability
Mutable
:可改变的immutable
:不可变的
在JavaScript
中,对象(object)和数组(array)默认都是mutable
的。
如果我创建一个对象,我可以改变其字段的内容。如果我创建一个数组,也可以更改其中的内容。
const obj = { a: 1, b: 2 }
// 对外仍然还是那个对象,但它的内容已经变了
obj.b = 3
const arr = ['a', 'b']
// 同样的,数组的内容改变了
arr.push('c')
arr[1] = 'd'
以上是使用
改变
对象或数组的例子。内存中还是原来对象或者数组的引用,但是里面的内容发生了变化。
如果想要使用不可变的方式来更新,代码必须复制原来的object/array,然后更新它的复制体
比如如下代码,就是使用不可变的方式来进行更新。
const obj = {
a: {
// 为了安全的更新 obj.a.c,需要先复制一份
c: 3
},
b: 2
}
const obj2 = {
// obj 的备份
...obj,
// 覆盖 a
a: {
// obj.a 的备份
...obj.a,
// 覆盖 c
c: 42
}
}
const arr = ['a', 'b']
// 创建 arr 的备份,并把 c 拼接到最后。
const arr2 = arr.concat('c')
// 或者,可以对原来的数组创建复制体
const arr3 = arr.slice()
// 修改复制体
arr3.push('c')
术语
Action
Action Creator
Reducer
reducer是一个函数,接收当前的state
和一个action
对象
Reducer 必需符合以下规则:
- 仅使用 state 和 action 参数计算新的状态值
- 禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做 不可变更新(immutable updates)。
- 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码
reducer函数内部的逻辑通常遵循以下几个步骤:
- 检查reducer是否关心这个
action
- 如果关心,那么就复制
state
,并用新值更新state
副本,然后返回新的state
- 不关心就直接返回
state
- 如果关心,那么就复制
如下所示:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// 检查 reducer 是否关心这个 action
if (action.type === 'counter/increment') {
// 如果是,复制 `state`
return {
...state,
// 使用新值更新 state 副本
value: state.value + 1
}
}
// 返回原来的 state 不变
return state
}
Store
当前Redux
应用的state
存在于一个名为store
的对象中。
store 是通过传入一个 reducer 来创建的,并且有一个名为 getState
的方法
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
dispatch
Redux store 有一个方法叫 dispatch
。更新 state 的唯一方法是调用 **store.dispatch()**
并传入一个 action 对象。 store 将执行所有 reducer 函数并计算出更新后的 state,调用 getState()
可以获取新 state。
store.dispatch({ type: 'counter/increment' })
console.log(store.getState())
// {value: 1}
dispatch 一个 action 可以形象的理解为 “触发一个事件”。发生了一些事情,我们希望 store 知道这件事。 Reducer 就像事件监听器一样,当它们收到关注的 action 后,它就会更新 state 作为响应。
Selector
Selector 函数可以从 store 状态树中提取指定的片段。随着应用变得越来越大,会遇到应用程序的不同部分需要读取相同的数据,selector 可以避免重复这样的读取逻辑:
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
数据流
在React
中,我们谈到了单向的数据流,
- State 描述了应用程序在特定时间点的状况
- 基于 state 来渲染视图
- 当发生某些事情时(例如用户单击按钮),state 会根据发生的事情进行更新
- 基于新的 state 重新渲染视图