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

紧接上节,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的分析就结束了。

流程图如下
深入Preact源码分析(三)Preact组件实例到DOM的过程_第1张图片

你可能感兴趣的:(前端框架)