Redux,React-redux。基础
状态管理库,集中式存储状态,管理状态
✅ redux
//简单实现 redux源码
export function createStore(reducer) {
// reducer由用户编写, 必须是一个函数,dispatch的时候,reducer要执行
if (typeof reducer !== 'function') throw new Error('reducer必须是一个函数')
let state // 公共状态
let listeners = [] //事件池
// 1.获取公共状态 store.getState()
const getState = () => {
return state
}
// 2.组件更新方法 进入事件池
const subscribe = listener => {
if (typeof listener !== 'function') throw new Error('listener 必须是一个函数')
// 去重,防止重复添加
if (!listeners.includes(listener)) {
// 让组件更新的方法进入事件池
listeners.push(listener)
}
// 返回一个函数,用于从事件池中删除对应的方法 (取消订阅)
return function unsubscribe() {
// 找到事件池中的对应方法,并删除
listeners = listeners.filter(l => l !== listener)
}
}
// 3.派发任务通知 Reducer 执行
const dispatch = action => {
// 必须是对象
if (typeof action !== 'object') throw new Error('action 必须是一个对象')
// 必须有 type 属性
if (typeof action.type === 'undefined') throw new Error('action 必须有 type 属性')
// 执行 reducer,参数为当前 state 和 action动作,返回新的 state
const nextState = reducer(state, action)
// 更新 公共状态
state = nextState
// 通知事件池中的方法,更新组件
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
// 4.redux初始化state
dispatch({ type: '@@redux/INIT' })
// 5. 返回 store 对象 { getState, dispatch, subscribe }
return {
getState,
dispatch,
subscribe
}
}
1. 重要概念
状态是只读的,
后续修改状态的方式是通过Reducer(纯函数)接收旧状态和 Action,返回新状态。
Reducer无副作用,同样的输入必然得到同样的输出。
为什么reducer 不能直接更改状态?
- redux内部通过比较新旧状态的引用 来判断状态是否变化,接修改原状态,新旧状态的引用相同,Redux 会错误地认为状态未更新,致组件不重新渲染。
- 可预测性:
直接修改状态 会导致变更分散在代码各处,难以追踪变化的来源。
通过强制所有变更都集中在 reducer 中,使状态变化更透明、可维护。
2. 数据流
- 组件通过 dispatch(action) 发送 Action。
- Redux 调用对应的 Reducer,生成新状态。
- Store 更新状态,通知所有订阅了 Store 的组件。
- 组件通过 getState() 获取新状态并重新渲染。
⚠️⚠️ Redux 的监听机制 问题
- Redux 的 Store 维护一个订阅者列表
listeners= []
,每次调用 dispatch(action) 时: - Reducer 生成新状态(newState)。
- Store 用 newState 替换当前状态。
- 遍历执行所有订阅者(如 store.subscribe(listener) 注册的回调)。
这意味着,无论状态树中的哪一部分被修改,所有订阅者都会收到通知
更改了 a状态,未使用a状态,使用了b状态的组件也会重新渲染。
解决: 使用 React-Redux 的 useSelector 或 connect
React-Redux :
其核心设计是 「结构性共享」(仅更新变化部分的引用,未变化部分保留原引用)
通过 浅比较选择的状态引用,仅更新 引用变化的订阅组件,避免无关组件渲染。
问:Store 更新状态,如何通知组件更新?
通过Store内部的Subscription(listener)
方法,listener 通常是 触发组件重新渲染的函数,或是 与组件更新相关的副作用逻辑。见下:
- 类组件:listener 通常是调用 this.forceUpdate() 的函数,强制组件重新渲染。
class Counter extends React.Component {
componentDidMount() {
// 定义 listener:强制组件重新渲染
this.unsubscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
this.unsubscribe(); // 取消订阅
}
render() {
const count = store.getState().count;
return <div>{count}</div>;
}
}
- 函数组件:listener 是 useState 的 副作用函数
import React, { useState, useEffect } from 'react';
import store from './store';
function Counter() {
// 定义forceUpdate 单纯是为了触发组件渲染更新,属性无意义
const [_, forceUpdate] = useState(0);
useEffect(() => {
// 定义 listener:强制组件重新渲染
const unsubscribe = store.subscribe(() => {
forceUpdate(prev => prev + 1);
});
return unsubscribe; // useEffect 清理函数中取消订阅
}, []);
const count = store.getState().count;
return <div>{count}</div>;
}
- 使用 react-redux 的
connect
: connect 高阶组件内部会处理订阅逻辑,listener 是比较新旧 props 并决定是否更新组件的函数。
connect 是 高阶函数,内部会将公共状态以props的方式传递给组件。
import { connect } from 'react-redux';
class Counter extends React.Component {
render() {
return <div>{this.props.count}</div>;
}
}
// 映射状态到 props
const mapStateToProps = (state) => ({
count: state.count,
});
// connect 内部逻辑(简化):
// 1. Store的监听器会比较新旧 mapStateToProps 的结果。
// 2. 若结果变化,则触发组件更新。
export default connect(mapStateToProps)(Counter);
- react-redux 的 useSelector钩子
useSelector 的 listener 是 检查选择器返回值是否变化,并触发重新渲染 的函数。
import { useSelector } from 'react-redux';
function Counter() {
// 内部实现(简化):
// 1. 订阅 Store,监听器会比较新旧 selector(state) 的结果。
// 2. 若结果变化,则触发组件重新渲染。
const count = useSelector((state) => state.count);
return <div>{count}</div>;
}
❤️❤️❤️ React-Redux
对redux 进行了包装。
<Provider>
组件:Redux Store 注入整个 React 应用,使所有子组件可以访问 Store。usSelector
Hook:从 Store 中获取状态,并自动订阅状态更新,当状态变化时,若返回的值与之前不同,组件会重新渲染。useDispatch
Hook:获取 dispatch 方法,派发 Actionconnect
高阶组件(类组件兼容):。
import { connect } from 'react-redux';
import { increment, decrement } from './actions';
class Counter extends React.Component {
render() {
const { count, increment, decrement } = this.props;
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
}
// 映射 State 到 Counter的 props,以供取值
const mapStateToProps = (state) => ({
count: state.count,
});
// 映射 dispatch 到 Counter的 props,以供调用
const mapDispatchToProps = {
increment,
decrement,
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
connect :同 useSelector作用一样,为了访问 redux的store
但是这个比较绕一点(兼容了类组件)
- 输入:一个 React 组件(类 or FC)
- 输出:一个新的组件,该组件能访问 Redux store
// 模拟 react-redux 的 connect 函数
const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
return class Connect extends React.Component {
static contextType = ReactReduxContext; // 从 Provider 获取 store
constructor(props, context) {
super(props);
this.store = context.store;
this.state = {
mappedProps: this.calculateProps()
};
}
componentDidMount() {
this.unsubscribe = this.store.subscribe(() => {
const newMappedProps = this.calculateProps();
// 浅比较优化,避免不必要的渲染
if (!shallowEqual(this.state.mappedProps, newMappedProps)) {
this.setState({ mappedProps: newMappedProps });
}
});
}
componentWillUnmount() {
this.unsubscribe();
}
calculateProps() {
const state = this.store.getState();
const stateProps = mapStateToProps(state);
const dispatchProps = mapDispatchToProps(this.store.dispatch);
return { ...stateProps, ...dispatchProps };
}
render() {
return <WrappedComponent {...this.props} {...this.state.mappedProps} />;
}
};
};
combineReducers
组合多个独立 reducer 的核心工具函数
- 自动分发 Action:
当一个 action 被 dispatch 时,combineReducers 会 自动将该 action 传递给所有子 reducer
import { combineReducers } from 'redux'
import FatherAndSonReducer from './FatherAndSonReducer'
import TaskReducer from './TaskReducer'
const rootReducer = combineReducers({
FatherAndSonReducer,
TaskReducer
})
export default rootReducer