Skip to content

Preact源码分析 #63

Description

@xiaoxiaosaohuo

Preact 源码分析

Preact 是React的轻量级实现,和React有同样的API,但是体积只有3kb左右。

  • 当前版本 10.0.0-rc.3
  • ⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.

创建虚拟dom

create-element.js文件中有三个方法主要负责创建虚拟dom,他们分别是

  • createElement
  • createVNode
  • coerceToVNode

createElement

主要负责将children挂载到vode的props上,同时处理defaultProps

createElement(type, props, children) {
	props = assign({}, props);

	if (arguments.length>3) {
		children = [children];
		// https://github.com/preactjs/preact/issues/1916
		for (let i=3; i<arguments.length; i++) {
			children.push(arguments[i]);
		}
	}
	if (children!=null) {
		props.children = children;
	}
	if (type!=null && type.defaultProps!=null) {
		for (let i in type.defaultProps) {
			if (props[i]===undefined) props[i] = type.defaultProps[i];
		}
	}
	let ref = props.ref;
	let key = props.key;
	if (ref!=null) delete props.ref;
	if (key!=null) delete props.key;

	return createVNode(type, props, key, ref);
}

createVNode

创建vnode对象

export function createVNode(type, props, key, ref) {
	const vnode = {
		type,
		props,
		key,
		ref,
		_children: null,
		_parent: null,
		_depth: 0,
		_dom: null,
		_lastDomChild: null,
		_component: null,
		constructor: undefined
	};

	if (options.vnode) options.vnode(vnode);

	return vnode;
}

coerceToVNode

coerceToVNode则是将一些没有type类型的节点。比如一段字符串, 一个数字强制转换为VNode节点

export function coerceToVNode(possibleVNode) {
	if (possibleVNode == null || typeof possibleVNode === 'boolean') return null;
	if (typeof possibleVNode === 'string' || typeof possibleVNode === 'number') {
		return createVNode(null, possibleVNode, null, null);
	}
	if (possibleVNode._dom!=null || possibleVNode._component!=null) {
		let vnode = createVNode(possibleVNode.type, possibleVNode.props, possibleVNode.key, null);
		vnode._dom = possibleVNode._dom;
		return vnode;
	}

	return possibleVNode;
}

Component和setState

Preact的Component类很简单,在component.js中提供一下类和方法

  • component 组件基类
  • setState
  • forceUpdate
  • enqueueRender
  • getDomSibling

component

component 默认只有两个属性props, context.

后面注释部分会在后续过程中动态设置

export function Component(props, context) {
	this.props = props;
	this.context = context;
	// this.constructor // 当组件是函数式组件的时候会被重置为函数
	// if (this.state==null) this.state = {};
	// this.state = {};
	// this._dirty = true; //标记组件是否需要更新
	// this._renderCallbacks = []; setState的回调函数队列

	// this.base = null;// 组件的dom实例
	// this._context = null;
	// this._vnode = null;
	// this._nextState = null; //下一个state
	// this._processingException = null; // 用于处理异常
	// this._pendingError = null; // 用于处理异常,会被设置为parentVnode
}

setState

setState是异步的

这里使用的是Promise.resolve()来实现异步。多次setState操作都会被push到q队列中。最后调用enqueueRender进行调度更新

Component.prototype.setState = function(update, callback) 
	// copy this.state
	let s = (this._nextState!==this.state && this._nextState) || (this._nextState = assign({}, this.state));

	//如果 update是函数,就调用返回新的state,和旧state合并
	if (typeof update!=='function' || (update = update(s, this.props))) {
		assign(s, update);
	}

	//调过update,如果updater返回null
	if (update==null) return;

	if (this._vnode) {
		if (callback)
		//有callback的话,就将callback加入_renderCallbacks
		this._renderCallbacks.push(callback);
		// 调用enqueueRender 异步更新
		enqueueRender(this);
	}
};


forceUpdate

forceUpdate是同步的

