官网:https://redux.js.org/tutorials/essentials/part-1-overview-concepts
一、Redux 概览
1. Redux 是什么
Redux 是一种模式也是一个库,使用叫做“actions”的事件来管理和更新应用状态
(把 app、state 翻译成应用和状态在语句中读起来怪怪的,下面的篇幅中,我不翻译了,直接使用英文)
2. 什么时候使用 Redux
当有以下这些情况时,Redux 会很有用
- 在 app 中很多 state 在很多地方使用
- state 更新频繁
- 更新 state 的逻辑可能很复杂
- app 有很多代码量,很多人一起写这个 app
3. Redux 好帮手
- React-Redux
- Redux Toolkit
- Redux DevTools Extension
4. State Management
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(prevCounter => prevCounter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
- state,驱动整个 app
- view,基于 state 声明式地描述 UI
- actions,基于用户行为的事件触发更新 state
这是一个单向数据流的小例子
- State 描述 app 在一个特定点的情况
- UI 根据 state 渲染
- 当有什么事情发生的时候(比如点击一个按钮),state 根据发生的事情更新
- UI 根据新 state 重新渲染
5. Immutability
Immutability 意味着“不可变”。
JavaScript 的对象和数组默认是可变的,比如下面这个例子,改变了对象和数组的内容,但是它们指向的内存地址是同一个。
const obj = { a: 1, b: 2 }
obj.b = 3
const arr = ['a', 'b']
arr.push('c')
arr[1] = 'd'
我们要“不可变”地更新值,可以像下面这样
Redux 希望所有 state 的更新都是“不可变”的方式
const obj = {
a: {
c: 3
},
b: 2
}
const obj2 = {
// copy obj
...obj,
a: {
...obj.a,
c: 42
}
}
const arr = ['a', 'b']
const arr2 = arr.concat('c')
const arr3 = arr.slice()
arr3.push('c')
6. Actions
一个 action 是一个有 type 字段的对象。我们可以把一个 action 认为一个事件用来描述 app 发生的事情
一个典型的 action 长得像下面这个样子
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
- type 是一个描述性的名字,一般为
"domain/eventName"
- paload 是参数
7. Action Creators
一个 action creater 是一个函数,用来创建并返回一个 action 对象。
我们通常不必每次手动地直接写 action 对象
const addTodo = text => {
return {
type: 'todos/todoAdded',
payload: text
}
}
8. Reducer
一个 reducer 是一个函数,用来接收当前的 state
和一个 action
对象,它能够决定怎么更新 state 并返回新的 state:(state, action) => newState
。
我们可以认为一个 reducer 是一个事件监听器,根据收到的 action 类型来处理事件。
Reducers 必须遵守下面这些特定的规则
- 它们必须基于
state
和action
参数来计算新的 state 值 - 它们不允许修改现有的
state
,因此,它们必须通过复制现有的state
再修改进行“不可变更新” - 它们不能做任何异步逻辑、计算随机值,或导致其他“side effects”
reducer 函数的逻辑一般遵循下面这些步骤
- 检查 reducer 是否关心 action
- 如果是的,就复制一份 state 再更新并返回它
- 否则返回现有未变化的 state
Reducers 在内部可以使用任何类型的逻辑来决定新的 state,比如 if/else
、switch
等等
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
详细解释为什么这个函数名字叫 “Reducers”?
可以参看 Array.reduce()
(MDN),每次处理数组中的每一项,然后最终返回单个最终的值。可以认为“把数组缩减成一个值”。
const numbers = [2, 5, 8]
const addNumbers = (previousResult, currentItem) => {
console.log({ previousResult, currentItem })
return previousResult + currentItem
}
const initialValue = 0
const total = numbers.reduce(addNumbers, initialValue)
// {previousResult: 0, currentItem: 2}
// {previousResult: 2, currentItem: 5}
// {previousResult: 7, currentItem: 8}
console.log(total)
// 15
试试创建一个 React actions 调用 reduce()
const actions = [
{ type: 'counter/increment' },
{ type: 'counter/increment' },
{ type: 'counter/increment' }
]
const initialState = { value: 0 }
const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}
我们可以说 Redux reducers 把一系列的 actions 缩减成一个单个 state。
与 Array.reduce()
做比较
Array.reduce()
中,reducer 的运行是立刻的- Redux 中,reducer 的运行贯穿整个 app 的生命周期
9. Store
当前 Redux app state 存放在一个叫 store 的对象中
通过传入一个 reducer 来创建 store,然后它有一个 getState
方法用来返回当前的 state 值
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
10. Dispatch
Redux store 有一个方法叫 dispatch
,更新 state 的唯一方法就是调用 **store.dispatch()**
并传入一个 action 对象,store 将运行 reducer 函数并在内部保存新的 state,我们可以通过 getState()
获取新的值。
store.dispatch({ type: 'counter/increment' })
console.log(store.getState())
// {value: 1}
我们可以认为 dispatching actions 就是在 app 中“触发一个事件”
通常调用 action creators 来 dispatch action
const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
console.log(store.getState())
// {value: 2}
11. Selectors
Selectors 是一个函数,用于在一个 sotore state 中知道特定的信息。
当 app 越来越大,它们可以当 app 的不同部分需要读取相同的数据,帮助避免重复的逻辑。
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
12. Redux Application Data Flow
前面,我们说到“单向数据流”,用来描述更新 app 的一系列步骤
- State 描述 app 在一个特定点的情况
- UI 根据 state 渲染
- 当有什么事情发生的时候(比如点击一个按钮),state 根据发生的事情更新
- UI 根据新 state 重新渲染
对于 Redux,我们可以将这些步骤分解为更多细节
- 初始设置
- 使用一个 root reducer function 创建一个 Redux store
- store 立即调用 root reducer 并保存它返回的值作为初始
state
- 当 UI 首次更新,UI 组件得到 Redux store 中当前的 state,并用那数据决定渲染什么。它们并且订阅未来 store 的更新来知道 state 是否已经变化
- 更新
- app 中有东西发生了,比如点击了一个按钮
- app 代码 dispatch 一个 action 给 Redux store,比如
dispatch({type: 'counter/increment'})
- store 携带着先前的
state
和当前的action
再次运行 reducer 函数,然后保存返回的值作为新的state
- 当 store 更新了,store 通知所有订阅过 UI 部分
- 每个需要数据的 UI 组件检查它们需要的那部分 state 是否变化了
- 每个发现数据变化的组件会强制使用新的数据重新渲染
二、Redux DevTools
安装 Redux DevTools 插件,面板如下图所示:
- 面板左侧是 action 触发记录
- 面板右侧可以查看详细信息
右侧面板中:
- “Action” 可以查看 Action 的 type 和 payload
- “State” 可以查看 State
- “Diff” 可以查看 Action 触发后 State 的前后变化
- “Trace” 可以查看可以查看到 Action 触发的函数堆栈源头
「@浪里淘沙的小法师」