参考reac-redux 库 源码
以下实现的功能代码演示地址
官方 react-redux 库,使用
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './reducers/store';
ReactDOM.render(
<Provider store={store}>
<CoutA />
<CoutB />
</Provider>,
document.getElementById('root'),
);
Provider
组件, 传入一个 store
属性, 值是 redux
的 createStore(reducer)
方法的返回值在组件中使用 react-redux
, 需要导出一个 connect
函数, 进行连接组件和仓库
CoutA.js
import { connect } from 'react-redux';
class CoutA extends Component {
render() {
const { add, num } = this.props;
return (
<>
<p>CoutA组件:{num}</p>
<button onClick={add}>CoutA组件++</button>
</>
);
}
}
const mapStateToProps = (state) => state.count1;
const mapDispatchToProps = (dispatch) => ({
add() {
dispatch({type: ADD1});
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
redux
的 createStore(reducer)
创建store对象react-redux
的 Peovider
组件 包裹所有的 react 组件
, 通过 store属性
把 上边的 store 对象
传入react-redux
提供的 connect
方法连接 组件与数据源仓库Provider
组件在src / react-redux文件夹下:
该文件木有啥逻辑, 就是导出 connect
方法, Provider
组件
index.js
import Provider from './Provider';
import connect from './connect';
export { Provider, connect };
context.js
创建react 的上下文对象
import React from 'react';
export default React.createContext(null);
Provider.js
这个组件拿到外部提供的 store属性, 把所有的子组件通过this.props.children
渲染
import React, { PureComponent } from 'react';
import Context from './context';
export default class extends PureComponent {
render() {
// 拿到顶级组件传入的 store 属性
const { store, children } = this.props;
return <Context.Provider value={store}>{children}</Context.Provider>;
}
}
接着看 connect 方法
const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => ({
add() {
dispatch({type: ADD1});
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
这个方法传入俩参数,执行后,又返回一个函数,接着把组件传进去, 可能刚接触的感到挺难理解的,从函数层面上讲呢,算是高阶函数,同时又使用了函数的科里化,从react 上说,这就是个高阶组件,
connect(mapStateToProps, mapDispatchToProps)
,执行完后,它返回一个函数,所以你需要再次调用 传入组件, 可以拆分成以下两步,比较好理解// 1. 先执行第一步,返回一个函数
let connectFn = connect(mapStateToProps, mapDispatchToProps)
// 2. 再次调用这个函数,返回一个包装后的组件(就是高阶组件)
export default connectFn (CoutA)
根据以上梳理逻辑,接着实现这个 connect 方法
// connect(mapStateToProps, mapDispatchToProps)(CoutA)
// 第一层: 导出一个函数接收俩参数
export default function connect(mapStateToProps, mapDispatchToProps) {
// 第二层,返回一个函数,接收组件作为参数,同时最终渲染的还是你传入的这个组件,所以直接在render 返回
return function (WarpComponent) {
return class extends PureComponent {
render() {
return <WarpComponent />;
}
};
};
}
先让包装后的组件拿到状态
通过 react 的 context 上下文对象
export default function connect(mapStateToProps, mapDispatchToProps) {
// 第二层,返回一个函数,接收组件作为参数,同时最终渲染的还是你传入的这个组件,所以直接在render 返回
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
this.state = context.getState();
}
render() {
return <WarpComponent {...state}/>;
}
};
};
}
到这里,你在组件中直接这样调用,就可以通过props 拿到值了
connect()(CoutA)
再看传入connect刚调用时传入的参数格式, 传入了俩函数,俩函数的返回值都是个对象
const mapStateToProps = (state) => state
const mapDispatchToProps = (dispatch) => ({
add() {
dispatch({type: ADD1});
},
});
connect(mapStateToProps, mapDispatchToProps)(CoutA)
处理上边俩参数
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
this.state = context.getState();
}
UNSAFE_componentWillMount() {
// 判断传入的第一个参数是不是函数,用户可能传,或者不传,需要判断处理
if (typeof mapDispatchToProps === 'function') {
// 这里是把 你传入的自定义的方法,绑定到props 上
this.setState({
...this.state,
...mapDispatchToProps(this.context.dispatch),
});
// 把dispacth 当作它的返回值
mapDispatchToProps(this.context.dispatch);
}
if (typeof mapStateToProps === 'function') {
// 把传入的数据替换到 state 上
this.setState({
...this.state,
...mapStateToProps(this.state),
});
// 把 this .state 当作它的返回值
mapStateToProps(this.state);
}
}
render() {
return <WarpComponent {...this.state} />;
}
};
};
}
接着进行订阅数据的变化
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
// .... 略
// 订阅数据
componentDidMount() {
this.unsubscribe = this.context.subscribe(() =>
this.setState(this.context.getState()),
);
}
// 组件销毁前,取消订阅
componentWillUnmount() {
this.unsubscribe();
}
// .... 略
};
};
}
到这已经基本实现了,
希望只获取自己组件依赖的数据,而不关心其它的数据, 可以这样写
const mapStateToProps = (state) => state.count1;
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
但上边的代码是不支持这样的写法,需要进行优化, 既然 第二个参数,是动作,返回的恰好是个对象
可以考虑使用 redux
的 bindActionCreators
这个方法,它刚好适用于这种对象的语法
import React, { PureComponent } from 'react';
import Context from './context';
import { bindActionCreators } from './../redux';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
// 这里 直接执行 mapStateToProps 函数,把默认的 值返回给回调函数
this.state = mapStateToProps ? mapStateToProps(context.getState()) : context.getState();
}
componentDidMount() {
this.unsubscribe = this.context.subscribe(() =>
this.setState(mapStateToProps(this.context.getState())),
);
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
// 使用 bindActionCreators 集中处理 所有的 actions,
let boundActions = bindActionCreators(
mapDispatchToProps(),
this.context.dispatch,
);
return <WarpComponent {...this.state} {...boundActions} />;
}
};
};
}
所以组件中就简写了
import React, { Component } from 'react';
import { ADD1 } from '../reducers/type';
import { connect } from './../react-redux';
class CoutA extends Component {
render() {
const { add, num } = this.props;
return (
<>
<p>CoutA组件:{num}</p>
<button onClick={add}>CoutA组件++</button>
</>
);
}
}
const mapStateToProps = (state) => state.count1;
// 这里不在dispatch 了
const mapDispatchToProps = () => ({
add() {
return { type: ADD1 };
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);
但是使用官方,它是需要在组件中手动dispacth 的, 所以再接着简化,改成官方的那种
import React, { PureComponent } from 'react';
import Context from './context';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
componentDidMount() {
this.unsubscribe = this.context.subscribe(() =>
this.setState(mapStateToProps(this.context.getState())),
);
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { getState, dispatch } = this.context;
return (
<WarpComponent
{...(mapStateToProps && mapStateToProps(getState()))}
{...(mapDispatchToProps && mapDispatchToProps(dispatch))}
/>
);
}
};
};
}
这时组件就需要手动 dispatch
了,但是现在还有个问题, 不管哪个组件的数据变化了,每个组件都会再执行一次render
(演示地址代码,为了重现这个问题, 没有改)
那这就需要处理了,因为原先每次render的时候都新建了函数,所以 PureComponent
浅比较的话, 就是不相等的 , 所以
shoudComponentUpdate
就会返回true,导致每个都会更新
import React, { PureComponent } from 'react';
import Context from './context';
export default function connect(mapStateToProps, mapDispatchToProps) {
return function (WarpComponent) {
return class extends PureComponent {
static contextType = Context;
constructor(props, context) {
super(props);
// 构造函数里定义死
this.boundActions =
mapDispatchToProps && mapDispatchToProps(context.dispatch);
}
render() {
const { getState } = this.context;
return (
<WarpComponent
{...(mapStateToProps && mapStateToProps(getState()))}
{...this.boundActions}
/>
);
}
};
};
}