Component.prototype.forceUpdate = function(callback) {
	let vnode = this._vnode, oldDom = this._vnode._dom, parentDom = this._parentDom;
	if (parentDom) {
		   // 判断是否是强制的update
		const force = callback!==false;
    
		let mounts = [];
		let newDom = diff(parentDom, vnode, assign({}, vnode), this._context, parentDom.ownerSVGElement!==undefined, null, mounts, force, oldDom == null ? getDomSibling(vnode) : oldDom);
		commitRoot(mounts, vnode);

		if (newDom != oldDom) {
			updateParentDomPointers(vnode);
		}
	}
	if (callback) callback();
};

enqueueRender

将组件的rerender入队,进行异步调度

_dirty 和q.push(c) === 1 保证在一个渲染周期内只进行一次rerender

const defer = typeof Promise=='function' ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout;


export function enqueueRender(c) {
	if ((!c._dirty && (c._dirty = true) && q.push(c) === 1) ||
	    (prevDebounce !== options.debounceRendering)) {
		prevDebounce = options.debounceRendering;
		(options.debounceRendering || defer)(process);
	}
}

运行队列中所有的渲染任务
function process() {
	let p;
	q.sort((a, b) => b._vnode._depth - a._vnode._depth);
	while ((p=q.pop())) {
		// forceUpdate's callback 参数表示这不是一个强制的update
		if (p._dirty) p.forceUpdate(false);
	}
}

diff算法

在preact中diff算法是边比较边更新,和React差别很大。

文本节点

文本节点的type 是null

在diff方法中首先会对vnode的type进行判断,如果不是fuction就会调用diffElementNodes

文本节点VNode


{
  type: null,
  props: null,
  text: '你的文本'
  _dom: TextNode
}
newVNode._dom = diffElementNodes(oldVNode._dom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, isHydrating);
  • 如果是初次渲染,dom还没有创建,就会创建dom
  • 如果是更新,会对比新旧属性,直接更新节点
if (dom==null) {
		if (newVNode.type===null) {
			return document.createTextNode(newProps);
		}
		dom = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', newVNode.type) : document.createElement(newVNode.type);
		// we created a new parent, so none of the previously attached children can be reused:
		excessDomChildren = null;
	}

if (newVNode.type===null) {
	if (oldProps !== newProps) {
		if (excessDomChildren!=null) excessDomChildren[excessDomChildren.indexOf(dom)] = null;
		dom.data = newProps;
	}
}

非文本DOM节点

非文本DOM节点指的是type为div, span, h1的VNode节点。这些类型的节点在diff方法中依旧会调用diffElementNodes函数去处理。

  • 初次渲染,调用 document.createElement 创建真实的DOM节点,挂载到VNode的_dom属性上
  • 更新阶段,比较新旧VNode的属性props,更新属性,接下来通过递归,完成对当前节点的子节点的更新操作

newVNode._dom = diffElementNodes(oldVNode._dom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, isHydrating);

if (newVNode.type===null) {
		if (oldProps !== newProps) {
			if (excessDomChildren!=null) excessDomChildren[excessDomChildren.indexOf(dom)] = null;
			dom.data = newProps;
		}
	}
	// newVNode !== oldVNode 这说明是一个静态节点
	else if (newVNode!==oldVNode) {
		if (excessDomChildren!=null) {
			excessDomChildren = EMPTY_ARR.slice.call(dom.childNodes);
		}

		oldProps = oldVNode.props || EMPTY_OBJ;
        //dangerouslySetInnerHTML处理
		let oldHtml = oldProps.dangerouslySetInnerHTML;
		let newHtml = newProps.dangerouslySetInnerHTML;

		if (!isHydrating) {
			if (newHtml || oldHtml) {
				// Avoid re-applying the same '__html' if it did not changed between re-render
				if (!newHtml || !oldHtml || newHtml.__html!=oldHtml.__html) {
					dom.innerHTML = newHtml && newHtml.__html || '';
				}
			}
		}

        //递归比对DOM属性
		diffProps(dom, newProps, oldProps, isSvg, isHydrating);

		newVNode._children = newVNode.props.children;

		// If the new vnode didn't have dangerouslySetInnerHTML, diff its children
		if (!newHtml) {
		//递归比对子元素
			diffChildren(dom, newVNode, oldVNode, context, newVNode.type==='foreignObject' ? false : isSvg, excessDomChildren, mounts, EMPTY_OBJ, isHydrating);
		}

		if (!isHydrating) {
			if (('value' in newProps) && newProps.value!==undefined && newProps.value !== dom.value) dom.value = newProps.value==null ? '' : newProps.value;
			if (('checked' in newProps) && newProps.checked!==undefined && newProps.checked !== dom.checked) dom.checked = newProps.checked;
		}
	}

