前言
react 和redux 并没有什么直接的联系. redux 作为一个通用模块,主要还是用来处理应用中的state的变更,而展示层不一定是react .
但当我们希望在React + Redux的项目中将两者结合的更好,可以通过react-redux 做连接。
本文结合react-redux的使用,分析其实现原理。
react-redux
react-redux 是一个轻量级的封装库,核心方法只有两个:
下面我们来逐个分析其作用
Provider
完整源码请戳这里
Provider模块的功能并不复杂,主要分为以下两点:
import { Component,Children } from 'react'
import PropTypes from 'prop-types'
import { storeShape,subscriptionShape } from '../utils/PropTypes'
import warning from '../utils/warning'
let didWarnAboutReceivingStore = false
function warnAboutReceivingStore() {
if (didWarnAboutReceivingStore) {
return
}
didWarnAboutReceivingStore = true
}
export function createProvider(storeKey = 'store',subKey) {
const subscriptionKey = subKey || `${storeKey}Subscription`
class Provider extends Component {
getChildContext() {
return { [storeKey]: this[storeKey],[subscriptionKey]: null }
}
constructor(props,context) {
super(props,context)
this[storeKey] = props.store;
}
render() {
return Children.only(this.props.children)
}
}
if (process.env.NODE_ENV !== 'production') {
Provider.prototype.componentWillReceiveProps = function (nextProps) {
if (this[storeKey] !== nextProps.store) {
warnAboutReceivingStore()
}
}
}
return Provider
}
export default createProvider()
1.1 封装原应用
render方法中,渲染了其子级元素,使整个应用成为Provider的子组件.
this.props.children 是react内置在this.props 上的对象,用于获取当前组件的所有子组件.
Children 为react内部定义的顶级对象,该对象封装了一些方便操作字组件的方法. Children.only 用于获取仅有的一个子组件, 没有或者超过一个均会报错. 所以注意: 确保Provider组件的直接子级为单个封闭元素,切勿多个组件平行放置
1.2 传递store
constructor方法: Provider初始化时,获取到props中的store对象;
getChildContext方法: 将外部的store 对象放入context 对象中,使子孙组件上的connect 可以直接访问到context 对象中的store。
context 可以使子孙组件直接获取父级组件中的数据或方法,而无需一层一层通过props向下传递。context 对象相当于一个独立的空间,父组件通过getChildContext()向该空间内写值;定义了contextTypes 验证的子孙组件可以通过this.context.xxx ,从context 对象中读取xxx 字段的值
1.3 小结
总而言之,Provider 模块的功能很简单,从最外部封装了整个应用,并向connect 模块传递store 。 而最核心的功能在connect 模块中。
connect
正如这个模块的命名,connect 模块才是真正连接了React 和Redux 。
现在,我们可以先回想一下Redux是怎样运作的:首先需要注册一个全局唯一的store对象,用来维护整个应用的state;当要变更state时,我们会dispatch一个action,reducer根据action更新相应的state。
下面我们再考虑一下使用react-redux时,我们做了什么:
import React from "react"
import ReactDOM from "react-dom"
import { bindActionCreators } from "redux"
import {connect} from "react-redux"
class xxxComponent extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
this.props.aActions.xxx1();
}
render (
<div>
{this.props.$$aProps}
</div>
)
}
export default connect(
state => ({
$$aProps: state.$$aProps,$$bProps: state.$$bProps,// ...
}),dispatch => ({
aActions: bindActionCreators(AActions,dispatch),bActions: bindActionCreators(BActions,// ...
})
)(xxxComponent)
由export的component对象进行如下猜想: 1、使用了react-redux 的connect 后,我们导出的对象不再是原先定义的xxx Component ,而是通过connect 包裹后的新React.Component 对象。
connect 执行后返回一个函数(wrapWithConnect),那么其内部势必形成了闭包。而wrapWithConnect 执行后,必须要返回一个ReactComponent 对象,才能保证原代码逻辑可以正常运行,而这个ReactComponent 对象通过render 原组件,形成对原组件的封装。 2、渲染页面需要store tree 中的state 片段,变更state 需要dispatch 一个action ,而这两部分,都是从this.props 获取。故在我们调用connect 时,作为参数传入的state 和action ,便在connect 内部进行合并,通过props的方式传递给包裹后的ReactComponent 。 好了,以上只是我们的猜测,下面看具体实现,完整代码请戳这里.
connect(
mapStateToProps(state,ownProps) => stateProps: object,mapDispatchToProps(dispatch,ownProps) => dispatchProps: object,mergeProps(stateProps,dispatchProps,ownProps) => props: Object,options: object
) => (
component
) => component
再来看下connect函数体结构,我们摘取核心步骤进行描述:
export default function connect(mapStateToProps,mapDispatchToProps,mergeProps,options = {}) {
// 参数处理
// ...
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props,context) {
super(props,context)
this.store = props.store || context.store;
const storeState = this.store.getState()
this.state = { storeState }
}
// 周期方法及操作方法
// ...
render(){
this.renderedElement = createElement(WrappedComponent,this.mergedProps //mearge stateProps,props
)
return this.renderedElement;
}
}
return hoistStatics(Connect,WrappedComponent);
}
}
其实已经基本印证了我们的猜测: 1、connect 通过context 获取Provider 中的store ,通过store.getState() 获取整个store tree 上所有state 。 2、connect 模块的返回值wrapWithConnect 为function 。 3、wrapWithConnect 返回一个ReactComponent 对象Connect ,Connect 重新render 外部传入的原组件WrappedComponent ,并把connect 中传入的mapStateToProps ,mapDispatchToProps 与组件上原有的props 合并后,通过属性的方式传给WrappedComponent 。 下面我们结合代码进行分析一下每个函数的意义。
mapStateToProps
mapStateToProps(state,props) 必须是一个函数. 参数state 为store tree 中所有state,参数props 为通过组件Connect 传入的props . 返回值表示需要merge 进props 中的state .
mapDispatchToProps
mapDispatchToProps(dispatch,props) 可以是一个函数,也可以是一个对象. 参数dispatch 为store.dispatch 函数,参数props 为通过组件Connect 传入的props . 返回值表示需要merge 进props 中的action .
mergeProps(一般不用)
mergeProps 是一个函数,定义了mapState ,mapDispatch 及this.props 的合并规则.
options(一般不用)
options 是一个对象,包含pure 和withRef 两个属性
pure : 表示是否开启pure 优化,默认值为true.
withRef : withRef 用来给包装在里面的组件一个ref ,可以通过getWrappedInstance 方法来获取这个ref,默认为false。
React如何响应Store变化
文章一开始我们也提到React其实跟Redux没有直接联系,也就是说,Redux中dispatch触发store中state变化,并不会导致React重新渲染. react-redux才是真正触发React重新渲染的模块,那么这一过程怎样实现的呢? 刚刚提到connect模块返回一个wrapConnect函数,此函数中又返回了一个Connect组件. Connect组件的功能有以下两点:
包装组件,将state和action通过props的方式传入到原组件内部
监听store tree变化,使其包装的原组件可以响应state变化 下面我们主要分析下第二点
如何注册监听
在redux中,可以通过store.subscribe(listener)注册一个监听器.listener会在store tree更新后执行.以下代码为Connect组件内部,向store tree注册listener的过程。
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.onStateChange)
: this.store.subscribe(this.onStateChange)
this.listeners = createListenerCollection()
}
}
何时注册
componentDidMount() {
...
this.subscription.trySubscribe()
...
}
可以看到,当Connect组件加载到页面后,当前组件开始监听store tree变化
何时注销
当前Connect组件销毁后,我们希望其中注册的listener也一并销毁,避免性能问题。此时可以在Connect的componentWillUnmount周期函数中执行这一过程。
componentWillUnmount() {
if (this.subscription) this.subscription.tryUnsubscribe()
...
}
变更处理逻辑
有了触发组件更新的时机,我们下面主要看下,组件是通过何种方式触发重新渲染
onStateChange() {
...
if (!this.selector.shouldComponentUpdate) {
...
} else {
...
this.setState(dummyState) // dummyState = {},仅仅是为了触发更新
}
}
小结
可以看到,react-redux的核心功能都在connect模块中,理解好这个模块,有助于我们更好的使用react-redux处理业务问题,优化代码性能。 (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|