一文让你不再困惑setState之原理剖析和代码实现(下)


我们在面试的过程中经常会被问到,我们通过 setState 更改状态后发生了什么?状态是如何变更的?本期将从以下几个方面来深入了解 setState的工作原理。

  • 原理剖析
  • 实现异步队列 updateQueueUpdaterComponent

setState 原理剖析

我们通过 class Cmp extends Component 来定义一个class 组件,在源码中,Component的实现很简单,除了定义了一些实例变量,只有setState 和 forceUpdate两个方法。

class Component{
	static isReactComponent = {}
	constructor(props, context){
		// 更新器: 管理当前组件中所有变更
		this.$updater = new Updater(this)
		this.$cache = { isMounted: false }
		this.props = props
		this.state = {}
		this.refs = {}
		this.context = context
	}
	// 跳过所有生命周期执行强制更新, 实际更新组件的函数
	forceUpdate(callback) {}
	
	// nextState 可能是对象或函数
	setState(nextState, callback) {
		// 添加异步队列  不是每次都更新
		this.$updater.addCallback(callback)
		this.$updater.addState(nextState)
	}
}

暂且先不看 forceUpdate,在组件中我们通过 new Updater(this) 一个更新器(与组件一一对应),来管理组件的所有变更,setState 中也只是简单的通过更新器将变更动作和回调添加到异步队列中。通过updater.addState(nextState)和updater.addCallback(callback)将 nextState 和 callback 分别添加到 penddingStates 和peddingCallbacks中,然后 React 中通过 updateQueue 来管理这些 updater, 调用 updateQueue.add 将任务添加到队列等待系统批量更batchUpdate

刚刚说了一堆,眼睛有点缭乱了,我们来画个图,理解一下它们之间的关系。
请添加图片描述
用5分钟给大家用图更加客观的描述了 updateQueueupdaterComponent 的关系。
到此,我们还需要了解 updater 是如何来管理当前组件变更的,来实现一个 Updater

Updater

class Updater{
	constructor(instance){
		this.instance = instance // 组件实例
		this.pendingStates = [] // 待处理状态数组
		this.pendingCallbacks = [] // 待处理回调数组
		this.isPending = false
		this.nextProps = this.nextContext = null
		this.clearCallbacks = this.clearCallbacks.bind(this)
	}
	
	// 通知更新函数
	emitUpdate(nextProps, nextContext) {
		this.nextProps = nextProps
		this.nextContext = nextContext
		// 如果有接受到新的props,则立即更新
		nextProps || !updateQueue.isPending
		? this.updateComponent()
		: updateQueue.add(this)
	}
	
	// 实际更新函数
	updateComponent() {
		let { instance, pendingStates, nextProps, nextContext } = this
		if (nextProps || pendingStates.length > 0) {
			nextProps = nextProps || instance.props
			nextContext = nextContext || instance.context
			this.nextProps = this.nextContext = null
			// getState 合并所有的state的数据,一次更新
			shouldUpdate(instance, nextProps, this.getState(), nextContext, this.clearCallbacks)
		}
	}
	addState(nextState) {
		if (nextState) {
			this.pendingStates.push(nextState)
			// 如果当前队列空闲则直接更新
			if (!this.isPending) {
				this.emitUpdate()
			}
		}
	}
	
	addCallback(callback) {
		if (_.isFn(callback)) {
			this.pendingCallbacks.push(callback)
		}
	}

	getState() {
		let { instance, pendingStates } = this
		let { state, props } = instance
		if (pendingStates.length) {
			state = {...state}
			pendingStates.forEach(nextState => {
				if (_.isFn(nextState)) {
					nextState = nextState.call(instance, state, props)
				}
				state = {...state, ...nextState}
			})
			pendingStates = []
		}
		return state
	}
	
	clearCallbacks() {
		let { pendingCallbacks, instance } = this
		if (pendingCallbacks.length > 0) {
			pendingCallbacks.forEach(callback => callback.call(instance))
			this.pendingCallbacks = []
		}
	}
}

