站在伟人的肩膀上剖析vue3,次要用来记录本人看完HcySunYang文档的读后感并且加深本人的了解。倡议大家浏览 原文地址

一个组件最外围的就是 render 函数,残余的其余内容,如data、compouted、props 等都是为render函数提供数据起源服务的,render函数能够产出 Virtual DOM。

Virtual DOM 最终都要渲染成实在的DOM,这个过程就叫做patch。

什么是 Vnode

vue首先会将template进行编译,这其中包含parse、optimize、generate三个过程。
parse会应用正则等形式解析template模版中的指令、class、style等数据,造成AST。

<ul id='list' class='item'>  <li class='item1'>Item 1</li>  <li class='item3' style='font-size: 20px'>Item 2</li></ul>
var element = {  tag: 'ul', // 节点标签名  data: { // DOM的属性,用一个对象存储键值对    class: 'item',    id: 'list'  },  children: [ // 该节点的子节点    {tag: 'li', data: {class: 'item1'}, children: {tag: null, data: null, children: "Item 1"}},    {tag: 'li', data: {class: 'item3', style: 'font-size: 20px'}, children: {tag: null, data: null, children: "Item 2"}},  ]}

如下面代码所述,一个 template 模版能够用AST语法树进行描述。咱们应用 tag 属性来存储标签的名字,用 data 属性来存储该标签的附加信息,比方 style、class、事件等。children 用来形容子节点。

Vnode 品种

下面讲述的是一些一般HTML标签,比方div、span、p这种,然而在理论的代码开发过程中咱们会抽离出很多组件

<div>  <MyComponent /></div>

像这种组件依然须要应用 VNode 来形容 <MyComponent/>。并给此类用来形容组件的 VNode 增加一个标识,以便在挂载的时候有方法辨别一个 VNode 到底是一般的 html 标签还是组件。

const elementVNode = {  tag: 'div',  data: null,  children: {    tag: MyComponent,    data: null  }}

因而咱们能够应用 tag 来判断将要挂载的内容,通过不同的渲染函数去渲染对应的HTML构造。

组件能够分为两类,一种是函数式组件,一种是有状态组件。函数式组件只是一个纯函数,没有本身的状态,只接管内部数据;有组态组件是一个类,须要实例化,有本人的状态。
// 函数式组件function MyComponent(props) {}// 有状态组件class MyComponent {}

除组件之外,还有两种类型须要形容,即 Fragment 和 Portal。

在vue3中 template 曾经不须要一个大盒子进行包裹所有的html内容了,也就是咱们简称的根元素。

<template>  <td></td>  <td></td>  <td></td></template>

模板中不仅仅只有一个 td 标签,而是有多个 td 标签,即多个根元素,须要引入一个形象元素,也就是咱们要介绍的 Fragment。将 tag 标记为 Fragment,只须要把该VNode 的子节点渲染到页面。

再来看看 Portal,什么是 Portal 呢?就是把子节点渲染到给定的指标。

const portalVNode = {  tag: Portal,  data: {    target: '#app-root'  },  children: {    tag: 'div',    data: {      class: 'overlay'    }  }}

无论在何处应用 <Overlay/> 组件,它都会把内容渲染到 id="app-root" 的元素下。

总的来说,咱们能够把 VNode 分成五类,别离是:html/svg 元素、组件、纯文本、Fragment 以及 Portal:

优化: flags 作为 VNode 的标识

既然 VNode 有类别之分,咱们就有必要应用一个惟一的标识,来表明某一个 VNode 属于哪一类。同时给 VNode 增加 flags 也是 Virtual DOM 算法的优化伎俩之一。

在vue2中辨别vnode 类型步骤:

  1. 拿到vnode 尝试把它作为组件去解决,如果胜利的创立了组建,那阐明该vnode 就是组建的vnode
  2. 如果没有胜利创立,则查看 vnode.tag 是否有定义,如果有定义当作一般标签解决
  3. 如果 vnode.tag 没有定义则查看是否是凝视节点
  4. 如果不是正文节点,则把他当作文本看待。

以上这些判断都是在挂载(或patch)阶段进行的,换句话说,一个 VNode 到底形容的是什么是在挂载或 patch 的时候才晓得的。这就带来了两个难题:无奈从 AOT 的层面优化、开发者无奈手动优化。

为了解决这个问题,咱们的思路是在 VNode 创立的时候就把该 VNode 的类型通过 flags 表明,这样在挂载或 patch 阶段通过 flags 能够间接防止掉很多耗费性能的判断。这也是咱们须要讲的渲染器

// mount 函数的作用是把一个 VNode 渲染成实在 DOM,依据不同类型的 VNode 须要采纳不同的挂载形式export function mount(vnode, container) {  const { flags } = vnode  if (flags & VNodeFlags.ELEMENT) {    // 挂载一般标签    mountElement(vnode, container)  } else if (flags & VNodeFlags.COMPONENT) {    // 挂载组件    mountComponent(vnode, container)  } else if (flags & VNodeFlags.TEXT) {    // 挂载纯文本    mountText(vnode, container)  } else if (flags & VNodeFlags.FRAGMENT) {    // 挂载 Fragment    mountFragment(vnode, container)  } else if (flags & VNodeFlags.PORTAL) {    // 挂载 Portal    mountPortal(vnode, container)  }}

至此,咱们曾经对 VNode 实现了肯定的设计,目前为止咱们所设计的 VNode 对象如下:

export interface VNode {  // _isVNode 属性在上文中没有提到,它是一个始终为 true 的值,有了它,咱们就能够判断一个对象是否是 VNode 对象  _isVNode: true  // el 属性在上文中也没有提到,当一个 VNode 被渲染为实在 DOM 之后,el 属性的值会援用该实在DOM  el: Element | null  flags: VNodeFlags  tag: string | FunctionalComponent | ComponentClass | null  data: VNodeData | null  children: VNodeChildren  childFlags: ChildrenFlags}

h 函数创立 VNode

组件 的 flags 类型

下面讲到了如何应用vnode 去形容一个template构造,咱们能够用函数去管制如何主动生成vnode,这也是vue的一个外围api,vue3 h函数

h函数返回一个”虚构节点“,通常缩写为 VNode:一个一般对象,其中蕴含向 Vue 形容它应在页面上渲染哪种节点的信息,包含所有子节点的形容。它的目标是用于手动编写的渲染函数:

render() {  return h('h1', null, '')}
function h() {  return {    _isVNode: true,    flags: VNodeFlags.ELEMENT_HTML,    tag: 'h1',    data: null,    children: null,    childFlags: ChildrenFlags.NO_CHILDREN,    el: null  }}

h 函数返回代码中的一些 vnode 信息,须要承受三个参数 tag、data 和 children。其中只须要确定 flags 和 childFlags 类型即可,其余的都是默认或者通过参数传递。

通过 tag 来确定 flags

须要留神的一点是:在 vue2 中用一个对象(object)作为组件的形容,在vue3中,有状态的组件是一个继承基类的类,是 Vue2 的对象式组件,咱们通过查看该对象的 functional 属性的虚实来判断该组件是否是函数式组件。在 Vue3 中,因为有状态组件会继承基类,所以通过原型链判断其原型中是否有 render 函数的定义来确定该组件是否是有状态组件

// 兼容 Vue2 的对象式组件if (tag !== null && typeof tag === 'object') {  flags = tag.functional    ? VNodeFlags.COMPONENT_FUNCTIONAL       // 函数式组件    : VNodeFlags.COMPONENT_STATEFUL_NORMAL  // 有状态组件} else if (typeof tag === 'function') {  // Vue3 的类组件  flags = tag.prototype && tag.prototype.render    ? VNodeFlags.COMPONENT_STATEFUL_NORMAL  // 有状态组件    : VNodeFlags.COMPONENT_FUNCTIONAL       // 函数式组件}

children 的 flags 类型

children 能够分为四种

  1. children 是一个数组 h('ul', null, [h('li'),h('li')])
  2. children 是一个 vnode 对象 h('div', null, h('span'))
  3. children 是一个一般的文本字符串 h('div', null, '我是文本')
  4. 没有children h('div')

children 是数组能够分为两种,一种是有key、另一种就是无key的状况,都会被标记为KEYED_VNODES, 没有key的会调用 normalizeVNodes 进行人工干预生成key

// 多个子节点,且子节点应用keychildFlags = ChildrenFlags.KEYED_VNODESchildren = normalizeVNodes(children)

render 函数 渲染Vnode成实在 DOM

渲染器的工作次要分为两个阶段:mount, path。如果旧的vnode存在,则会应用新的vnode与旧的vnode进行比照,试图以最小的资源开销实现 DOM 更新,这个过程就叫做patch,如果旧的 vnode 不存在,则间接将新的 vnode 挂载成全新的 DOM, 这给过程叫mount

render 函数接管两个参数,第一个参数是将要被渲染的 vnode 对象,第二个参数是一个用来承载内容的容器,通常也叫挂载点,

function render(vnode, container) {  const prevVNode = container.vnode  if (prevVNode == null) {    if (vnode) {      // 没有旧的 VNode,只有新的 VNode。应用 `mount` 函数挂载全新的 VNode      mount(vnode, container)      // 将新的 VNode 增加到 container.vnode 属性下,这样下一次渲染时旧的 VNode 就存在了      container.vnode = vnode    }  } else {    if (vnode) {      // 有旧的 VNode,也有新的 VNode。则调用 `patch` 函数打补丁      patch(prevVNode, vnode, container)      // 更新 container.vnode      container.vnode = vnode    } else {      // 有旧的 VNode 然而没有新的 VNode,这阐明应该移除 DOM,在浏览器中能够应用 removeChild 函数。      container.removeChild(prevVNode.el)      container.vnode = null    }  }}
旧的vnode新的vnode操作
调用 mount 函数
移除DOM
调用 patch 函数

渲染器的责任十分之大,是因为它不仅仅是一个把 VNode 渲染成实在 DOM 的工具,它还负责以下工作:

  • 管制局部组件生命周期钩子的调用,组件的挂载,卸载调用机会。
  • 多端渲染的桥梁。
    自定义渲染器的实质就是把特定平台操作dom 的办法从外围算法中抽离,并提供可配置的计划
  • 异步渲染有间接的关系
    vue3 的异步渲染是基于调度器的实现,若要实现异步渲染,组件的挂载酒不能同步进行,dom 的变更就要在适合的机会,???
  • 蕴含最外围的算法 Diff 算法

渲染一般标签元素

下面在讲 flags 的时候也说过,不同tag 会被h 函数打上 flags,通过 flags 的不同咱们就能够辨别出须要渲染的内容是什么类型。不同的vnode 采纳不同的挂载函数

接下来咱们将围绕这三个问题去实现一般标签元素渲染的过程

  1. VNode 被渲染为实在DOM之后,没有援用实在DOM元素
  2. 没有将 VNodeData 利用到实在DOM元素上
  3. 没有持续挂载子节点,即 children

问题1

function mountElement(vnode, container) {  const el = document.createElement(vnode.tag)  vnode.el = el  container.appendChild(el)}

问题2 通过遍历 VNodeData, switch 取值作用到元素上

// 拿到 VNodeData  const data = vnode.data  if (data) {    // 如果 VNodeData 存在,则遍历之    for(let key in data) {      // key 可能是 class、style、on 等等      switch(key) {        case 'style':          // 如果 key 的值是 style,阐明是内联款式,一一将款式规定利用到 el          for(let k in data.style) {            el.style[k] = data.style[k]          }        break      }    }  }

问题2 递归挂载子节点

// 拿到 children 和 childFlagsconst childFlags = vnode.childFlagsconst children = vnode.children// 检测如果没有子节点则无需递归挂载if (childFlags !== ChildrenFlags.NO_CHILDREN) {    if (childFlags & ChildrenFlags.SINGLE_VNODE) {      // 如果是单个子节点则调用 mount 函数挂载      mount(children, el)    } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {      // 如果是单多个子节点则遍历并调用 mount 函数挂载      for (let i = 0; i < children.length; i++) {        mount(children[i], el)      }}

arrtibutes跟props的区别:浏览器在加载页面之后会对页面中的标签进行解析,并生成与之相符的 DOM 对象,每个标签中都可能蕴含一些属性,如果这些属性是规范属性,那么解析生成的DOM对象中也会蕴含与之对应的属性。如果是非规范属性,那么则会当作是props解决

对于其余的比方class、arrtibutes、props、事件的解决能够移步文档

渲染纯文本、Fragment 和 Portal

  1. 纯文本
    纯文本最简略,只须要将元素增加到页面上即可

    function mountText(vnode, container) {  const el = document.createTextNode(vnode.children)  vnode.el = el  container.appendChild(el)}
  2. Fragment
    对于Fragment 不须要渲染,只须要挂载children,如果有多个子节点的话遍历挂载调用mount即可,如果是空节点则创立一个空的文本节点 调用mountText 挂载。

Fragment 类型的 VNode 来说,当它被渲染为实在DOM之后,其 el 属性的援用

如果只有一个节点,那么 el 属性就指向该节点;如果有多个节点,则 el 属性值是第一个节点的援用;如果片段中没有节点,即空片段,则 el 属性援用的是占位的空文本节点元素

那么这样设计有什么意义呢?

在 patch 阶段对 dom 元素进行挪动时,应该确保其放到正确的地位,而不应该始终应用 appendChild 函数,有时须要 insertBefore 函数,这时候咱们就须要拿到相应的节点利用,这时候 vnode.el 属性是必不可少的,即便 fragment 没有子节点咱们仍然须要一个占位的空文本节点作为地位的援用。

  1. Portal
    Portal挂载跟Fragment 一样,只须要特地留神的是Portal的tag是挂载点。

    Portal 类型的 VNode 其 el 属性应该指向谁

protal 所形容的内容能够挂载到任何地位,但依然须要一个占位元素,并且 protal 类型的vnode 的 el 属性应该指向该占位元素 这是因为 Portal 的另外一个个性:尽管 Portal 的内容能够被渲染到任意地位,但它的行为依然像一般的DOM元素一样,如事件的捕捉/冒泡机制依然依照代码所编写的DOM构造施行。要实现这个性能就必须须要一个占位的DOM元素来承接事件。但目前来说,咱们用一个空的文本节点占位即可

渲染组件

还是通过vnode.flags 来判断挂载的vnode 是否属于有状态组件还是函数组件。

挂载一个有状态组件,也就是class 类组件

class MyComponent {  render() {    return h(      'div',      {        style: {          background: 'green'        }      },      [        h('span', null, '我是组件的题目1......'),        h('span', null, '我是组件的题目2......')      ]    )  }}
// 组件挂载function mountStatefulComponent(vnode, container) {  // 创立组件实例  const instance = new vnode.tag()  // 渲染VNode  instance.$vnode = instance.render()  // 挂载  mount(instance.$vnode, container)  // el 属性值 和 组件实例的 $el 属性都援用组件的根DOM元素  instance.$el = vnode.el = instance.$vnode.el}

函数式组件间接返回vnode 的函数

function MyFunctionalComponent() {  // 返回要渲染的内容形容,即 VNode  return h(    'div',    {      style: {        background: 'green'      }    },    [      h('span', null, '我是组件的题目1......'),      h('span', null, '我是组件的题目2......')    ]  )}

如下是 函数式组件 mountFunctionalComponent 函数的实现:

function mountFunctionalComponent(vnode, container, isSVG) {  // 获取 VNode  const $vnode = vnode.tag()  // 挂载  mount($vnode, container)  // el 元素援用该组件的根元素  vnode.el = $vnode.el}

patch 函数更新渲染的DOM

上一节讲没有旧的 vnode, 应用 mount 函数挂载全新的 vnode。那么有 vnode 应该以何种适合的形式更新 DOM,这也就是咱们常说的 patch。

当应用 render 渲染一个全新的 vnode, 会调用 mount 函数挂载该vnode,同时让容器元素存储该vnode 对象的援用。这样在此调用渲染器渲染新的vnode 对象到雷同的容器元素,因为旧的 vnode 曾经存在,所以会调用 patch 函数以适合的形式进行更新

计划一,vnode之间有类型之分,只有雷同类型的vnode才有比照意义,如果不同,最优的计划就是用新的 vnode 替换旧的 vnode。如果新旧 VNode 的类型雷同,则依据不同的类型调用不同的比对函数

更新一般标签元素

计划二,不同的标签渲染的内容不同,比照没有意义

例如 ul 标签下只能渲染 li 标签,所以拿 ul 标签和一个 div 标签进行比对是没有任何意义的,这种状况下咱们不会对旧的标签元素打补丁,而是应用新的标签元素替换旧的标签元素。

如果新旧 vnode 标签雷同,那么不同的只有 VNodeData 和 children。实质上还是对这两个值做比照。

更新 VNodeData 时的思路分为以下几步:

  • 第1步:当新的 VNodeData 存在时,遍历新的 VNodeData。
  • 第2步:依据新的 VNodeData 中的key,分贝尝试读取旧值和新值。即prevValue 和 nextValue
  • 第3步:应用switch...case 语句匹配不同的数据进行不同的更新操作。

以款式(style)的更新为例,如上代码所展现的更新过程是:

  1. 遍历新的款式数据,将新的款式数据全副利用到元素上
  2. 遍历旧的款式数据,将那些不存在新的款式数据中的款式从元素上移除

子节点的更新,次要是在patchElement函数中递归的调用patchChildren。留神对于子节点的比拟只能是 同层级 的比拟。

// 调用 patchChildren 函数递归地更新子节点  patchChildren(    prevVNode.childFlags, // 旧的 VNode 子节点的类型    nextVNode.childFlags, // 新的 VNode 子节点的类型    prevVNode.children,   // 旧的 VNode 子节点    nextVNode.children,   // 新的 VNode 子节点    el                    // 以后标签元素,即这些子节点的父节点  )

因为子节点的状态总共能够分为三种,一种是没有子节点,一种是子节点只有一个,最初一种就是子节点多个的状况,子节点同级比拟因而就会呈现九种状况。

实际上在整个新旧 children 的比对中,只有当新旧子节点都是多个子节点时才有必要进行真正的外围 diff,从而尽可能的复用子节点。 前面有章节也会着重解说diff如何尽可能的复用子节点。

更新纯文本、Fragment 和 Portal

  1. 纯文本
    纯文本的更新能够通过 DOM 对象的 nodeValue 属性读取或设置文本节点(或正文节点)的内容

    function patchText(prevVNode, nextVNode) {  // 拿到文本元素 el,同时让 nextVNode.el 指向该文本元素  const el = (nextVNode.el = prevVNode.el)  // 只有当新旧文本内容不统一时才有必要更新  if (nextVNode.children !== prevVNode.children) { el.nodeValue = nextVNode.children  }}
  2. Fragment
    因为 Fragment 没有包裹元素,只有子节点,所以咱们对 Fragment 的更新实质上就是更新两个片段的“子节点”。间接调用标签元素的patchChildren函数,只须要留神el的指向。
  • 如果新的片段 children 是单个子节点,则意味着其 vnode.children 属性的值就是 VNode 对象 nextVNode.el = nextVNode.children.el
  • 如果新的片段 children 是空文本节点。prevVNode.el 属性援用就是该空文本节点 nextVNode.el = prevVNode.el
  • 如果新的片段 children 是多个子节点。 nextVNode.el = nextVNode.children[0].el
  1. Portal
    portal 也是一样的,没有元素包裹只须要比拟子节点,并且留神el指向就能够nextVNode.el = prevVNode.el

如果新旧容器不同,才须要搬运。这块也就不扩大了,感兴趣的能够查看文档

更新组件

更新有状态组件

有状态组件更新能够分为两种,一种是 被动更新被动更新

被动更新:就是组件本身的状态产生扭转所导致的更新。例如data的变动等等状况

被动更新:就是组件内部因素导致的,例如props的扭转

1. 被动更新

当组件的状态变动时,咱们须要做的就是从新执行渲染函数并产出新的vnode,最初通过新旧vnode 实现真是dom的更新。

比方咱们须要更新这样的组件该如何做呢?

class MyComponent {  // 本身状态 or 本地状态  localState = 'one'  // mounted 钩子  mounted() {    // 两秒钟之后批改本地状态的值,并从新调用 _update() 函数更新组件    setTimeout(() => {      this.localState = 'two'      this._update()    }, 2000)  }  render() {    return h('div', null, this.localState)  }}

能够回顾一下组件的挂载步骤:

  1. 创立组件的实例
  2. 调用组件的 render 取得vnode
  3. 将 vnode 挂载到容器元素上
  4. el 属性值 和 组件实例的 $el 属性都援用组件的根DOM元素

咱们将所有的操作都封装到一个_update函数里。

function mountStatefulComponent(vnode, container, isSVG) {  // 创立组件实例  const instance = new vnode.tag()  instance._update = function() {    // 1、渲染VNode    instance.$vnode = instance.render()    // 2、挂载    mount(instance.$vnode, container, isSVG)    // 4、el 属性值 和 组件实例的 $el 属性都援用组件的根DOM元素    instance.$el = vnode.el = instance.$vnode.el    // 5、调用 mounted 钩子    instance.mounted && instance.mounted()  }  instance._update()}

当更新时只须要再次调用_update函数即,那么如何须要去判断一个组件是第一次渲染还是须要更新你,通过设置一个变量_mounted boolean类型用来标记即可。

function mountStatefulComponent(vnode, container, isSVG) {  // 创立组件实例  const instance = new vnode.tag()  instance._update = function() {    // 如果 instance._mounted 为真,阐明组件已挂载,应该执行更新操作    if (instance._mounted) {      // 1、拿到旧的 VNode      const prevVNode = instance.$vnode      // 2、重渲染新的 VNode      const nextVNode = (instance.$vnode = instance.render())      // 3、patch 更新      patch(prevVNode, nextVNode, prevVNode.el.parentNode)      // 4、更新 vnode.el 和 $el      instance.$el = vnode.el = instance.$vnode.el    } else {      // 1、渲染VNode      instance.$vnode = instance.render()      // 2、挂载      mount(instance.$vnode, container, isSVG)      // 3、组件已挂载的标识      instance._mounted = true      // 4、el 属性值 和 组件实例的 $el 属性都援用组件的根DOM元素      instance.$el = vnode.el = instance.$vnode.el      // 5、调用 mounted 钩子      instance.mounted && instance.mounted()    }  }  instance._update()}

组件的更新大抵能够分为三步:

  1. 获取旧的vnode
  2. 从新调用render 函数产生新的 vnode
  3. 调用patch 函数比照新旧 vnode

2. 被动更新

咱们能够在组件实例创立之后立刻初始化组件的 props,
instance.$props = vnode.data 这样子组件中就能够通过 this.$props.text 拜访从父组件传递进来的 props 数据

举个案例:

第一次渲染产出的 VNode 是:

const prevCompVNode = h(ChildComponent, {  text: 'one'})````第二次渲染产出的 VNode 是:

const prevCompVNode = h(ChildComponent, {
text: 'two'
})

因为渲染进去的tag都是组件,所以在 patch 函数外部会调用 patchComponent 函数进行更新```jsfunction patchComponent(prevVNode, nextVNode, container) {  // 查看组件是否是有状态组件  if (nextVNode.flags & VNodeFlags.COMPONENT_STATEFUL_NORMAL) {    // 1、获取组件实例    const instance = (nextVNode.children = prevVNode.children)    // 2、更新 props    instance.$props = nextVNode.data    // 3、更新组件    instance._update()  }}```有状态组件更新能够分为三步:1. 通过prevVNode.childredn 拿到组件实例2. 更新props,应用新的VNodeData从新设置组件实例的 `$props` 属性3. 因为组件的 `$props` 曾经更新,所以在调用组件的 _update 办法,让组件从新渲染> 不同的组件类型,则须要移除从新渲染,组件的unmounted生命周期函数将会执行。```jsfunction replaceVNode(prevVNode, nextVNode, container) {  container.removeChild(prevVNode.el)  // 如果将要被移除的 VNode 类型是组件,则须要调用该组件实例的 unmounted 钩子函数  if (prevVNode.flags & VNodeFlags.COMPONENT_STATEFUL_NORMAL) {    // 类型为有状态组件的 VNode,其 children 属性被用来存储组件实例对象    const instance = prevVNode.children    instance.unmounted && instance.unmounted()  }  mount(nextVNode, container)}```**特别强调`shouldUpdateComponent`**在vue2中没有shouldUpdateComponent这个生命周期。在某些状况下,组件不须要更新,然而组件仍旧跑了一次update。因而咱们应用patchFlag和props的简略比照等形式来决定是否update。这就是shouldUpdateComponent的作用。#### 更新函数式组件无论是有状态组件和函数式组件都是组件通过执行 `_update` 产出新旧vnode做比照,从而实现更新。1. 函数式组件承受props只能在mount阶段传递过来```jsfunction mountFunctionalComponent(vnode, container, isSVG) {  // 获取 props  const props = vnode.data  // 获取 VNode 执行函数并将props传递到函数中  const $vnode = (vnode.children = vnode.tag(props))  // 挂载  mount($vnode, container, isSVG)  // el 元素援用该组件的根元素  vnode.el = $vnode.el}```2. 函数式组件通过定义一个函数在VNode中定义一个handle函数将整个挂载的过程都实现,下次更新的时候只须要执行vnode的handle 函数即可。```jsvnode.handle = {  prev: null,  next: vnode,  container,  update() {/*...*/}}```参数阐明:- prev: 存储旧的函数式组件vnode,在首次挂载时,没有旧的vnode- next: 存储新的函数式组件vnode, 在首次挂载时,被赋值为以后正在挂载的函数式组件- container: 存储的挂载容器具体的实现过程也根本和有状态组件相似,具体的可参考[文档](http://hcysun.me/vue-design/zh/renderer-patch.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6%E7%9A%84%E6%9B%B4%E6%96%B0)## 下一节下一节次要介绍diff算法如果尽可能的复用dom元素