第一篇连接: redux真的不复杂——源码解读javascript
预备知识:1. 了解redux的基本使用; 2. Context APIhtml
了解redux原理更好,若是不了解,不妨先看看第一篇博客。java
redux是一个状态管理的工具,本质上是一个js对象(包含状态,以及一些处理状态的方法)。react
因此redux具备很强的适应性,能够配合其余工具/框架一块儿使用。git
react-redux则是一个让你更容易地在react中使用redux的工具。github
咱们使用redux的目的是存储状态,在react中有存储状态的东西吗?redux
有,state。可是state有一些局限性。api
对于一个组件来讲,(用setState触发)state的变化会触发这个组件以及它全部子组件的更新,因此为了优化考虑,咱们每每将state放在更“局部”的组件中,这样state的变化只会引发最少的(有必要的)更新。缓存
那么若是咱们的react应用有一些状态在多个地方均可能用到,特别是一些全局数据(好比当前用户信息,全局的通知等等),对于这些数据咱们有两个选择:app
在各个局部组件中各存一份
优势是:保证了数据的变化只会引发最小的组件更新,
缺点是:
在全局组件中存一份
优势是:只须要在一个地方请求API,数据是彻底同步的
缺点是:数据的变化会引发整个应用大量的更新。
你会发现这两个选择各有优缺点,但仔细想一想,你会发现其实咱们有第三个选择:
利用Context API。在全局组件外包一个Provider,将数据存在Provider上。子组件经过访问context来使用这些数据
这样就兼顾了各个优势:
一切仿佛变得美好了,不是吗?
可是问题又来了:
这个时候你就会想到redux了,redux提供了一套优雅的管理状态的方案。
优雅在什么地方?接着往下看。
想要使用context api的方案,而且还要使用redux,那么你须要作的事情有:
差很少就这些,是否是也挺简单?
可是从技术上来说,你须要作的事情有:
难受吗?
你须要react-redux,它帮你把这些操做都封装了起来。
为了源码更清晰,分析时只展现了一些核心代码,省略了错误处理,通用性处理等代码。建议你参照着真正的源码阅读。
在看源码以前,先简单回忆一下react-redux的用法:
mapStateToProps
和mapDispatchToProps
两个参数(还有其余可选参数mergeProps
,options
),获得一个高阶组件【注1】。【注1】高阶组件:输入为组件,输出为另外一个组件的函数。
ok,咱们来看看源码的结构:
还挺复杂,不过不要紧,看看index.js:
//index.js
import Provider, { createProvider } from './components/Provider'
import connectAdvanced from './components/connectAdvanced'
import connect from './connect/connect'
export { Provider, createProvider, connectAdvanced, connect }
复制代码
createProvider和connectAdvanced这两个方法是定制react-redux的工做方式时使用的,咱们通常不会直接用到,因此咱们就从Provider和connect这两个模块入手。
Provider.js
的源码比较少,结构也很简单:
//Provider.js
//import略
//输出建立Provider的函数
export function createProvider(storeKey = 'store'){
class Provider extends Component {
// ...
}
return Provider
}
//默认输出建立后的Provider
export default createProvider()
复制代码
这个文件提供了两个输出,一个是建立Provider的函数,还有一个是建立过的Provider。
下面咱们来详细看看Provider这个组件具体是如何实现的:
class Provider extends Component {
// 访问context的钩子函数【注2】
getChildContext() {
// 返回一个对象,键是store的标识(可自定义),值是store
return { [storeKey]: this[storeKey] }
}
constructor(props, context) {
super(props, context)
// 将咱们(经过props)传进来的store存在本身的实例中。
this[storeKey] = props.store
}
render() {
// Children.only是react提供的API函数,
// 做用是限制this.props.children只能是一个React元素,不然会报错
return Children.only(this.props.children)
}
}
Provider.childContextTypes = {
// ...
}
复制代码
看到这里,能够发现Provider的实现很是简单,只是将咱们(经过props)传进去的store,建立了一个context而已。
注2:React能够经过在一个组件中设置
getChildContext
和childContextTypes
来建立context,在其子组件中设置contextTypes
属性来接收context
原来,react-redux只是将store放到Provider组件的context上。那么问题来了,
问题1:
Provider的子组件如何使用store?
顾名思义,connect函数的做用是——”链接“,将一个正常的组件与咱们的store链接起来,这样子组件就能够“使用”store了。
然而其实是如何实现链接的呢?若是你使用过react-redux的话,你就知道是:
咱们使用connect建立高阶组件时一般会传入mapStateToProps
,mapDispatchToProps
,然而高阶组件并无直接将其经过props传进被包裹组件——而是通过筛选和包装后,再经过props传入一些东西(store的一部分分支,或者自动dispatch的action creator)。
回答问题1(Provider的子组件如何使用store?):
经过将“筛选和包装”后的与store相关的东西,经过props传入组件,来使用store。
可见,筛选和包装是一个很是重要的任务,那么它是在connect中实现的吗?
咱们来看看connect的源码:
//connect.js
export function createConnect({ connectHOC = connectAdvanced, // 记住这个函数,后面会讲 // 选择器工厂:根据一些配置生成一个选择器 // (选择器工厂的实现,以及选择器的做用后面咱们会讲) selectorFactory = defaultSelectorFactory // ... 一些处理connect参数的工厂函数 } = {}) {
// 这里才是connect函数
return function connect({ mapStateToProps, mapDispatchToProps, mergeProps, options={} }){
// connect的参数有:mapStateToProps,mapDispatchToProps,mergeProps,options
// 然而这些参数并不能直接使用,它们多是对象,也多是函数
// 因此在这里进行通用性处理,是他们能够直接使用
// connect返回了一个高阶组件(由connectHOC建立)
return connectHOC(selectorFactory, { /*配置对象*/ })
}
}
// 默认输出的connect
export default createConnect()
复制代码
彷佛connect并无作筛选和包装这件事,仅仅返回了一个高阶组件,而这个高阶组件默认是由connectAdvanced
建立的。
因此也就是说:
connect
只是connectAdvanced
(这个函数一会再看)的一个包装,connect
自己只是一个预处理函数,真正的“筛选和包装”实际上是在connectAdvanced
这个函数里进行。
connect
作的事情仅仅是:
将选择器工厂和配置对象传给connectAdvanced
进行进一步处理。
(彷佛“筛选和包装”的功能是经过选择器工厂和配置对象实现的,下面咱们来看看是否是如此。)
问题2:
“筛选和包装”的功能是如何实现的?
从上面的代码能够看出,connectAdvanced
的做用是:
根据selectorFactory
和配置对象,建立一个高阶组件。
下面看看源码:
function connectAdvanced(selectorFactory, { /*options配置对象*/ }) {
// ... 根据options初始化一些内部变量
//返回一个高阶组件(输入为一个组件,输出为另外一个组件的函数)
return function wrapWithConnect(WrappedComponent){
// 高阶组件返回的组件Connect
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.state = {}
// 从props或context读取store
//(由于WrappedComponent本身可能也是一个Provider)
// 在本文的分析中咱们忽略Provider嵌套的这种状况
this.store = props[storeKey] || context[storeKey]
// ...
this.initSelector() // 初始化一个selector(后面会讲)
}
// 初始化selector的函数
initSelector() {/*...*/}
// ... 其余一些生命周期函数和工具函数
}
Connect.contextTypes = contextTypes // 获取外层的context
Connect.propTypes = contextTypes
// 返回Connect组件的时候多了一步处理
// 这个函数的做用是将WrappedComponent上的静态方法拷贝到Connect上,并返回Connect
// 这样就能够彻底把Connect看成一个“WrappedComponent”使用
return hoistNonReactStatics(Connect, WrappedComponent);
}
}
复制代码
结构依旧很简单,就是返回一个高阶组件。所谓高阶组件,实际上是一个函数。
高阶组件接收被包裹的组件,返回一个Connect组件,下面咱们的重点就放在这个Connect组件是如何建立的。
constructor看起来很普通,只不过从context中获取了store,而后将store存下来。还调用了一个initSelector()
函数,初始化选择器?选择器是什么东西???别急咱们一步一步来看。
看看其源码:
initSelector() {
// 传入dispatch方法和配置对象,获得一个原始选择器
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
// 对这个原始选择器进行进一步处理,获得一个最终的"stateful"的选择器selector
this.selector = makeSelectorStateful(sourceSelector, this.store)
// 调用selector的一个方法
this.selector.run(this.props)
}
复制代码
看完难免又有疑惑了:
一个一个来看。
// ...
function impureFinalPropsSelectorFactory({ /*配置对象*/ }){
// ...
}
function pureFinalPropsSelectorFactory({ /*配置对象*/ }){
// ...
}
export default function finalPropsSelectorFactory(dispatch, { /*配置对象*/ }) {
// 从配置对象拿到initMapStateToProps,initMapDispatchToProps,initMergeProps
// 这些方法是对connect函数参数(mapStateToProps,mapDispatchToProps,mergeProps)的包装
// 因此调用后返回的是加强后,能够直接使用的同名函数
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
// 根据options的pure字段肯定使用哪一种工厂函数
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
// 使用选择器工厂,返回一个选择器
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options //areStatesEqual, areOwnPropsEqual, areStatePropsEqual
)
}
复制代码
咱们来看看两种工厂函数:
// pure为false时的选择器工厂
// 功能及其简单,每次调用都返回一个新组装的props
function impureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch ) {
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}
// pure为true时的选择器工厂
function pureFinalPropsSelectorFactory({ mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } }){
let hasRunAtLeastOnce = false // 是不是第一次使用选择器
let state // 这个state并非组件的状态,而是redux的store
let ownProps // 存储上一次传入的ownProps
let stateProps // 经过mapStateToProps筛选出的要放进props的数据
let dispatchProps // 经过mapDispatchToProps筛选出的要放进props的数据
let mergedProps // 合并后的props,最终选择器返回的就是这个参数。
// 第一次调用选择器的函数
function handleFirstCall(firstState, firstOwnProps){
state = firstState
ownProps = firstOwnProps
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
return mergedProps
}
// 处理不一样状况时返回什么props
// 这三个函数也没什么稀奇的骚操做,就不展开了
// 仅仅是根据须要调用mapStateToProps或mapDispatchToProps获得stateProps和dispatchProps
// 而后调用mergeProps获得mergedProps
function handleNewPropsAndNewState(){}
function handleNewProps(){}
function handleNewState(){}
function handleSubsequentCalls(nextState, nextOwnProps) {
// areOwnPropsEqual,areStatesEqual用于比较新旧state,props是否相同
// 这是从配置对象中拿到的方法,实现方式就只是一个浅比较
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
const stateChanged = !areStatesEqual(nextState, state)
state = nextState
ownProps = nextOwnProps
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
// 这里是返回的选择器
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce // 判断是不是第一次使用选择器
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
复制代码
能够看出选择器工厂返回了一个函数pureFinalPropsSelector
,这就一个选择器。
能够看出,选择器的功能,就是接收nextState
和nextOwnProps
,返回一个通过“筛选和包装”的props。返回的props能够直接传给被包裹的组件。
回答问题2(“筛选和包装”的功能是如何实现的?):
使用选择器工厂,根据咱们传进去的配置项(通过处理的mapXXXToProps,dispatch,浅比较方法),生成一个具备“筛选和包装”功能的选择器
在connectAvanced
中使用的selectorFactory
已经弄明白了,下面看看另外一个makeSelectorStateful
函数。
function makeSelectorStateful(sourceSelector, store) {
// 建立了一个selector对象,这个对象有一个run方法
const selector = {
// run方法接收原始props(外部传给被包裹组件的props),
// 而且调用了一次原始选择器,获得调用后的props,
// 将新props和内部缓存的旧props比较,
// 根据结果,设置selector的shouldComponentUpdate属性。
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props)
if (nextProps !== selector.props || selector.error) {
selector.shouldComponentUpdate = true
selector.props = nextProps
selector.error = null
}
} catch (error) {
selector.shouldComponentUpdate = true
selector.error = error
}
}
}
return selector
}
复制代码
如今再回到connectAdvanced的源码:
function connectAdvanced(selectorFactory, { /*options配置对象*/ }) {
//返回一个高阶组件(输入为一个组件,输出为另外一个组件的函数)
return function wrapWithConnect(WrappedComponent){
// 高阶组件返回的组件Connect
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.state = {}
// 从props或context读取store
this.store = props[storeKey] || context[storeKey]
this.initSelector() // 初始化一个选择器
}
initSelector() {/*...*/}
// 咱们如今要重点看这里!!!!!!!!
// ... 其余一些生命周期函数和工具函数
}
// ...
return hoistNonReactStatics(Connect, WrappedComponent);
}
}
复制代码
咱们已经知道选择器的功能是:获取“筛选和包装”后的props,如今咱们看看Connect组件是如何使用选择器的。将关注点放在Connect这个组件的生命周期函数是如何使用的:
class Connect extends Component {
constructor(props, context) {
// ...
this.initSelector()
}
componentDidMount() {
// 向store中添加监听器,监听器函数在下面
// 就不详细看这个函数的实现了,就是简单的调用store的subscribe方法
this.subscription.trySubscribe()
// 运行选择器,根据选择器运行后的结果判断是否须要更新
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}
// 当接收新的props时,运行选择器
componentWillReceiveProps(nextProps) {
this.selector.run(nextProps)
}
// 根据选择器运行后的结果判断是否须要更新
shouldComponentUpdate() {
return this.selector.shouldComponentUpdate
}
// 卸载组件时清理内存
componentWillUnmount() {
// 卸载监听器
if (this.subscription) this.subscription.tryUnsubscribe()
this.store = null
this.selector.run = noop // 空函数function noop() {}
this.selector.shouldComponentUpdate = false
}
// 监听器,将会用subscribe方法添加到store上,每当store被dispatch会被调用
onStateChange() {
this.selector.run(this.props)
}
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
// addExtraProps方法的做用时将selector筛选后的props,添加到本来的props上。
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}
复制代码
原来这么简单啊,就只是:
在须要的时候:
运行选择器,获得须要添加的额外的props
根据运行的结果肯定是否更新Connect组件
渲染时向被包裹组件添加额外的props。
若是你看到了最后,你会发现,react-redux的实现方式和咱们文章开头的解决方案一毛同样:
实现的亮点在于,react-redux用了高阶组件这种优雅的方式,将这种需求进行了封装。
若是有疑问,或者想要交流的地方,欢迎在评论区讨论。