这里 Updater 除了在 setState 中用到的两个方法,还实现了另外两个重要的方法,emitUpdate 和 updateComponent分别用于通知组件更新和 更新组件。在组件实例化时,我们通过new Updater(this)将组件实例存储在 this.instance中。
通过分析addState 和 emitUpdate ,组件只有在updater.isPedding 和 updateQueue.isPending 均处于空闲时才会调用 updateComponent 去执行组件更新,注意在 updater.isPedding 空闲且组件存在新的 props 时,组件会立即更新 。

OK,重点来了,在 updateComponent 方法中 着重看 shouldUpdate(instance, nextProps, this.getState(), nextContext, this.clearCallbacks) ,通过 this.getState() 将合并后的新状态传入方法中,this.clearCallbacks 是用来批量执行回调的。

我们知道 在Component 中 setState 和 forceUpdate 的主要区别是,前者会去判断是否需要执行更新,后者会跳过这些步骤,强制更新。

function shouldUpdate(component, nextProps, nextState, nextContext, callback) {
	// 是否应该更新 判断shouldComponentUpdate生命周期
	let shouldComponentUpdate = true
	if (component.shouldComponentUpdate) {
		shouldComponentUpdate = component.shouldComponentUpdate(nextProps, nextState, nextContext)
	}
	if (shouldComponentUpdate === false) {
		component.props = nextProps
		component.state = nextState
		component.context = nextContext || {}
		return
	}
	let cache = component.$cache
	cache.props = nextProps
	cache.state = nextState
	cache.context = nextContext || {}
	component.forceUpdate(callback)
}

可以看出,绕了这么多,真正执行更新的是组件实例的 forceUpdate, 在执行更新前,回去判断组件是否有定义 shouldComponentUpdate ,根据其返回值来决定是否更新,将当前状态和参数缓存在$cache中,显然在 forceUpdate 中我们会用到。

foreUpdate

forceUpdate 会跳过所有生命周期,强制执行组件更新。

	forceUpdate(callback) {
		let { $updater, $cache, props, state, context } = this
		if (!$cache.isMounted) {
			return
		}
		if ($updater.isPending) {
            $updater.addState(state)
			return;
		}
		let nextProps = $cache.props || props
		let nextState = $cache.state || state
		let nextContext = $cache.context || context
		let parentContext = $cache.parentContext
		let node = $cache.node
		let vnode = $cache.vnode
		$cache.props = $cache.state = $cache.context = null
		$updater.isPending = true
		if (this.componentWillUpdate) {
			this.componentWillUpdate(nextProps, nextState, nextContext)
		}
		this.state = nextState
		this.props = nextProps
		this.context = nextContext

		// 对比vnode
	    let newVnode = renderComponent(this)
		let newNode = compareTwoVnodes(vnode, newVnode, node, getChildContext(this, parentContext))
		if (newNode !== node) {
			newNode.cache = newNode.cache || {}
			syncCache(newNode.cache, node.cache, newNode)
		}
		$cache.vnode = newVnode
		$cache.node = newNode
		clearPending()
		if (this.componentDidUpdate) {
			this.componentDidUpdate(props, state, context)
		}
		if (callback) {
			callback.call(this)
		}
		$updater.isPending = false
		$updater.emitUpdate()
	}

可以暂时不看缓存和虚拟 dom 相关方法的具体实现部分,在forceUpdate中,执行过程是:
isPending = true ——> 执行 componentWillUpdate ——> 对比vnode,更新 dom ——> 执行componentDidUpdate ——> 批量执行回调 ——> isPending = false ——> 继续调用emitUpdate 直到没有需要更新工作(!(nextProps || pendingStates.length > 0))。

最后,补齐下 updateQueue 相关的实现。

updateQueue

let updateQueue = {
	updaters: [],
	isPending: false,
	add(updater) {
		this.updaters.push(updater)
	},
	batchUpdate() {
		if (this.isPending) {
			return
		}
		this.isPending = true
		let { updaters } = this
		let updater
		while (updater = updaters.pop()) {
			updater.updateComponent()
		}
		this.isPending = false
	}
}

总结

说了这么多,最后我们来理一下思路,再来画一张图,源代码后期整理后会放在github中。
请添加图片描述
由此可见,setState 在合成事件和勾子函数中之所以是异步的,是因为执行函数在更新动作前就执行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值