阅读须知
- 文章参考的preact版本是10.5.13
- 文章会省略大部分逻辑,比如hydrating,context,isSvg等。所以如果有大佬点进来需谨慎。
- 简略版本代码
setState
- setState注意点,默认setState是批量更新,setState(updater, callback),updater如果是函数,参数可以拿到最新的state,callback也可以拿到最新的state。
- 拿到组件本身最新的state
- 如果update是函数,执行update拿到修改后的state,合并到旧的state中。如果update是对象,合并到旧的state中。
- 保存callback函数到组件本身的_renderCallbacks,
Component.prototype.setState = function(update, callback) {
let s;
if (this._nextState != null && this._nextState !== this.state) {
s = this._nextState;
} else {
s = this._nextState = assign({}, this.state);
}
if (typeof update == 'function') {
update = update(assign({}, s), this.props);
}
if (update) {
assign(s, update);
}
if (update == null) return;
if (this._vnode) {
if (callback) this._renderCallbacks.push(callback);
enqueueRender(this);
}
};
enqueueRender
- c._dirty=true 表示组件是否处于更新状态。
- 在首次渲染初始化时:c._dirty = true。render之后:c._dirty = false。
- setState会检查组件是否处于更新状态,不是进入更新队列延迟更新,标记组件处于更新状态。
- preact异步更新:将更新函数放置在promiose.then 执行
- rerenderQueue基于_depth升序排序,更新从子组件开始更新,由下向上的更新。
- 比如子组件setState之后将点击函数传递给父组件,父组件同样执行setState。
// 存储需要更新的组件
let rerenderQueue = []
// 异步更新
const defer =
typeof Promise == 'function'
? Promise.prototype.then.bind(Promise.resolve())
: setTimeout
export function enqueueRender(c) {
if (
(!c._dirty &&
(c._dirty = true) &&
rerenderQueue.push(c) &&
!process._rerenderCount++)
) {
defer(process)
}
}
// 循环更新
function process() {
let queue
while ((process._rerenderCount = rerenderQueue.length)) {
queue = rerenderQueue.sort((a, b) => a._vnode._depth - b._vnode._depth)
rerenderQueue = []
queue.some(c => {
if (c._dirty) renderComponent(c)
})
}
}
// 记录当前更新队列的个数
process._rerenderCount = 0
renderComponent
- 调用组件的diff去更新。之后commitRoot执行保存在commitQueue的生命周期
function renderComponent(component) {
let vnode = component._vnode,
oldDom = vnode._dom,
parentDom = component._parentDom
if (parentDom) {
let commitQueue = []
const oldVNode = assign({}, vnode)
oldVNode._original = vnode._original + 1
diff(
parentDom,
vnode,
oldVNode,,
commitQueue,
oldDom == null ? getDomSibling(vnode) : oldDom,
)
commitRoot(commitQueue, vnode)
if (vnode._dom != oldDom) {
updateParentDomPointers(vnode)
}
}
}
forceUpdate
Component.prototype.forceUpdate = function(callback) {
if (this._vnode) {
this._force = true
if (callback) this._renderCallbacks.push(callback)
enqueueRender(this)
}
}