diffProps 方法比较新旧属性的差异,对真实dom进行更新

export function diffProps(dom, newProps, oldProps, isSvg, hydrate) {
	let i;
    // 删除属性你
	for (i in oldProps) {
		if (!(i in newProps)) {
			setProperty(dom, i, null, oldProps[i], isSvg);
		}
	}
	//更新或者设置新属性

	for (i in newProps) {
		if ((!hydrate || typeof newProps[i]=='function') && i!=='value' && i!=='checked' && oldProps[i]!==newProps[i]) {
			setProperty(dom, i, newProps[i], oldProps[i], isSvg);
		}
	}
}

这里有必要说一下事件绑定,在React中有自己的合成事件,但是在Preact中不存在,事件绑定是绑定在各个元素上的,不存在事件代理

在dom节点添加_listeners属性实现同时绑定多个事件。

eventProxy 是添加的事件监听器

if (name[0]==='o' && name[1]==='n') {
	let useCapture = name !== (name=name.replace(/Capture$/, ''));
	let nameLower = name.toLowerCase();
	name = (nameLower in dom ? nameLower : name).slice(2);

	if (value) {
		if (!oldValue) dom.addEventListener(name, eventProxy, useCapture);
		(dom._listeners || (dom._listeners = {}))[name] = value;
	}
	else {
		dom.removeEventListener(name, eventProxy, useCapture);
	}
}

function eventProxy(e) {
	return this._listeners[e.type](options.event ? options.event(e) : e);
}

对比非DOM组件

非DOM组件是自己定义的组件,比如 Home,UserInfo等

我们可以把非DOM组件看做dom节点的一个容器,在其render方法中总会返回一些dom节点才能渲染到屏幕上。所以最终还是会调用diffElementNodes 方法实现dom的更新

但是非DOM组件有自己的生命周期,在不同时刻要调用不同的生命周期。

  • 如果VNode是非DOM组件类型。在diff函数中, 会在不同的时刻执行组件的生命周期。在diff中, 执行组件实例的render函数。我们将会拿到组件返回的VNode, 然后再将VNode再一次带入diff方法中进行diff比较。
