react-redux思想概览
实际上React和Redux并没有直接的联系,前者负责视图渲染,后者负责状态管理,单独抽出使用都是可以的,但无疑两者结合可以工作的更好。为了使状态和视图更好的连接,就要使用react-redux作为胶水层,其核心方法只有两个,Provider组件和connect方法,下面就分析其原理。
Provider
该组件的作用很简单,就是接受Redux的store,作为中转为子组件提供store的context。源码如下:
export default class Provider extends Component {
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
render() {
return Children.only(this.props.children)
}
}
if (process.env.NODE_ENV !== 'production') {
Provider.prototype.componentWillReceiveProps = function (nextProps) {
const { store } = this
const { store: nextStore } = nextProps
if (store !== nextStore) {
warnAboutReceivingStore()
}
}
}
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired
}
Provider.childContextTypes = {
store: storeShape.isRequired
}
首先在初始化时,获取到props中的store,然后通过react提供的getChildContext方法和childContextTypes验证向context内写入store以便子组件通过context获取。然后在render时,将当前组件的所有子组件渲染,Chirdren为react定义的顶级对象,该对象的only方法用于获取仅有的一个子组件,所以需要确保Provider组件只有一个子组件。
connect
正如其名,该方法才是连接react和redux的核心。其意义在于从展示组件派生出一个结合redux的容器组件,我们不用关心状态管理的细节,只用专注于展示组件的逻辑即可。先来看一下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
)
return this.renderedElement;
}
}
return hoistStatics(Connect, WrappedComponent);
}
}
可以看到首先在构造时就获取到了Provider提供的store,然后根据store获取到所有state,渲染时将mapStateToProps,mapDispatchToProps,props合并后传给展示组件, 并通过hoistStatics方法将展示组件的静态方法复制进connect组件,即完成了结合流程。下面再分析每个参数的意义。
mapStateToProps
该参数必须是一个函数,该函数也有两个参数,state表示store中的所有state,props表示通过组件connect传入的props,返回值即是merge进props的state。通过该参数将组件和Redux的状态树生成联系,计算待merge的state的源码如下:
computeStateProps(store, props) {
if (!this.finalMapStateToProps) {
return this.configureFinalMapState(store, props);
}
const state = store.getState();
const stateProps = this.doStatePropsDependOnOwnProps ?
this.finalMapStateToProps(state, props) :
this.finalMapStateToProps(state)
if (process.env.NODE_ENV !== 'production') {
checkStateShape(stateProps, 'mapStateToProps');
}
return stateProps;
}
其中作为参数的state通过store.getState()获取
mapDispatchToProps
该参数可以是actionCreators函数或者对象。当参数是对象时会运行redux中的bindActionCreators方法将其转化为函数形式。源码如下:
export default function wrapActionCreators(actionCreators) {
return dispatch => bindActionCreators(actionCreators, dispatch)
}
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
转化为函数后的映射就和mapStateToProps相似了,返回值即是merge进props的Dispatch方法。其中作为参数的dispatch就是store.dispatch。
mergeProps
该参数是一个定义了mapState,mapDispatch以及this.props合并规则的函数,默认规则如下:
const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
...parentProps,
...stateProps,
...dispatchProps
})
如果三个对象中字段有同名则后者会覆盖前者。
options
该参数是一个有pure和withRef两个属性的对象,pure表示是否开启pure优化,默认为true。withRef用来给展示组件注册一个ref,默认为false,开启时可以通过getWrappedInstance方法获取这个ref。
响应式机制
通过props中的dispatch函数可以派发action更新store,store的变化触发重新渲染的机制需要通过store的subscribe函数来实现。在connect组件中订阅变化的源码如下:
componentDidMount() {
this.trySubscribe();
}
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this));
this.handleChange();
}
}
componentWillUnmount() {
this.tryUnsubscribe();
this.clearCache();
}
可以看到在生命周期的挂载和卸载时,分别注册和注销了监听store变化的handleChange函数。接下来看处理变化的逻辑:
if (!this.unsubscribe) {
return;
}
const storeState = this.store.getState();
const prevStoreState = this.state.storeState;
if (pure && prevStoreState === storeState) {
return;
}
if (pure && !this.doStatePropsDependOnOwnProps) {
const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this);
if (!haveStatePropsChanged) {
return;
}
if (haveStatePropsChanged === errorObject) {
this.statePropsPrecalculationError = errorObject.value;
}
this.haveStatePropsBeenPrecalculated = true;
}
this.hasStoreStateChanged = true;
this.setState({storeState})
通过在connect组件中对比state的前后变化来决定是否调用setState触发重渲染,pure参数则用来决定是否只检测map相关的state数据变化。
-- EOF --