Redux技术原理
1. 什么是Redux
Redux是一个通过叫做action的事件,管理和更新应用程序状态的js库或者说是一种模式。使用Redux
可以使我们更容易地理解应用程序中的状态或者说数据何时,何地,为什么被更新,以及这种更新所
带来的行为。
2. 使用Redux的场景
- 应用程序中具有大量的数据状态分布在程序的多个地方
- 数据状态被频繁的更新
- 更新逻辑非常复杂
- 具有大规模的代码被多人同时开发和维护
3. Redux相关的工具和库
- React-Redux:提供React组件与Redux store进行交互的方式。
- Redux Toolkit: 提供编写Redux逻辑的模式和工具库,简化开发任务,Redux开发最佳实践。
- Redux DevTools Extension: 提供调试Redux的方法。
4. Redux Store
每一个Redux应用的中心是store, 它保存了应用的所有数据状态。store的本质是一个javascript对象,
具有一些特殊的方法和能力使得它与众不同。
- 不能直接修改保存在store中的数据状态
- 唯一引起store中的state变化的是action对象,它描述了要发生什么行为,创建action对象然后
将它分发给store, 告诉它发生的事件。 - 当一个action被分发后,store会调用reducer函数,让它基于action以及旧的state,计算出新的state。
- 最后store会通知订阅者状态已经更新,最后UI会根据最新的数据重新渲染。
const initialState = {
value: 0
};
const reducers = {
'counter/increment': (state, payload) => {
return {
...state,
value: state.value + 1
};
},
'counter/decrement': (state, payload) => {
return {
...state,
value: state.value - 1
};
}
};
const reducers = (state=initialState, action) => {
const { type, payload, rest } = action;
const reducer = reducers[type];
return reducer ? reducer(state, payload, rest) : state;
};
// 根据reducer创建store
const store = Redux.createStore(reducers);
// 处理用户点击事件,分发action对象
document.getElementById('increment').addEventListener('click', function () {
store.dispatch({ type: 'counter/incremented' })
})
document.getElementById('decrement').addEventListener('click', function () {
store.dispatch({ type: 'counter/decremented' })
})
// 当store中数据更新时调用订阅函数执行UI更新逻辑
store.subscribe(() => {
const state = store.getState();
const valueElment = document.getElemmentById('value');
valueElment.innerHTML = state.value.toString();
});
5. Redux 术语
- Actions
一个action就是一个具有type属性的普通javascript对象,你可以将它看作描述发生在应用程序中的
事件。type属性应当是一个描述性的名称,'domain/eventName', 第一部分表明action属于哪个功能
模块,第二部分指定一个发生事件名称。除了type属性,Actioin也可以包含其他字段,作为惯例可以
将其他信息存储在payload属性当中。
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
- Reducers
一个reducer就是一个函数接受当前的state和action, 然后决定是否有必要更新state,然后返回新的
state。你可以将它看作一个事件监听器,根据接收到的action (event) type,进行事件处理。
reducer 应该遵循下列规则:
- 应该根据当前state 和 action,计算新的state。
- 不允许修改当前state,应该复制当前的state,然后修改副本。
- 不应当进行异步操作或者产生随机数等有副作用的逻辑。
reducer 工作逻辑:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/incremented') {
// 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
}
- Store
当前应用程序的state,保存在一个叫Store的object中,它根据传入的reducer创建并具有
返回当前state的接口getState。
import { createStore } from 'redux'
const store = createStore(reducers);
console.log(store.getState())
- Dispatch
修改Store的唯一方式就是调用store.dispatch(action) 方法, store然后会调用reducer,保存新产生
的state数据。你可以将发送action理解为触发一个事件。
store.dispatch({ type: 'counter/incremented' })
console.log(store.getState()) // {value: 1}
- Selectors
Selectors 就是从store中选取指定state的函数,它避免了应用程序不同部分读取store中相同state的
代码逻辑重复。
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue) // 2
6. Redux的数据流及原则
数据流:
- state 描述应用程序在特定时间点的状态
- UI 基于此state进行渲染
- 发生用户事件时,基于action 的type对state进行更新
- UI基于最新的state重新渲染
原则: - 单一数据源
- 只读state
- 通过reducer纯函数进行state更新
7. Redux应用工作流程
- 初始设置
- 使用root reducer创建store
- store调用一次reducer产生初始state并保存
- UI第一次渲染时获取初始化的state,并使用这些数据决定渲染哪些部分,UI同时订阅了store的更新,
因此UI能感知到store中state的变化。
- 更新
- 应用中发生交互事件(点击事件)
- 应用派发一个action到Redux的store (dispatch({type: 'counter/incremented'}))
- store使用当前的state和action执行reducer函数,产生新的state并保存。
- store通知所有的订阅store更新的UI组件
- UI组件检测自己使用的那部分state数据是否发生更新
- UI组件使用新数据强制重新渲染
8. Redux store 内部实现
- Store内部保存有应用当前的状态值以及reducer函数
- getState方法返回当前应用状态
- subscribe方法将监听器回调函数存入数组,并返回unsubscribe 函数取消监听
- dispatch方法调用reducer 函数,并保存新生成的state值,并执行监听函数
- store自己发送一个action,并调用reducer初始化state
- store是一个具有dispatch, subscribe, getState接口方法的对象
function createStore(reducer, preloadedState) {
let state = preloadedState
const listeners = []
function getState() {
return state
}
function subscribe(listener) {
listeners.push(listener)
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
dispatch({ type: '@@redux/INIT' })
return { dispatch, subscribe, getState }
}