if (typeof newType==='function') {
	let c, isNew, oldProps, oldState, snapshot, clearProcessingException;
	let newProps = newVNode.props;

	// 读取context
	tmp = newType.contextType;
	let provider = tmp && context[tmp._id];
	let cctx = tmp ? (provider ? provider.props.value : tmp._defaultValue) : context;

	// 已经存在组件实例,则获取component赋值给`c`
	if (oldVNode._component) {
		c = newVNode._component = oldVNode._component;
		clearProcessingException = c._processingException = c._pendingError;
	}
	else {
		// 初始化组件实例
		if ('prototype' in newType && newType.prototype.render) {
		// 类组件
			newVNode._component = c = new newType(newProps, cctx); // eslint-disable-line new-cap
		}
		else {
		//函数组件
			newVNode._component = c = new Component(newProps, cctx);
			c.constructor = newType;
			c.render = doRender;
		}
		if (provider) provider.sub(c);

		c.props = newProps;
		if (!c.state) c.state = {};
		c.context = cctx;
		c._context = context;
		isNew = c._dirty = true;
		c._renderCallbacks = [];
	}

	// 调用getDerivedStateFromProps
	if (c._nextState==null) {
		c._nextState = c.state;
	}
	if (newType.getDerivedStateFromProps!=null) {
		assign(c._nextState==c.state ? (c._nextState = assign({}, c._nextState)) : c._nextState, newType.getDerivedStateFromProps(newProps, c._nextState));
	}

	// 调用render之前的方法
	if (isNew) {
		if (newType.getDerivedStateFromProps==null && c.componentWillMount!=null) c.componentWillMount();
		//将组件推入mounts数组,在整个组件树diff完成后批量调用, 他们在commitRoot方法中被调用
		//按照先进后出(栈)的顺序调用, 即子组件的componentDidMount会先调用
		if (c.componentDidMount!=null) mounts.push(c);
	}
	else {
		if (newType.getDerivedStateFromProps==null && force==null && c.componentWillReceiveProps!=null) {
			c.componentWillReceiveProps(newProps, cctx);
		}
    // 调用shouldComponentUpdate生命周期, 并将_dirty设置为false, 当_dirty被设置为false时, 执行的更新操作将会被暂停
		if (!force && c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newProps, c._nextState, cctx)===false) {
			c.props = newProps;
			c.state = c._nextState;
			c._dirty = false;
			c._vnode = newVNode;
			newVNode._dom = oldDom!=null ? oldDom!==oldVNode._dom ? oldDom : oldVNode._dom : null;
			newVNode._children = oldVNode._children;
			for (tmp = 0; tmp < newVNode._children.length; tmp++) {
				if (newVNode._children[tmp]) newVNode._children[tmp]._parent = newVNode;
			}
			// shouldComponentUpdate返回false,取消渲染更新
			// break后,不在执行以下的代码
			break outer;
		}
        // 调用componentWillUpdate
		if (c.componentWillUpdate!=null) {
			c.componentWillUpdate(newProps, c._nextState, cctx);
		}
	}

	oldProps = c.props;
	oldState = c.state;
    // 将更新后的state,props,赋予组件的state,porps
	c.context = cctx;
	c.props = newProps;
	c.state = c._nextState;

	if (tmp = options._render) tmp(newVNode);

	c._dirty = false;
	c._vnode = newVNode;
	c._parentDom = parentDom;
    //调用组件的render方法获取组件的VNode
	tmp = c.render(c.props, c.state, c.context);
	let isTopLevelFragment = tmp != null && tmp.type == Fragment && tmp.key == null;
	newVNode._children = isTopLevelFragment ? tmp.props.children : tmp;

	if (c.getChildContext!=null) {
		context = assign(assign({}, context), c.getChildContext());
	}
    // 调用getSnapshotBeforeUpdate生命周期
	if (!isNew && c.getSnapshotBeforeUpdate!=null) {
		snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);
	}
    // 对children进行diff
	diffChildren(parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, oldDom, isHydrating);

	c.base = newVNode._dom;

	while (tmp=c._renderCallbacks.pop()) {
		if (c._nextState) { c.state = c._nextState; }
		tmp.call(c);
	}

	// 调用componentDidUpdate
	if (!isNew && oldProps!=null && c.componentDidUpdate!=null) {
		c.componentDidUpdate(oldProps, oldState, snapshot);
	}

	if (clearProcessingException) {
		c._pendingError = c._processingException = null;
	}
}


如果oldVNode和newVNode类型不同,我们将会卸载整个子树,这个逻辑的实现并不在diff中,二是在diffChildren中。

大致流程如下

  • 前后vnode类型不同,将oldVnode置为EMPTY_OBJ
  • 再次进行diff,由于oldVNode._component不存在就会新建

diffChildren

