Appearance
Redux
导航目录
Redux 是什么
- Redux 是 React 状态管理工具库,可以实现跨组件通信,解决组件间状态共享的问题
一句话速记:单一数据源(一个 store)+ 状态只读(只能通过 dispatch action 修改)+ 纯函数修改(reducer 根据老 state 和 action 返回新 state)。
核心概念:
| 概念 | 作用 |
|---|---|
store | 唯一的数据仓库,保存整棵状态树 |
action | 描述「发生了什么」的普通对象,必须有 type 字段 |
reducer | 纯函数 (state, action) => newState,负责计算新状态 |
dispatch | 派发 action,是修改 state 的唯一途径 |
数据单向流动:dispatch(action) → reducer 计算新 state → 通知订阅者 → 视图更新。
Redux 设计思想
Redux 核心设计思想是发布-订阅模式,通过以下步骤实现:
一句话速记:
createStore内部维护state和listeners数组,对外暴露三件套 ——getState(读)、subscribe(订阅,返回取消订阅函数)、dispatch(改,跑 reducer 后通知所有订阅者)。
- 调用
createStore时,接收两个参数reducer和initState,返回一个 store 实例 - 内部会初始化 state 和 listeners 收集器,向全局暴露出三个核心函数:getState、subscribe、dispatch
- getState:用于外部获取最新的 state
- subscribe:订阅器,将订阅的回调函数存储在 listeners 收集器中,返回一个取消订阅的回调
- dispatch:派发器,执行
reducer获取最新的 state,同时执行所有订阅器
js
export default function createStore(reducer, initState) {
let state = initState;
// 收集器
const listeners = [];
// 获取状态
const getState = () => {
return state;
};
/**
*
* @param {*} listener 订阅的函数
* @returns 取消订阅的函数
*/
const subscribe = (listener) => {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
};
/**
*
* @param {*} action action提交计算新的状态
*/
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
};
dispatch({ type: "@@REDUX/INIT" });
return {
getState,
subscribe,
dispatch,
};
}combineReducers
默认情况下,我们只有一个 Reducer,如果项目比较庞大,所有的数据全部交给一个 Reducer 处理会比较冗余。combineReducers的目的就是将多个模块的 Reducer 进行合并成一个大的 Reducers,实现集中派发。
缺陷:每次触发 dispatch,所有 reducer 都会执行一遍
一句话速记:把「多个小 reducer」合并成「一个大 reducer」,每个小 reducer 只管理 state 树上自己那块(按 key 划分),合并后返回的仍是一个
(state, action) => newState的普通 reducer。合并后每次 dispatch 会遍历所有 reducer 各自计算自己的分片。
js
/**
* 合并多个reducers
* @param {*} reducers
*/
export default function combineReducers(reducers) {
/**
* createStore执行dispatch触发
* @param {*} state 老的state状态
* @param {*} action action动作
*/
return function combination(state = {}, action) {
// 总的state
let nextState = {};
// TODO:缺陷每次触发dispatch,所有reducer都会执行一遍
for (const key in reducers) {
const preState = state[key]; // 老的state状态
nextState[key] = reducers[key](preState, action);
}
return nextState;
};
}React-Redux
React-Redux 是用来连接组件和仓库的库,内部将订阅、解绑、视图更新统一封装,简化用户操作。它由两个核心部分组成:Provider 和 connect
一句话速记:
Provider用 Context 把 store 往下传给所有子组件;connect是高阶组件,负责「取 store → 订阅 → 把 state/dispatch 注入子组件 props → 卸载时取消订阅」。
Provider
Provider 组件基于 React 的 createContext 实现,其目的是将 store 实例存储在 Context 上下文对象中,使所有子组件都能访问到 store
js
import { createContext } from "react";
export const ReactReduxContext = createContext( null );
export default ReactReduxContext;
/**
* 利用Context给子孙组件传递store实例
* @param {*} props store实例
* @returns
*/
export default function Provider ( props ) {
return (
<ReactReduxContext.Provider value={{ store: props.store }}>
{props.children}
</ReactReduxContext.Provider>
)
}connect
connect 是连接组件和仓库的核心,它是一个高阶组件,主要功能包括:
- 从 Context 上下文解析出 store 实例
- 自动进行订阅,当触发 Dispatch 时,会通知订阅器,获取最新的 state,触发视图更新
- 将 props、state 传递给原始组件并返回增强后的组件
js
import { Component } from "react";
import ReactReduxContext from "./ReactReduxContext";
/**
* 连接组件和仓库
* @param {*} mapStateToProps
* @param {*} mapDispatchToProps
* @returns
*/
export default function connect(mapStateToProps, mapDispatchToProps) {
return (OldComponent) => {
return class extends Component {
static contextType = ReactReduxContext; // 注入store实例
constructor(props, context) {
super(props);
// 解析store实例
let {
store: { getState, subscribe, dispatch },
} = context;
this.state = mapStateToProps(getState());
// 订阅,dispatch执行会触发订阅器,触发更新
this.unsubscribe = subscribe(() => {
this.setState(mapStateToProps(getState()));
});
let dispatchProps;
if (typeof mapDispatchToProps === "function") {
// 执行注入dispatch
dispatchProps = mapDispatchToProps(dispatch);
} else {
dispatchProps = {};
// 将action动作,转换成dispatch能触发的动作
for (const key in mapDispatchToProps) {
dispatchProps[key] = () => dispatch(mapDispatchToProps[key]());
}
}
this.dispatchProps = dispatchProps;
}
// 组件销毁时取消订阅
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<OldComponent
{...this.props}
{...this.state}
{...this.dispatchProps}
></OldComponent>
);
}
};
};
}applyMiddleware 中间件处理
applyMiddleware 是 Redux 中用于增强 dispatch 功能的机制,方便开发人员根据需求定制自己的插件。它是一个三层闭包函数:
一句话速记:中间件的本质是层层包装 dispatch。每个中间件形如
({getState, dispatch}) => next => action => {...},通过compose把它们串成一条链,最终得到一个「增强版 dispatch」,调用时会依次经过每个中间件再到达原始 dispatch。
- 第一层接收中间件数组
- 第二层接收 createStore 函数
- 最后一层接收总的 reducer 和初始的 initState
其工作原理:
- 内部会创建 Store 实例
- 创建 middlewareAPI 封存在中间件内部
- 调用每一个中间件,形成调用栈
- 最后返回 store 实例和增强后的 dispatch
js
// BUG:如果在中间件直接调用第一层的dispatch,会造成死循环
export default function logger({ getState, dispatch }) {
return (nextDispatch) => {
return (action) => {
console.log("更新前", getState());
nextDispatch(action);
console.log("更新后", getState());
};
};
}js
import compose from "./compose";
/**
*
* @param {...any} middlewares 中间件
*/
export default function applyMiddleware(...middlewares) {
return (createStore) => {
return (reducer, initState) => {
let dispatch;
// 创建Store实例
const store = createStore(reducer, initState);
// 封存中间件API
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action), // 加强后的dispatch
};
// 揭开中间第一层(封存原始的store)
// BUG:如果在中间件直接调用第一层的dispatch,会造成死循环
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
// 增强后的dispatch(揭开中间第二层)
dispatch = compose(...chain)(store.dispatch);
// 第三层用户去触发
return { ...store, dispatch };
};
};
}compose:函数组合工具
compose 是 applyMiddleware 里把中间件串成链的关键工具。它把多个函数从右到左组合起来:compose(f, g, h)(x) 等价于 f(g(h(x))),从而让每个中间件都能拿到「下一个 dispatch」作为 next。
js
/**
* 从右到左组合多个函数:compose(f, g, h)(x) === f(g(h(x)))
* @param {...Function} funcs 要组合的函数(这里是各中间件返回的 next => action => {} )
*/
export default function compose(...funcs) {
// 没有中间件时,返回一个原样返回参数的函数
if (funcs.length === 0) return (arg) => arg;
// 只有一个时直接返回它
if (funcs.length === 1) return funcs[0];
// 用 reduce 把函数两两嵌套:最终形成 f(g(h(...))) 的调用链
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}记忆要点:
applyMiddleware= 「创建 store → 造一个内部会引用最终 dispatch 的 middlewareAPI → 每个中间件先吃掉 middlewareAPI 得到next => action => {}→ compose 串起来并接上原始store.dispatch→ 覆盖 dispatch 返回」。注意中间件里应调用next(action)往下传,若直接调用最外层dispatch(action)会形成死循环。