深入Preact源码分析(三)Preact组件实例到DOM的过程

标签: Preact 源码
11人阅读 评论(0) 收藏 举报
分类:

紧接上节,Preact组件从vnode到真实html的过程发生了什么?

...
// buildComponentFromVNode方法内部
// buildComponentFromVNode(undefined, vnode, {}, false);
c = createComponent(vnode.nodeName, props, context);// 创建组件
setComponentProps(c, props, SYNC_RENDER, context, mountAll);
dom = c.base;
    return dom;
....

从上节组件变成真实dom的过程中最重要的函数就是createComponentsetComponentProps。我们可以发现,在先后执行了createComponentsetComponentProps后,真实dom就是c.base了。那么
这个createComponent干了什么?去掉一些初始渲染时不会去执行的代码,简化后的代码如下:

// 如果是用class定义的那种有生命周期的组件,上文代码中的```vnode.nodeName```其实就是我们定义的那个class。
export function createComponent(Ctor, props, context) {
    let inst;
    if (Ctor.prototype && Ctor.prototype.render) {
        // 正常的组件 class xxx extends Component{} 定义的
        //首先是对自己的组件实例化
        inst = new Ctor(props, context);
        //然后再在我们实例化的组件,去获得一些Preact的内置属性(props、state,这两个是挂在实例上的)和一些内置方法(setState、render之类的,这些方法是挂在原型上的)
        Component.call(inst, props, context);
    } else {
        // 无状态组件
        //无状态组件是没有定义render的,它的render方法就是这个无状态组件本身
        inst = new Component(props, context);
        inst.constructor = Ctor;
        inst.render = doRender;
    }
    return inst;
}

function doRender(props, state, context) {
    // 无状态组件的render方法就是自己本身
    return this.constructor(props, context);
}

Component的定义如下。通过上面和下面的代码可以知道,createComponent的主要作用就是让我们编写的class型和无状态型组件实例化,
这个实例是具有相似的结构。并供后面的setComponentProps去使用产生真实dom。

// Component的定义
export function Component(props, context) {
    this._dirty = true;// 这个东西先不管,应该是和diff有关
    this.context = context;// 类似react的context
    this.props = props;
    this.state = this.state || {};
}
// 这里的extend就是一个工具函数,把setState、forceUpdate、render方法挂载到原型上
extend(Component.prototype,{
    setState(state,callback){},
    forceUpdate(callback){},
    render() {}
})

setComponentProps产生真实dom的过程。

setComponentProps(c, props, SYNC_RENDER, {}, false);

export function setComponentProps(component, props, opts, context, mountAll) {
    // 同理去除条件不成立的代码,只保留首次渲染时运行的关键步骤
    if (!component.base || mountAll) {
        // 可见。componentWillMount生命周期方法只会在未加载之前执行,
        if (component.componentWillMount) component.componentWillMount();
    }
    renderComponent(component, SYNC_RENDER, mountAll);
}

由上面代码可见,setComponentProps内部,实际上关键是调用了renderComponent方法。renderComponent逻辑有点绕,
精简版代码如下。

renderComponent主要逻辑简单来说如下:
1、调用组件实例的render方法去产生vnode。

2、如果这个组件产生的vnode不再是组件了。则通过diff函数去产生真实dom并挂载(前面已经分析过)diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true);

3、如果这个组件的子vnode还是子组件的话。则再次调用setComponentPropsrenderComponent去进一步生成真实dom,直到2中条件成立。(判断步骤和2、3类似),但是有点区别的是。这种调用代码是

component._component = inst = createComponent(childComponent, childProps, context);
setComponentProps(inst, childProps, NO_RENDER, context, false);// 不渲染。只是去执行下生命周期方法,在这个setComponentProps内部是不调用 renderComponent的。 至于为啥。。暂时我也不知道。NO_RENDER标志位
renderComponent(inst, SYNC_RENDER, mountAll, true);

精简版代码

export function renderComponent(component, opts, mountAll, isChild) {
    // 这个函数其实很长有点复杂的,只保留了初次渲染时执行的部分和关键的部分。
        // 调用组件的render方法,返回vnode
        rendered = component.render(props, state, context);//*****
        let childComponent = rendered && rendered.nodeName,base;
        if (typeof childComponent === 'function') {
            // 子节点也是自定义组件的情况
            let childProps = getNodeProps(rendered);
                component._component = inst = createComponent(childComponent, childProps, context);
                setComponentProps(inst, childProps, NO_RENDER, context, false);// 不渲染啊。只是去执行下生命周期方法
                renderComponent(inst, SYNC_RENDER, mountAll, true);// 对比  renderComponent(component, SYNC_RENDER, mountAll);
        } else {
            base = diff(。。。);// 挂载
        }
        if (!isUpdate || mountAll) {// 成立
        mounts.unshift(component);// 把已经挂载的组件实例存进mounts数组
        }
        component.base = base; //把真实dom挂载到base属性上
        if (!diffLevel && !isChild) flushMounts();
}