export function diffChildren(
    parentDom, // children的父DOM元素
    newParentVNode, // children的新父VNode
    oldParentVNode, // children的旧父VNode,diffChildren主要比对这两个Vnode的children
    context,
    isSvg, 
    excessDomChildren,
    mounts,// 保存在这次比对过程中被挂载的组件实例,在比对后,会触发这些组件的componentDidMount生命周期函数
    oldDom,  // 当前挂载的DOM,对于diffChildren来说,oldDom一开始指向第一个子节点
    isHydrating
) {
	let i, j, oldVNode, newDom, sibDom, firstChildDom, refs;

	let oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;

	let oldChildrenLength = oldChildren.length;

	if (oldDom == EMPTY_OBJ) {
		if (excessDomChildren != null) {
			oldDom = excessDomChildren[0];
		}
		else if (oldChildrenLength) {
			oldDom = getDomSibling(oldParentVNode, 0);
		}
		else {
			oldDom = null;
		}
	}

	i=0;
	newParentVNode._children = toChildArray(newParentVNode._children, childVNode => {

		if (childVNode!=null) {
			childVNode._parent = newParentVNode;
			childVNode._depth = newParentVNode._depth + 1;

		
			oldVNode = oldChildren[i];

			if (oldVNode===null || (oldVNode && childVNode.key == oldVNode.key && childVNode.type === oldVNode.type)) {
				oldChildren[i] = undefined;
			}
			else {
			
				for (j=0; j<oldChildrenLength; j++) {
					oldVNode = oldChildren[j];
					
					if (oldVNode && childVNode.key == oldVNode.key && childVNode.type === oldVNode.type) {
						oldChildren[j] = undefined;
						break;
					}
					oldVNode = null;
				}
			}

			oldVNode = oldVNode || EMPTY_OBJ;

			
			if ((j = childVNode.ref) && oldVNode.ref != j) {
				(refs || (refs=[])).push(j, childVNode._component || newDom, childVNode);
			}

		
			if (newDom!=null) {
				if (firstChildDom == null) {
					firstChildDom = newDom;
				}
                //只有Fragment或组件返回Fragment的Vnode会有非null的_lastDomChild
				if (childVNode._lastDomChild != null) {
					
					newDom = childVNode._lastDomChild;

				
					childVNode._lastDomChild = null;
				}
				else if (excessDomChildren==oldVNode || newDom!=oldDom || newDom.parentNode==null) {
				
					outer: if (oldDom==null || oldDom.parentNode!==parentDom) {
						parentDom.appendChild(newDom);
					}
					else {
					 (sibDom=sibDom.nextSibling) && j<oldChildrenLength; j+=2) {
							if (sibDom==newDom) {
								break outer;
							}
						}
						parentDom.insertBefore(newDom, oldDom);
					}

					
					if (newParentVNode.type == 'option') {
						parentDom.value = '';
					}
				}

				oldDom = newDom.nextSibling;

				if (typeof newParentVNode.type == 'function') {
					
					newParentVNode._lastDomChild = newDom;
				}
			}
		}

		i++;
		return childVNode;
	});

	newParentVNode._dom = firstChildDom;

	if (excessDomChildren!=null && typeof newParentVNode.type !== 'function') for (i=excessDomChildren.length; i--; ) if (excessDomChildren[i]!=null) removeNode(excessDomChildren[i]);


	for (i=oldChildrenLength; i--; ) if (oldChildren[i]!=null) unmount(oldChildren[i], oldChildren[i]);

	if (refs) {
		for (i = 0; i < refs.length; i++) {
			applyRef(refs[i], refs[++i], refs[++i]);
		}
	}
}
  • 在diffChildren中,首先通过toChildArray函数将子节点以数组的形式存储在_children属性上。

  • firstChildDom为第一个子节点真实的dom,后面将通过它来判断是使用appendChild插入newDom还是使用insertBefore插入newDom或者什么也不做

  • 遍历_children属性。如果VNode有key属性, 则找到key与key相等的旧的VNode。如果没有key, 则找到最近的type相等的旧的VNode。然后将oldChildren对应的位置设置null, 避免重复的查找。使用diff算法对比, 新旧VNode。返回新的dom

  • 如果oldDom为null, 则将新dom,如果在oldDom的nextSibling没有找到和新的dom相等的dom, 我们将dom插入oldDom的前面。

  • 遍历剩余没有使用到oldChildren, 卸载这些节点或者组件。

Hooks

在这一版本的Preact中也实现了Hooks功能,其实现方式和React不同,但是背后的思想是一致的。 Preact 提供了options对象来对 Preact diff 进行功能扩展,options 本质上类似于生命周期钩子,在 diff 过程中的不同阶段被调用

挑出hooks被调用的代码

diff(){
    //开始diff
        if (tmp = options._diff) tmp(newVNode);
    try{
        //开始渲染
         if (tmp = options._render) tmp(newVNode);
         //diff结束
         if (tmp = options.diffed) tmp(newVNode);
    }catch(){
    //  捕获异常
        options._catchError(e, newVNode, oldVNode);
    }
   
}


