preact源码学习(1)

前端之家收集整理的这篇文章主要介绍了preact源码学习(1)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

preact是目前最小的react兼容库了,因此学习它对提升anujs有很大的帮助。

preact的一些模块非常简单。

//vnode.js
export function VNode() {}

一句话一个模块,其实这个在preact-compat 会被扩展原型。

//util.js
//糅杂,相当于es6的Object.assign
export function extend(obj,props) {
    for (let i in props) obj[i] = props[i];
    return obj;
}

//用于异步执行一个函数,Promise比setTimeout的执行间隔太短
export const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;

有关异步的内容可以看我的书《javascript框架设计》,这里有详细介绍。这其实也涉及到microtask,macrotask的概念,有兴趣的人可以搜索一下。

preact的工具模块是我见过的库中最精简的。

//options.js

export default {

    // 用于同步刷新组件
    //syncComponentUpdates: true,// 用于扩展VNode实例
    //vnode(vnode) { }

    // 在组件插入DOM时调用,不同于componentDidMount,它是专门给框架或组件内部使用,比如说chrome debug tools这样的工具进行扩展
    // afterMount(component) { }

    // 同上,内置的后门
    // afterUpdate(component) { }

    // 同上,内置的后门
    // beforeUnmount(component) { }
};

options这个模块是用于扩展preact的功能,从而兼容官方react。

// constants.js
// 各种渲染模式

export const NO_RENDER = 0; //不渲染
export const SYNC_RENDER = 1;//React.render就是同步
export const FORCE_RENDER = 2;//forceUpdate
export const ASYNC_RENDER = 3;//组件的更新是异步


export const ATTR_KEY = '__preactattr_';//在节点中添加属性

//用于识别那些样式不用自动添加px的正则
export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i;

下面是h.js,其实就是React.createElement,这里做了一个不同于react的操作,就是立即将children扁平化,并且在扁平化过程成进行hydrate操作。hydrate是最早出现于inferno(另一个著名的react-like框架),并相邻的简单数据类型合并成一个字符串。因为在react的虚拟DOM体系中,字符串相当于一个文本节点。减少children中的个数,就相当减少实际生成的文本节点的数量,也减少了以后diff的数量,能有效提高性能

// h.js
import { VNode } from './vnode';
import options from './options';


const stack = [];

const EMPTY_CHILDREN = [];
/**
* nodeName相当于react的type
* attributes相当于react的props
* 这是preact早期设计不周,这个标新立异导致它在兼容官方react要走许多弯路
*/
export function h(nodeName,attributes) {
    let children=EMPTY_CHILDREN,lastSimple,child,simple,i;
    for (i=arguments.length; i-- > 2; ) {
        stack.push(arguments[i]);
    }
    if (attributes && attributes.children!=null) {
        if (!stack.length) stack.push(attributes.children);
        delete attributes.children;
    }
    while (stack.length) {
        if ((child = stack.pop()) && child.pop!==undefined) {
            for (i=child.length; i--; ) stack.push(child[i]);
        }
        else {
            //减少比较类型
            if (typeof child==='boolean') child = null;

            if ((simple = typeof nodeName!=='function')) {
                //转化为字符串
                if (child==null) child = '';
                //合并相邻简单类型
                else if (typeof child==='number') child = String(child);
                else if (typeof child!=='string') simple = false;
            }

            if (simple && lastSimple) {
                children[children.length-1] += child;
            }
            else if (children===EMPTY_CHILDREN) {
                children = [child];
            }
            else {
                children.push(child);
            }

            lastSimple = simple;
        }
    }

    let p = new VNode();
    p.nodeName = nodeName;
    p.children = children;
    p.attributes = attributes==null ? undefined : attributes;
    p.key = attributes==null ? undefined : attributes.key;
    //对最终生成的虚拟DOM进行扩展
       if (options.vnode!==undefined) options.vnode(p);

    return p;
}
属性 react preact
类别 type nodeName
属性 props attributes
孩子 props.children children
数组追踪用的trace by属性 key key

cloneElement与createElement是一对的,cloneElement是基于createElement实现

import { extend } from './util';
import { h } from './h';

export function cloneElement(vnode,props) {
    return h(
        vnode.nodeName,extend(extend({},vnode.attributes),props),arguments.length>2 ? [].slice.call(arguments,2) : vnode.children
    );
}

React.Component的实现

import { FORCE_RENDER } from './constants';
import { extend } from './util';
import { renderComponent } from './vdom/component';
import { enqueueRender } from './render-queue';

/** Base Component class.
 *    Provides `setState()` and `forceUpdate()`,which trigger rendering.
 *    @public
 *
 *    @example
 *    class MyFoo extends Component {
 *        render(props,state) {
 *            return <div />;
 *        }
 *    }
 */
export function Component(props,context) {
    //只有在_dirty为true时才能更新组件
    this._dirty = true;
    this.context = context;
    this.props = props;
    this.state = this.state || {};
}


extend(Component.prototype,{

    /** 
     * 立即对state进行合并,而官方react是将state先放到一个数组中
     */
    setState(state,callback) {
        let s = this.state;
        if (!this.prevState) this.prevState = extend({},s);
        extend(s,typeof state==='function' ? state(s,this.props) : state);
        if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback);
        enqueueRender(this);
    },//强制渲染,注意它与setState的实现是不一样的
    forceUpdate(callback) {
        if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback);
        renderComponent(this,FORCE_RENDER);
    },//将方法要求返回虚拟DOM或null
    render() {}

});

Component依赖两个方法enqueueRender与renderComponent,一个是异步的,一个是同步的。enqueueRender则是基于renderComponent上构建的。

我们看render-queue.js,这模块名与里面的方法名对应不一致,算是一个瑕疵。

import options from './options';
import { defer } from './util';
import { renderComponent } from './vdom/component';
let items = [];

//用于延迟渲染当前组件(setState)
export function enqueueRender(component) {
    if (!component._dirty && (component._dirty = true) && items.push(component)==1) {
        (options.debounceRendering || defer)(rerender);
    }
}
export function rerender() {
    let p,list = items;
    items = [];
    while ( (p = list.pop()) ) {
        if (p._dirty) renderComponent(p);
    }
}

到这里,比较简单的模块已经介绍完了。render.js?这个模块其实放到vdom文件夹比较合适。读preact的源码,其实可以给我们带来许多启迪,原来组件的渲染是有许多种模式的。这是一个要点。如何每次setState都是同步更新,这性能肯定好差,而异步则要求怎么更新才是最适合。于是有了enqueueRender这样的函数。下一节,我们还会看到_disabled 这样的开差,用来调济更新的频率。

猜你在找的React相关文章