在学习 redux react-redux 模拟实现过程中, 为了方便派发 dispatch 使react-redux 接受一个 mapDispatchToProps 函数, 内部将 dispatch 和 props 传入并接受调用的返回值, 由使用者自行定义触发的 dispatch 事件, 通过高阶组件的形式再通过 props 流向目标组件。
react-redux 实现如下:
import React from "react";
import PropTypes from "prop-types";
export const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => {
// mapStateToProps 保存在闭包环境中
return class Connect extends React.Component {
static contextTypes = {
store: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
allProps: {}
};
}
componentWillMount() {
const { store } = this.context;
this._updateProps();
// state 已经更新
store.subscribe(() => this._updateProps());
}
_updateProps() {
const { store } = this.context;
// 额外传入 props, 让获取数据更加灵活方便
const stateProps = mapStateToProps?.(store.getState(), this.props) ?? {};
const dispatchProps = mapDispatchToProps?.(store.dispatch, this.props) ?? {};
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render() {
const { store } = this.context;
const stateProps = mapStateToProps(store.getState());
return ;
}
};
};
使用的组件 ThemeSwitch 组件代码如下:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "./util/react-redux";
class ThemeSwitch extends Component {
static contextTypes = {
themeColor: PropTypes.string,
onSwitchColor: PropTypes.func
};
render() {
const { themeColor, onSwitchColor } = this.props;
return (
);
}
}
const mapDispatchToProps = dispatch => {
return {
onSwitchColor(color) {
dispatch({ type: "CHANGE_COLOR", themeColor: color });
}
};
};
const mapStateToProps = store => {
return {
themeColor: store.themeColor
};
};
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch);
export default ThemeSwitch;
运行后报错结果:
Maximum update depth exceeded.
This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
React limits the number of nested updates to prevent infinite loops.
报错结果可以看出,堆栈溢出,因为重复调用某个生命周期钩子 componentWillUpdate 或 componentDidUpdate 导致一直重新渲染。
首先排查了 react-redux 的代码,并且在别的组件都完全正常,所以问题原因就锁定在 ThemeSwitch 组件。
通过在 react-redux 文件的 _updateProps 中 debugger 发现该方法会无限的调用,调用的原因无非第一次 componentWillMount 钩子中对其初始化一次,并且订阅了 store 中 state 的变化,那么之所以重读被调用,肯定是 store.subscribe 内一直发布事件,导致订阅函数一直被更新,所以一直调用 _updateProps 函数。
所以问题锁定在 state 一直被修改 state 数据定义在 redux 模拟实现中。redux 简单的实现如下:
function createStore(reducer) {
let state = null;
// 缓存旧的 state
const listeners = [];
const subscribe = listener => listeners.push(listener);
const getState = () => state;
const dispatch = action => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
dispatch({}); // 初始化 state
return {
getState,
dispatch,
subscribe
};
}
发现 state 更新的唯一途径就是 dispatch 函数的触发,哪里无限调用 dispatch 函数了呢? 最后发现 ThemeSwitch 组件的给按钮绑定监听事件,修改主题色的函数是直接调用的 - -
就导致 dispatch 一直更新。无限循环导致内存泄露的流程:
onSwitchColor方法调用 -> 触发dispatch更新 -> _updateProps触发 -> setState更新状态 -> 更新目标组件Prop -> 目标组件重新 Render -> onSwitchColor方法调用 -> 触发dispatch更新
修改 ThemeSwitch 文件有问题的代码:
return (
);
修改后:
return (
);
完美运行!