connect函数原理:
我们将通过自己实现一下connect函数,来学习一下connect函数的底层实现逻辑。
step1
首先通过观察三方库react-redux提供的connect函数,调用时传递了两个参数,返回值又是一个高阶组件,调用该高阶组件将我们自己的组件传递进去。
那么我们先创建一个这样函数,
在src中创建一个hoc文件夹,在hoc中创建一个connect.js文件
该文件中定义一个 myConnect 函数,接收两个参数。
返回值是一个高阶组件(即:函数)第12行 。 据高阶组件的定义:对传入的组件做拦截处理加工,要求入参是个组件,返回值也是一个组件。 所以13行定义了一个新组件NewComponent, 在这个新组件对传入的WrapperComponent进行拦截渲染操作。
在思考一下react-redux库提供的connect函数作用是什么?
正如他的字面意思建立连接,就是将store中的存储的值 通过 props的形式传递给组件,从而将组件和store产生关联。
所以我们17行代码渲染WrapperComponent时要将store中的值传递给组件。 我们通过调用外面传入的mapStateToProps(store.getState()) 和 mapDispatchToProps(store.dispatch) 获取到Store中要共享出去的值 和 能操作dispatch的自定义方法。 将这两者通过props传递给WrapperComponent组件。
还要注意的是:第9行的函数执行完,返回的是第12行定义的这个高级组件(函数),该高阶组件调用时return的是NewComponent这个组件,所以外界真实拿到的是一个内部帮忙渲染了WrapperComponent组件的NewComponent组件,看似开发中在使用WrapperComponent组件,然后给WrapperComponent组件传递props。 其实实际是在使用NewComponent组件,给NewComponent组件传递props。 WrapperComponent组件中并不能拿到外界传递进来的props,所以需要将this.props传递给WrapperComponent组件。 所以17行传递了 this.props、stateObj、dispatchObj。
step2
上面基本完成了connect的实现,但是此时的connect还不能做到当store中的数据改变时,自动刷新组件,需要自己去监听store中数据的变化。通过store.subscribe去订阅,当store中发生修改数据的行为时(不管改的值是不是和上次的值一样,都会触发订阅回调函数)
step3
step2中数据变化时,直接全部强制render性能不好,我们应该只当 修改的值与上次不一样时,才进行render,如果修改的值与上次的一样时,不进行render。
第15行: 用store中共享出来的值,给当前组件state赋值
第24行:通过setState函数,修改组件的State值。 这样就不会当修改的值和上次的值一样时,也去render组件了,因为该组件 继承自 PureComponent , 是做了这个优化的。
对connect中依赖的store进行解耦:
上面我们写的connect.js文件中,对store有强耦合,写死了导入路径。别人使用我们的connect.js 时,就不一定找的到 store了。
我们可以提供一个StoreContext.js文件,在其中创建一个StoreContext用于将store共享到我们自己写的connect中
同时再写一个index.js文件,将connect和StoreContext 统一导出供外界方便访问
用自定义的StoreContext对store进行共享,这样就可以在connect.js文件中获取到了,就不用直接导入store了。
connect.js 代码如下:
// connect函数的参数:
// 参数一: 函数
// 参数二: 函数
// 返回值: 函数 (高阶组件)
import { PureComponent } from "react";
// import { store } from '../store'
import { StoreContext } from "./index";
export function myConnect(mapStateToProps, mapDispatchToProps) {
//返回高阶组件:(本质就是函数)
return function(WrapperComponent){
class NewComponent extends PureComponent {
// 用store中共享出来的值,初始化组件state
constructor(props, context){
super(props)
// this.state = mapStateToProps(store.getState())
this.state = mapStateToProps(context.getState())
}
// 需要自己调用store.subscribe对数据进行监听,有数据就刷新一下页面
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
// this.forceUpdate()
this.setState(mapStateToProps(this.context.getState()))
})
}
// 取消监听
componentWillUnmount() {
this.unsubscribe()
}
render() {
const stateObj = mapStateToProps(this.context.getState())
const dispatchObj = mapDispatchToProps(this.context.dispatch)
return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj}/>
}
}
NewComponent.contextType = StoreContext
return NewComponent
}
}
第6行: 不再直接导入store,避免强耦合
第7行: 导入自己创建的StoreContext
第41行:给组件设置了contextType = StoreContext
第16行: 构造函数,默认会将context传入的,因为我们给组件设置了contextType = StoreContext,所以这里传入的context就是共享出来的store。 19行的写法就可以代替18行的写法了
第24行、26行、35行、36行,因组件本身就有context属性,并且我们给组件设置了contextType = StoreContext,所以可以直接this.context获取到共享出来的store。