Skip to content

preact 源码学习 #1

@XDfield

Description

@XDfield

先看一个最简单的 preact 例子:

import {h, render} from 'preact';

render(
    <div id="app">Hello</div>,
    document.body
)

经过 babel 转换后可得:

"use strict";

var _preact = require("preact");

(0, _preact.render)((0, _preact.h)(
  "div",
  { id: "app" },
  "Hello"
), document.body);

可见,将一个 jsx 定义的元素挂载到 dom 上是执行了:

render(createElement(type, props, children), parentDom)

hcreateElement 方法的别名。

现在来看下 createElement 的实现:

export function createElement(type, props, children) {
    // 配置相应的参数
    if (props == null) props = {};
    if (arguments.length > 3) {
        children = [children];
        for (let i = 3; i < arguments.length; i++) {
            children.push(arguments[i])
        }
    }
    // 子节点被设为 props.children
    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];
        }
    }
    // 提取 ref
    let ref = props.ref;
    if (ref) delete props.ref;
    // 提取 key
    let key = props.key;
    if (key) delete props.key;
    
    // 返回 vnode 对象
    return createVNode(type, props, null, key, ref);
}

// 组合参数生成 vnode 对象
export function createVNode(type, props, text, key, ref) {
    const vnode = {
        type,
        props,
        text,
        key,
        ref,
        _children: null,
        _dom: null,
        _lastDomChild: null,
        _component: null
    };
    // 这里是一个自定义装饰 vnode 的方法
    if (options.vnode) options.vnode(vnode);
    return vnode
}

现在问题变成:

render(vnode, parentDom)

render 的实现:

export function render(vnode, parentDom) {
    let oldVNode = parentDom._prevVNode;
    // 这里将 vnode 用一个空的节点再包起来
    vnode = createElement(Fragment, null, [vnode]);
    // 比较差异生成 dom 节点
    let mounts = [];
    diffChildren(parentDom, parentDom._prevVNode = vnode. oldVNode, EMPTY_OBJ, parentDom.ownerSVGElement !== undefined, oldVNode ? null : EMPTY_ARR.slice.call(parentDom.childNodes), mounts, vnode);
    commitRoot(mounts, vnode);
}

preact 的组件

一些用到的辅助函数

// 复制对象属性
const assign = (obj, props) => {
    for (i in props) {
        obj[i] = props[i]
    }
    return obj
}
// 空内容
const Fragment = () => {}

Component

export function Component(props, context) {
    // 这里从上层接受组件参数与上下文对象
    this.props = props;
    this.context = context;
}

Component.prototype.setState = function (update, callback) {
    // 当第一次执行时, _nextState 为 null 此时会执行后半部分即将 state 复制给 _nextState 并返回。之后其实都是返回 _nextState
    let s = (this._nextState !== this.state && this._nextState) || (this._nextState = assign({}, this.state));
    // 将 update 的属性复制给 s
    if (typeof update !== 'function' || (update = update(s, this.props))) {
        assign(s, update);
    }
    // 若没有传入实质参数则返回
    if (update == null) return;
    // 更新后的回调函数
    if (callback != null) this._renderCallbacks.push(callback);
    // 通过 setState 方法更新组件时需要设定 _force 属性为 false 表面为正常更新手段
    this._force = false;
    // 执行更新
    enqueueRender(this);
}

Component.prototype.forceUpdate = function(callback) {
    let vnode = this._vnode, dom = this._vnode._dom, parentDom = this._parentDom;
    if (parentDom != null) {
        // 这里判断 _force 属性若为 null 表明为手动执行该更新方法!因为 setState 会设定 _force 为 false 且每次刷新后 _force 都会变回 null。这么做是为了比较 dom 差异时判断是否要跳过 shouldComponentUpdate 检查
        if (this._force == null) this._force = true;
        
        // 执行 dom 差异计算
        let mounts = [];
        dom = diff(dom, parentDom, vnode, vnode, this._context, parentDom.ownerSVGElement !== undefined, null, mounts, this._ancestorComponent);
        if (dom !== null && dom.parentNode !== parentDom) {
            parentDom.appendChild(dom);
        }
        commitRoot(mounts, vnode);
        
        // 将 _force 置为 null
        this._force = null;
    }
    // 更新后的回调,手动更新用
    if (callback != null) callback();
}

// render 方法默认返回空内容
Component.prototype.render = Fragment;

渲染队列

// 渲染堆栈
let q = [];

// 延迟函数
const defer = typeof Promise == 'function' ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout;

// 入栈
export function enqueueRender(c) {
    // 只有当 _dirty 为 false 时才能入栈。且之后 _dirty 被设置为 true。当队列非空时不允许入栈(此时 _dirty 已经被改为 true)
    if (!c._dirty && (c._dirty = true) && q.push(c) === 1) {
        // 这里将更新操作异步执行
        (options.debounceRendering || defer)(process);
    }
}

// 出栈执行强制更新
function process() {
    let p;
    while ((p = q.pop())) {
        if (p._dirty) p.forceUpdate();
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions