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无法停止,且调用栈很深。
Preact 源码分析
Preact 是React的轻量级实现,和React有同样的API,但是体积只有3kb左右。
10.0.0-rc.3创建虚拟dom
在
create-element.js文件中有三个方法主要负责创建虚拟dom,他们分别是createElement
主要负责将children挂载到vode的props上,同时处理defaultProps
createVNode
创建vnode对象
coerceToVNode
coerceToVNode则是将一些没有type类型的节点。比如一段字符串, 一个数字强制转换为VNode节点
Component和setState
Preact的Component类很简单,在
component.js中提供一下类和方法component
component 默认只有两个属性props, context.
后面注释部分会在后续过程中动态设置
setState
setState是异步的
这里使用的是Promise.resolve()来实现异步。多次setState操作都会被push到q队列中。最后调用enqueueRender进行调度更新
forceUpdate
forceUpdate是同步的
enqueueRender
将组件的rerender入队,进行异步调度
_dirty 和q.push(c) === 1保证在一个渲染周期内只进行一次rerenderdiff算法
在preact中diff算法是边比较边更新,和React差别很大。
文本节点
文本节点的type 是null
在diff方法中首先会对vnode的type进行判断,如果不是fuction就会调用diffElementNodes
文本节点VNode
非文本DOM节点
非文本DOM节点指的是type为div, span, h1的VNode节点。这些类型的节点在diff方法中依旧会调用diffElementNodes函数去处理。
document.createElement创建真实的DOM节点,挂载到VNode的_dom属性上diffProps 方法比较新旧属性的差异,对真实dom进行更新
这里有必要说一下事件绑定,在React中有自己的合成事件,但是在Preact中不存在,事件绑定是绑定在各个元素上的,不存在事件代理
在dom节点添加_listeners属性实现同时绑定多个事件。
eventProxy 是添加的事件监听器
对比非DOM组件
非DOM组件是自己定义的组件,比如 Home,UserInfo等
我们可以把非DOM组件看做dom节点的一个容器,在其render方法中总会返回一些dom节点才能渲染到屏幕上。所以最终还是会调用
diffElementNodes方法实现dom的更新但是非DOM组件有自己的生命周期,在不同时刻要调用不同的生命周期。
如果oldVNode和newVNode类型不同,我们将会卸载整个子树,这个逻辑的实现并不在diff中,二是在diffChildren中。
大致流程如下
diffChildren
在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被调用的代码
context 如何跳过 shouldComponentUpdate
在comsumer初始化的时候会调用
if (provider) provider.sub(c);将该组件添加到prover的subs数组中。enenqueueRender(c)所以如果consumer的父级组件的shouldComponentUpdate返回false,也能保证consumer正常更新。
总结
Preact虽然简单,代码量很少,但是其实现和React相比显得很混乱,diff和更新操作混淆在一起,不是很清晰。对组件的并没有清晰的类型定义,采用的是stack reconlication无法停止,且调用栈很深。