您的位置:首页 > Web前端 > React

Redux源码分析

2017-07-20 23:45 274 查看

1 redux使用步骤

React仅仅是一个前端View框架库,可以看做是MVC里面的V。没有解决组件间通信,MVC分离,数据共享等问题。Redux的出现使得这些都不是问题。使用Redux也比较简单,步骤大概如下

编写React Component,这里不涉及Redux

编写reducer,它接收一个state和action,返回一个新的state

createStore(reducer) 得到store对象,它是Redux的核心对象。包含的主要API如下

getState(), 得到store中保存的state对象

dispatch(action), 发送action消息给store,它会调用reducer更新state,并回调subscribe的监听器

subcribe(listener), 注册监听器,dispatch触发时会回调

编写React Component中的事件回调,比如onClick。使用store.dispatch(action)发送消息,reducer此时会得到调用,更新state。

其中createStore(reducer)比较关键,下面来分析它的源码

2 createStore()源码分析

function createStore(reducer, preloadedState, enhancer) {
// reducer, preloadedState, enhancer 入参检查

let currentReducer = reducer
let currentState = preloadedState  // createStore传入的state会存储为初始state
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false

// 判断是否能触发nextListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}

// 返回store中存储的state
function getState() {
return currentState
}

// 注册事件,加入到listener数组中。每次dispatch调用时,会回调注册的listener
function subscribe(listener) {
let isSubscribed = true

// 存储listener
ensureCanMutateNextListeners()
nextListeners.push(listener)

// 取消订阅事件
return function unsubscribe() {
if (!isSubscribed) {
return
}

isSubscribed = false

ensureCanMutateNextListeners()
// 找到要取消订阅的listener,然后从数组中删除,以后就不会回调到它了
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}

// 发送消息action。主要两件事
// 1. 调用reducer更新state
// 2. 回调subscribe注册的listeners
function dispatch(action) {
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}

try {
isDispatching = true
// 调用reducer来更新state, 传入当前state和action
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}

// 回调subscribe的listeners, dispatch会触发回调listener
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

return action
}

// createStore时触发一次dispatch, 一般会调用到我们reducer里面的default分支,此时返回默认state,更新currentState
dispatch({ type: ActionTypes.INIT })

return {
dispatch,
subscribe,
getState,
}


源码分析中对一些不是很关键的地方做了省略,我们重点关注dispatch, subscribe, getState三个API即可。其中dispatch稍微麻烦点,它主要做两件事

调用reducer更新state

回调subscribe注册的listeners

3 React-redux 使用步骤

Redux已经足够优秀了,但需要用户手动调用setState来更新view,也没有建立store上的state数据和React Component之间联系,而React-redux框架很好的解决了这个问题。它的使用步骤如下

编写UI组件,也就是React Component。这部分和Redux无关

编写mapStateToProps, mapDispatchToProps,然后connect(mapStateToProps, mapDispatchToProps)(UI组件)来生成容器组件。容器组件包含了UI,数据和逻辑。

编写reducers,完成控制逻辑。这部分和只是用Redux相同

应用顶层做整合

createStore(reducers) 生成store

使用Provider包装顶层组件,如下

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);


步骤不复杂,下面我们来分析下connect源码

4 connect源码分析

connectAPI为connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]), 后两个参数不是很关键,我们源码分析中省略它们,这样以最简洁的方式分析主流程

function connect(mapStateToProps, mapDispatchToProps) {
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')

return connectHOC(selectorFactory, {
// used in error messages
methodName: 'connect',

// used to compute Connect's displayName from the wrapped component's displayName.
getDisplayName: name => `Connect(${name})`,

// mapStateToProps为空时,不能自动处理View刷新,此时需要用户自己setState来更新view
shouldHandleStateChanges: Boolean(mapStateToProps),

// 参数传递到selectorFactory
initMapStateToProps,
initMapDispatchToProps,
})
}


connect方法没做什么事情,调用的connectHOC方法,connectHOC默认实现为connectAdvanced。connectAdvanced最终返回经过包装的React组件,也就是Redux所说的容器型组件。它对React组件生命周期方法进行了一些处理,源码如下。

