Appearance
ReactRedux
Redux 是一个状态管理库,提供了可以预测、可以回溯的状态管理方案。但是其不跟 React 绑定,所以使用 Redux 的时候我们需要通过 react-redux 来将 Redux 和 React 绑定起来。
说明
React-Reudx 是一个连接 redux 和 react 组件的中间插件,对于 redux 其只是提供了一个数据管理,订阅者更新通知的能力,我们在 React 中去使用 Redux 还存在很多问题。 如以下几个问题
- 在 React 中究竟在哪里去存储 store 的数据?
- 当 store 的数据发生了更新的时候 怎么通知对应的组件进行更新?
- store 中只有某一个数据发生了改变的情况下,订阅了当前 store,但是不关心这个数据的组件是否也要更新?
原理
- 遵循 Flux 思想
- 发布订阅模式
- 遵从函数式编程
- 外部存储
优点
- 单一数据源 :Redux 提供了单一的 Store 来管理整个应用的状态,使得状态管理变得更加统一和可控。
- 可预测性 :由于 Redux 遵循严格的数据流规则,状态的变化变得可预测,容易追踪和调试。
- 可插拔的中间件 :Redux 支持中间件机制,可以方便地添加各种功能,例如异步操作、日志记录、时间旅行等。
- 易于测试 :由于 Redux 中的数据和逻辑都是纯函数,因此可以更容易地进行单元测试和集成测试。
- 组件解耦 :通过 connect 函数将组件与 Redux Store 连接,可以将状态管理逻辑与组件逻辑分离,提高了组件的复用性和可维护性。
缺点
- 学习曲线 :Redux 有一定的学习曲线,特别是对于初学者来说,理解 Redux 的概念和使用方式可能需要一定的时间和精力。
- 繁琐的模板代码 :Redux 在实现上需要编写大量的模板代码,例如定义 action 类型、编写 action 创建函数、编写 reducer 等,这可能会增加代码量和复杂度。
- 过度使用 :有时候为了状态管理而引入 Redux 可能会显得过度,特别是对于较小规模的应用或简单的组件来说,使用 Redux 可能会增加不必要的复杂性。
React-Redux 就借助于 React 的能力, 如 通过 Provider 去维护整个 Redux 的 store 的数据,并使得所有的子孙组件可以通过 react.useContext 来获取到 Redux 的 store 中的数据;借助于 useState 在数据更新触发回调的时候强制当前组件刷新从而获取最新的数据等。
通过 connect 来将 React 组件绑定到 Redux 的 store 中,这样就可以在 React 组件中获取到 Redux 的 state 以及触发 Redux 的 action。 另外提供了一系列的 hooks , 如
useSelector: 用于在 React 组件中获取 Redux 的 stateuseDispatch: 用于在 React 组件中触发 Redux 的 actionuseStore: 用于在 React 组件中获取 Redux 的 store
其核心就是 两个 ,一个是 Provider ,一个是 connect 。下面我就详细的介绍一下这两个。
Provider
首先我们需要先简单知道 Provier 的作用是什么?
- 其借助于
<Context.Provider></Context.Provider>的能力向后代组件传递了一个关键的信息store、subscription(发布订阅者对象) - 其通过
const subscription = new Subscription(store)来创建了一个发布订阅者模式的对象,这样所有后代(connect、 useSelector 等)中通过subscription.addNestedSub()订阅了当前 store 的变化的时候就可以通过subscription.notifyNestedSubs()来通知其更新(这个更新不一定发生)
然后其对于 Redux 中的问题进行了那些优化和处理
- 只更新订阅了的 connect
在 redux 中我们知道 state 整体是一个对象,无法去只关注 state 中某一个节点数据的变化,所以当我们修改了 state 的其他内容的时候,其也会发生更新,
但是在 react-redux 中, 其通过虽然其核心也是通过发布订阅者的方式去通知所有订阅了当前 store 的后代组件更新,但是其在 connect 中通过 selector 的作为中间过滤,从而实现了只在自己订阅的 state 发生了更新的时候才会进行更新, 具体怎么实现看下文。
所以 Provider 的核心就是这两点。
下面我们看一下源码:
js
function Provider<A extends Action = AnyAction, S = unknown>({
store, // Redux store 实例,包含应用的状态和 dispatch 方法
context, // 可选的上下文对象,用于自定义 React Redux 的上下文
children, // 应用的子组件,将通过 Context.Provider 接收 store
serverState, // 服务端渲染时的初始状态
}: ProviderProps<A, S>) {
// 使用useMemo缓存上下文值,避免每次渲染重新创建
const contextValue = useMemo(() => {
// 创建Redux订阅实例,用于监听store状态变化
const subscription = createSubscription(store)
return {
// 存放了 store 的数据
store, // Redux store实例
subscription, // 订阅管理器实例
getServerState: serverState ? () => serverState : undefined, // 服务端渲染时获取初始状态的方法
}
}, [store, serverState]) // 依赖:store实例和serverState变化时重新计算
// 缓存初始状态,用于后续比较状态是否变化
const previousState = useMemo(() => store.getState(), [store])
// 使用同构布局effect处理订阅逻辑(SSR兼容)
useIsomorphicLayoutEffect(() => {
const { subscription } = contextValue
// 设置状态变化回调为通知嵌套订阅者
subscription.onStateChange = subscription.notifyNestedSubs
// 尝试订阅store变化
subscription.trySubscribe()
// 如果初始状态已变化,立即通知订阅者
if (previousState !== store.getState()) {
subscription.notifyNestedSubs()
}
// 清理函数:取消订阅并重置回调
return () => {
subscription.tryUnsubscribe()
subscription.onStateChange = undefined
}
}, [contextValue, previousState]) // 依赖:上下文值和初始状态
// 确定使用的上下文(用户自定义或默认ReactReduxContext)
const Context = context || ReactReduxContext
// @ts-ignore 'AnyAction' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype
// 使用Context.Provider提供Redux上下文,使子组件可访问store
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}核心就是 contextValue 这个上下对象, 其主要有两个属性
- store : 其就是我们传入的 Redux 的 store 实例
- subscription : 其就是我们通过
const subscription = new Subscription(store)来创建的发布订阅者模式的对象
这是一个非常重要的对象,下面我们就看一下这个方法
createSubscription(store: any, parentSub?: Subscription)
js
export function createSubscription(store: any, parentSub?: Subscription) {
// 存储取消订阅的函数,用于后续清理订阅
let unsubscribe: VoidFunc | undefined;
// 监听器集合,管理所有订阅的回调函数
let listeners: ListenerCollection = nullListeners;
/**
* 添加嵌套订阅者
* 订阅者通过此函数将自己的回调函数添加到订阅列表,以便在状态变化时触发
* @param {() => void} listener - 状态变化时触发的回调函数
* @returns {() => void} 取消该订阅的函数
*/
function addNestedSub(listener: () => void) {
trySubscribe(); // 确保已订阅store变化
// 添加监听器并返回取消订阅函数
return listeners.subscribe(listener);
}
/**
* 通知所有嵌套订阅者状态已变化
*/
function notifyNestedSubs() {
listeners.notify(); // 触发所有监听器的回调函数
}
/**
* 状态变化处理的包装函数
* 作用:触发当前订阅的状态变化回调
*/
function handleChangeWrapper() {
if (subscription.onStateChange) {
subscription.onStateChange(); // 如果设置了状态变化回调,则执行
}
}
/**
* 检查是否已订阅状态变化
* @returns {boolean} 是否处于订阅状态
*/
function isSubscribed() {
return Boolean(unsubscribe); // 通过是否存在取消订阅函数判断订阅状态
}
/**
* 尝试订阅store状态变化
* 作用:如果尚未订阅,则创建订阅并初始化监听器集合
*/
function trySubscribe() {
if (!unsubscribe) {
// 如果有父订阅则添加为嵌套订阅,否则直接订阅store
unsubscribe = parentSub
? parentSub.addNestedSub(handleChangeWrapper)
: store.subscribe(handleChangeWrapper);
// 初始化 listeners
listeners = createListenerCollection(); // 初始化监听器集合
}
}
/**
* 尝试取消订阅
* 作用:清理订阅关系和监听器集合
*/
function tryUnsubscribe() {
if (unsubscribe) {
unsubscribe(); // 执行取消订阅
unsubscribe = undefined; // 清除取消订阅函数引用
listeners.clear(); // 清空监听器集合
listeners = nullListeners; // 重置为默认空监听器
}
}
// 创建订阅管理器对象,暴露订阅管理相关方法
const subscription: Subscription = {
addNestedSub, // 添加嵌套订阅者
notifyNestedSubs, // 通知所有嵌套订阅者
handleChangeWrapper, // 状态变化处理包装函数
isSubscribed, // 检查订阅状态
trySubscribe, // 尝试订阅, 主要是进行发布订阅的初始化工作,和 进行父sub的订阅 或者 store的订阅
tryUnsubscribe, // 尝试取消订阅
getListeners: () => listeners, // 获取当前监听器集合
};
return subscription;
}正常的发布订阅者模式其维护所有订阅者的结构是一个数组的结构( let listeners = []), 但是这个部分其不是