import { options } from 'preact';

// hooks的下标
let currentIndex; 

//当前组件
let currentComponent;

// paint之后执行的effect
let afterPaintEffects = [];

let oldBeforeRender = options._render;
options._render = vnode => {
	if (oldBeforeRender) oldBeforeRender(vnode);

	currentComponent = vnode._component;
	currentIndex = 0;
//存在__hooks,在render之前运行pendinng的effect
	if (currentComponent.__hooks) {
		currentComponent.__hooks._pendingEffects = handleEffects(currentComponent.__hooks._pendingEffects);
	}
};

// diff完成后执行,因为diff完成表示更新完成,就执行pendingLayoutEffects
let oldAfterDiff = options.diffed;
options.diffed = vnode => {
	if (oldAfterDiff) oldAfterDiff(vnode);

	const c = vnode._component;
	if (!c) return;

	const hooks = c.__hooks;
	if (hooks) {
		hooks._handles = bindHandles(hooks._handles);
		hooks._pendingLayoutEffects = handleEffects(hooks._pendingLayoutEffects);
	}
};


// 卸载时执行,清空hooks
let oldBeforeUnmount = options.unmount;
options.unmount = vnode => {
	if (oldBeforeUnmount) oldBeforeUnmount(vnode);

	const c = vnode._component;
	if (!c) return;

	const hooks = c.__hooks;
	if (hooks) {
		hooks._list.forEach(hook => hook._cleanup && hook._cleanup());
	}
};

// 根据下标获取当前hook的状态
function getHookState(index) {
	if (options._hook) options._hook(currentComponent);
	
	const hooks = currentComponent.__hooks || (currentComponent.__hooks = { _list: [], _pendingEffects: [], _pendingLayoutEffects: [], _handles: [] });

	if (index >= hooks._list.length) {
		hooks._list.push({});
	}
	return hooks._list[index];
}

export function useState(initialState) {
	return useReducer(invokeOrReturn, initialState);
}

export function useReducer(reducer, initialState, init) {

	const hookState = getHookState(currentIndex++);
	if (!hookState._component) {
		hookState._component = currentComponent;

		hookState._value = [
			!init ? invokeOrReturn(null, initialState) : init(initialState),

			action => {
				const nextValue = reducer(hookState._value[0], action);
				if (hookState._value[0]!==nextValue) {
					hookState._value[0] = nextValue;
					hookState._component.setState({});
				}
			}
		];
	}

	return hookState._value;
}


export function useEffect(callback, args) {

	const state = getHookState(currentIndex++);
	if (argsChanged(state._args, args)) {
		state._value = callback;
		state._args = args;

		currentComponent.__hooks._pendingEffects.push(state);
		afterPaint(currentComponent);
	}
}

export function useLayoutEffect(callback, args) {

	/** @type {import('./internal').EffectHookState} */
	const state = getHookState(currentIndex++);
	if (argsChanged(state._args, args)) {
		state._value = callback;
		state._args = args;
		currentComponent.__hooks._pendingLayoutEffects.push(state);
	}
}

export function useRef(initialValue) {
	return useMemo(() => ({ current: initialValue }), []);
}

export function useImperativeHandle(ref, createHandle, args) {
	const state = getHookState(currentIndex++);
	if (argsChanged(state._args, args)) {
		state._args = args;
		currentComponent.__hooks._handles.push({ ref, createHandle });
	}
}

function bindHandles(handles) {
	handles.some(handle => {
		if (handle.ref) handle.ref.current = handle.createHandle();
	});
	return [];
}


export function useMemo(callback, args) {


	const state = getHookState(currentIndex++);
	if (argsChanged(state._args, args)) {
		state._args = args;
		state._callback = callback;
		return state._value = callback();
	}

	return state._value;
}


export function useCallback(callback, args) {
	return useMemo(() => callback, args);
}


export function useContext(context) {
	const provider = currentComponent.context[context._id];
	if (!provider) return context._defaultValue;
	const state = getHookState(currentIndex++);
	// This is probably not safe to convert to "!"
	if (state._value == null) {
		state._value = true;
		provider.sub(currentComponent);
	}
	return provider.props.value;
}


