博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
preact源码学习(1)
阅读量:6672 次
发布时间:2019-06-25

本文共 5692 字,大约阅读时间需要 18 分钟。

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

preact的一些模块非常简单。

//vnode.jsexport function VNode() {}

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

//util.js//糅杂,相当于es6的Object.assignexport 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.jsexport 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;//forceUpdateexport 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.jsimport { 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 
; * } * } */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 这样的开差,用来调济更新的频率。

转载地址:http://kxlxo.baihongyu.com/

你可能感兴趣的文章
Linux-进程间的通信-信号集函数【转】
查看>>
js2word/html2word的简单实现
查看>>
jQuery.extend和jQuery.fn.extend的区别?
查看>>
职业发展
查看>>
Linux下环境变量设置
查看>>
phonegap 安装和使用eclipse
查看>>
ASP.NET MVC使用动态产生meta
查看>>
WIN7无法访问共享打印机及文件的解决办法
查看>>
C语言基础(9)-字符串格式化输入和输出
查看>>
【转】使用import scope解决maven继承(单)问题
查看>>
.NET Core微服务之路:让我们对上一个Demo通讯进行修改,完成RPC通讯
查看>>
你 我 我们 技术 让世界变得更好一些
查看>>
统计建模与R软件-附R原程序
查看>>
安装MSSQL2008出现的问题记录
查看>>
iOS 中NSRunLoop的使用
查看>>
WCF安全:通过 扩展实现用户名密码认证
查看>>
模块化JavaScript设计模式(一)
查看>>
Android4.2.2NDK和源代码开发jni的异同
查看>>
oracle 删除已连接用户
查看>>
这家棋牌游戏厂商冲刺港交所:上半年营收近3亿 曾李青是投资人
查看>>