class Connect extends Component {
constructor(props, context) {
super(props, context)

this.version = version
this.state = {}
this.renderCount = 0
this.store = props[storeKey] || context[storeKey]
this.propsMode = Boolean(props[storeKey])
this.setWrappedInstance = this.setWrappedInstance.bind(this)
this.initSelector()
this.initSubscription()
}

componentDidMount() {
// shouldHandleStateChanges: Boolean(mapStateToProps)
// 当mapStateToProps为空时,不能自动处理View刷新,此时需要用户自己setState来更新view
if (!shouldHandleStateChanges) return

// 关键在selector.run()方法中,它会将mapStateToProps mapDispatchToProps等扩展的数据添加到React Component的props上。具体看后面makeSelectorStateful的分析
this.subscription.trySubscribe()
this.selector.run(this.props)
// 如果props有改变,shouldComponentUpdate会为true,此时forupdate强制刷新view
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}

componentWillReceiveProps(nextProps) {
// 具体代码看后面的makeSelectorStateful,主要是判断props有没有更新,如果更新则刷新view
this.selector.run(nextProps)
}

// 判断是否取消掉view的刷新,React生命周期方法
// selector.shouldComponentUpdate在makeSelectorStateful进行赋值的
// 如果前后两次props有改变,则设置为true,此时不会拦截view的刷新
shouldComponentUpdate() {
return this.selector.shouldComponentUpdate
}

// unmount卸载前的调用
componentWillUnmount() {
if (this.subscription) this.subscription.tryUnsubscribe()
this.subscription = null
this.notifyNestedSubs = noop
this.store = null
this.selector.run = noop
this.selector.shouldComponentUpdate = false
}

// wrappedInstance为未经过包装的React组件,也就是Redux所说的UI组件
getWrappedInstance() {
return this.wrappedInstance
}

setWrappedInstance(ref) {
this.wrappedInstance = ref
}

// 比较关键,具体分析可以看后面对makeSelectorStateful的单独分析
initSelector() {
// selectorFactory很复杂,大概就是将stateProps,dispatchProps和原先就有的props合并。
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
// run方法在props有变化时,将shouldComponentUpdate设置为true,React生命周期时可以刷新view。另外将mapStateToProps mapDispatchToProps等扩展的数据添加到React Component的props上。
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}

onStateChange() {
this.selector.run(this.props)

if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs()
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
this.setState(dummyState)
}
}

addExtraProps(props) {
// 省略不关键的代码,基本就是将props返回。
const withExtras = { ...props }
return withExtras
}

// Redux封装了React Component,重写了render方法,对props进行了增强
render() {
const selector = this.selector
selector.shouldComponentUpdate = false

if (selector.error) {
throw selector.error
} else {
// WrappedComponent是connect()(reactComponent)中传入的React Component
// Redux对React Component进行了封装,render方法最后调用React的createElement 将props传入
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}
}


initSelector()方法调用makeSelectorStateful获取selector,并调用它的run方法

function makeSelectorStateful(sourceSelector, store) {
const selector = {
run: function runComponentSelector(props) {
try {
// 得到props, selector为我们包装的React Component对象
const nextProps = sourceSelector(store.getState(), props)
if (nextProps !== selector.props || selector.error) {
// props有变化,则shouldComponentUpdate为true,会刷新view
selector.shouldComponentUpdate = true
// 将mapStateToProps mapDispatchToProps等扩展的数据添加到React Component的props上
selector.props = nextProps
selector.error = null
}
} catch (error) {
selector.shouldComponentUpdate = true
selector.error = error
}
}
}

return selector
}


connect方法源码十分复杂,我们省略了很多不关键的地方。从源码中我们可以看出

mapStateToProps,mapDispatchToProps,最终会经过selectorFactory,与React组件本身的props进行合并。

mapStateToProps注册了subscribe,这样每次dispatch时,回调完reducer后,就会调用mapStateToProps,将新的state传递过来。此时再将state合并到组件的props上,会引起props的改变,从而触发一次forceUpdate,从而自动刷新view

connect方法对React组件进行了包装,返回一个容器型组件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redux react connect 源码