前面看到了componentWillMount生命周期了,那么componentDidMount这个生命周期呢?它就是在flushMounts。这个if语句成立的条件是在祖先组件并且初次渲染时才执行(初次渲染的diffLevel值为0)。

export function flushMounts() {
    let c;
    while ((c = mounts.pop())) {
        if (options.afterMount) options.afterMount(c);
        if (c.componentDidMount) c.componentDidMount();
    }
}

flushMounts中的mounts就是当前挂载的组件的实例。它是一个栈的结构并依次出栈执行componentDidMount。所以,
这就能说明了Preact(React也一样)父子组件的生命周期执行顺序了 parentWillMount -> parentRender -> childWillMount -> childRender -> childDidMount -> parentDidParent。

至此组件类型的vnode产生真实dom的分析就结束了。

流程图如下
这里写图片描述

查看评论

从 React 到 Preact 迁移指南

作者 | 刘凯专注 Web 前端技术,崇尚一目了然的设计。为什么选 PreactFast 3kB alternative to React with the same ES6 API.React 的 ...
  • y4x5M0nivSrJaY3X92c
  • y4x5M0nivSrJaY3X92c
  • 2017-12-12 00:00:00
  • 536

React与Preact差异之 -- setState

Preact是React的轻量级实现,是React比较好的替代者之一,有着体积小的优点,当然与React之间一定会存在实现上的差异,本文介绍了在 setState 方面的差异之处。 源码分析 ...
  • qq3401247010
  • qq3401247010
  • 2017-10-23 12:29:55
  • 175

「React」与(Preact)差异之setState

react是React的轻量级实现,是React比较好的替代者之一,有着体积小的优点,当然与React之间一定会存在实现上的差异,本文介绍了在 setState 方面的差异之处。React关键代码:s...
  • Bg70PVnyBv1
  • Bg70PVnyBv1
  • 2018-03-23 00:00:00
  • 47

前端框架三巨头年度走势对比:Vue 增长率最高

Javascriptreport 近日发布了一篇文章来比较流行前端框架的走势。走势图是通过统计最新的数据,并与前一年的数据进行比较而生成。数据来源于 npm trends 的每日下载量。前端框架三巨头...
  • oschina2017
  • oschina2017
  • 2018-02-08 11:17:29
  • 151

深入Preact源码分析(二)virtualDOM如何变为真实dom

一个简单的Preact代码如下 // 一个简单的Preact demo import { h, render, Component } from 'preact'; class Clock ext...
  • flytam
  • flytam
  • 2018-04-17 21:04:30
  • 9

深入Preact源码分析(五)非组件类型的diff解析

非组件节点的diff分析 diff的流程,我们从简单到复杂进行分析 通过前面几篇文章的源码阅读,我们也大概清楚了diff函数参数的定义和component各参数的作用 /** * @par...
  • flytam
  • flytam
  • 2018-04-20 16:45:23
  • 8

React 源码剖析系列 - 不可思议的 react diff

著作权归作者所有。 商业转载请联系作者获得授权,非商业转载请注明出处。 作者:twobin 链接:http://zhuanlan.zhihu.com/purerender/20346379 来...
  • yczz
  • yczz
  • 2015-11-17 12:56:15
  • 9052

react 入门-创建元素

为了能让react在浏览器中运行,需要引入两个js库,分别是 React和ReactDom。
  • teamlet
  • teamlet
  • 2017-12-13 17:16:46
  • 575

深入Preact源码分析(四)setState发生了什么

setState发生了什么 setState(state, callback) { let s = this.state; if (!this.prevState) this.p...
  • flytam
  • flytam
  • 2018-04-17 21:12:04
  • 4

React源码分析3 — React组件插入DOM流程

React组件插入流程1 简介React广受好评的一个重要原因就是组件化开发,一方面分模块的方式便于协同开发,降低耦合,后期维护也轻松;另一方面使得一次开发,多处复用称为现实,甚至可以直接复用开源Re...
  • u013510838
  • u013510838
  • 2017-02-23 18:08:37
  • 2318
    个人资料
    持之以恒
    等级:
    访问量: 6911
    积分: 491
    排名: 10万+
    文章存档