export function useDebugValue(value, formatter) {
	if (options.useDebugValue) {
		options.useDebugValue(formatter ? formatter(value) : value);
	}
}


function flushAfterPaintEffects() {
	afterPaintEffects.some(component => {
		component._afterPaintQueued = false;
		if (component._parentDom) {
			component.__hooks._pendingEffects = handleEffects(component.__hooks._pendingEffects);
		}
	});
	afterPaintEffects = [];
}

// 使用requestAnimationFrame保证effect在paint之后调用
function safeRaf(callback) {
	const done = () => {
		clearTimeout(timeout);
		cancelAnimationFrame(raf);
		setTimeout(callback);
	};
	const timeout = setTimeout(done, RAF_TIMEOUT);
	const raf = requestAnimationFrame(done);
}


if (typeof window !== 'undefined') {
	let prevRaf = options.requestAnimationFrame;
	afterPaint = (component) => {
		if ((!component._afterPaintQueued && (component._afterPaintQueued = true) && afterPaintEffects.push(component) === 1) ||
		    prevRaf !== options.requestAnimationFrame) {
			prevRaf = options.requestAnimationFrame;

		
			(options.requestAnimationFrame || safeRaf)(flushAfterPaintEffects);
		}
	};
}

function handleEffects(effects) {
	effects.forEach(invokeCleanup);
	effects.forEach(invokeEffect);
	return [];
}

function invokeCleanup(hook) {
	if (hook._cleanup) hook._cleanup();
}


function invokeEffect(hook) {
	const result = hook._value();
	if (typeof result === 'function') hook._cleanup = result;
}

function argsChanged(oldArgs, newArgs) {
	return !oldArgs || newArgs.some((arg, index) => arg !== oldArgs[index]);
}

function invokeOrReturn(arg, f) {
	return typeof f === 'function' ? f(arg) : f;
}

context 如何跳过 shouldComponentUpdate

export function createContext(defaultValue) {
	const ctx = {};

	const context = {
		_id: '__cC' + i++,
		_defaultValue: defaultValue,
		Consumer(props, context) {
			this.shouldComponentUpdate = function (_props, _state, _context) {
				return _context !== context;
			};
			return props.children(context);
		},
		Provider(props) {
			if (!this.getChildContext) {
				const subs = [];
				this.getChildContext = () => {
					ctx[context._id] = this;
					return ctx;
				};
				this.shouldComponentUpdate = props => {
					subs.some(c => {
						// Check if still mounted
						if (c._parentDom) {
							c.context = props.value;
							enqueueRender(c);
						}
					});
				};
				this.sub = (c) => {
					subs.push(c);
					let old = c.componentWillUnmount;
					c.componentWillUnmount = () => {
						subs.splice(subs.indexOf(c), 1);
						old && old.call(c);
					};
				};
			}
			return props.children;
		}
	};

	context.Consumer.contextType = context;

	return context;
}
  • 在首次挂载的时候,会修改provider的shouldComponentUpdate方法

在comsumer初始化的时候会调用if (provider) provider.sub(c); 将该组件添加到prover的subs数组中。

  • 更新时,调用shouldComponentUpdate方法,将consumer组件加入渲染队列中 enenqueueRender(c)

所以如果consumer的父级组件的shouldComponentUpdate返回false,也能保证consumer正常更新。

this.shouldComponentUpdate = props => {
	subs.some(c => {
		// Check if still mounted
		if (c._parentDom) {
			c.context = props.value;
			enqueueRender(c);
		}
	});
};
this.sub = (c) => {
	subs.push(c);
	let old = c.componentWillUnmount;
	c.componentWillUnmount = () => {
		subs.splice(subs.indexOf(c), 1);
		old && old.call(c);
	};
};

总结

Preact虽然简单,代码量很少,但是其实现和React相比显得很混乱,diff和更新操作混淆在一起,不是很清晰。对组件的并没有清晰的类型定义,采用的是stack reconlication无法停止,且调用栈很深。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions