06Vue 源码解析3

Vue 源码解析3

模板编译

模板编译的主要目标是将模板(template)转为渲染函数(render)

template==>render

模板编译必要性

Vue2.0 需要用到 Vnode 描述视图以及各种交互,手写显然不切实际,因此用户只需编写类似 HTML 代码的 Vue 模板,通过编译器将模板转换为可返回 Vnode 的 render 函数。

体验模板编译

带编译器的版本中,可以使用 template 或 el 的方式生命模板,测试 demo

(function anonymous (
  ) {
    with (this) {
      return _c('div', { attrs: { "id": "demo" } }, [_c('h1', [_v("Vue模板编
    译")]),_v(" "),_c('p',[_v(_s(foo))]),_v(" "),_c('comp')],1)}
    })

输出结果大致如下:

(function anonymous () {
  with (this) {
    return _c('div', { attrs: { "id": "demo" } }, [
      _c('h1', [_v("Vue模板编译")]),
      _v(" "), _c('p', [_v(_s(foo))]),
      _v(" "), _c('comp')], 1)
  }
})
  • 元素节点使用 createElement 创建,别名 _c

  • 本文节点使用 createTextVNode 创建,别名 _v

  • 表达式先使用 toString 格式化,别名 _s

  • 其他渲染 helpers:src\core\instance\render-helpers\index.js

整体流程

compileToFunctions

若指定 template 或 el 选项,则会执行编译,platforms\web\entry-runtime-with-compiler.js

编译过程

编译分为三步:解析、优化和生成,src\compiler\index.js

测试 demo

模板编译过程

image.png

实现模板编译共有三个阶段:解析、优化和生成。

解析 - parse

解析器将模板解析为抽象语法树,基于 AST 可以做优化或者代码生成工作。

调试查看得到的 AST,/src/compiler/parser/index.js,结构如下:

image.png

解析器内部分了 HTML 解析器、文本解析器和过滤解析器,最主要是 HTML 解析器。

优化 - optimize

优化器的作用是在 AST 中找出静态子树并打上标记。静态子树是在 AST 中永远不变的节点,如纯文本节点。

标记静态子树的好处:

  • 每次重新渲染,不需要为静态子树创建新节点;

  • 虚拟 DOM 中 patch 时,可以跳过静态子树。

测试 demo

代码实现,src/compiler/optimizer.js - optimize

标记结束

image.png

代码生成 - generage

将 AST 转换成渲染函数中的内容,即代码字符串。

generate 方法生成渲染函数代码,src/compiler/codegen/index.js

生成的 code:

`_c('div',{attrs:{"id":"demo"}},[
_c('h1',[_v("Vue.js测试")]),
_c('p',[_v(_s(foo))])
])`

典型指令的实现:v-if、v-for

image.png

着重观察几个结构性指令的解析过程。

解析 v-if:parser/index.js

processIf 用于处理 v-if 解析:

function processIf (el) {
  const exp = getAndRemoveAttr(el, 'v-if')
  if (exp) {
    el.if = exp
    addIfCondition(el, {
      exp: exp,
      block: el
    })
  } else {
    if (getAndRemoveAttr(el, 'v-else') != null) {
      el.else = true
    }
    const elseif = getAndRemoveAttr(el, 'v-else-if')
    if (elseif) {
      el.elseif = elseif
    }
  }
}

解析结果:

image.png

代码生成,codegen/index.js

genIfConditions 等用于生产条件语句相关代码。

生成结果:

"with(this){return _c('div',{attrs:{"id":"demo"}},[
(foo) ? _c('h1',[_v(_s(foo))]) : _c('h1',[_v("no title")]),
_v(" "),_c('abc')],1)}"

解析 v-for:parser/index.js

processFor 用于处理 v-for 指令:

export function processFor (el: ASTElement) {
  let exp
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    const res = parseFor(exp)
    if (res) {
      extend(el, res)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `Invalid v-for expression: ${exp}`,
        el.rawAttrsMap['v-for']
      )
    }
  }
}

解析结果:v-for="item in items"

for:"items"

alias:"item"

image.png

代码生成,src\compiler\codegen\index.js

genFor 用于生成响应代码。

生成结果:

"with(this){return _c('div',{attrs:{"id":"demo"}},[_m(0),_v(" "),(foo)?_c('p',
[_v(_s(foo))]):_e(),_v(" "),
_l((arr),function(s){return _c('b',{key:s},[_v(_s(s))])})
,_v(" "),_c('comp')],2)}"

v-if、v-for 这些指令只能在编译器阶段处理,如果我们要在 render 函数处理条件或循环只能使用 if 和 for。

Vue.component('comp', {
  props: ['foo'],
  render (h) { // 渲染内容跟foo的值挂钩,只能⽤if语句
    if (this.foo == 'foo') {
      return h('div', 'foo')
    }
    return h('div', 'bar')
  }
})
(function anonymous (
) {
  with (this) {
    return _c('div', { attrs: { "id": "demo" } }, [_m(0), _v(" "), (foo) ? _c('p',
      [_v(_s(foo))]) : _e(), _v(" "), _c('comp')], 1)
  }
})

组件化机制

组件声明:Vue.component()

initAssetRegisters(Vue) src/core/global-api/assets.js,组件注册使用 extend 方法将配置转换为构造函数并添加到 components 选项。

组件实例创建及挂载

观察生成的渲染函数:

"with(this){return _c('div',{attrs:{"id":"demo"}},[
  _c('h1', [_v("虚拟DOM")]), _v(" "),
  _c('p', [_v(_s(foo))]), _v(" "),
  _c('comp') // 对于组件的处理并⽆特殊之处
  ], 1)}"

整体流程

首先创建的是跟实例,首次 _render() 时,会得到整棵树的 Vnode 结构,其中自定义组件相关的主要有:

整体流程:

new Vue() => $mount() => vm._render(h) => createElement() => createComponent() => patch => createElm => createComponent()

_createElement - src\core\vdom\create-element.js

_createElement 实际执行 Vnode 创建的函数,由于传入 tag 是非保留标签,因此判定为自定义组件通过 createComponent 去创建。

createComponent - src/core/vdom/create-component.js

创建组件 Vnode,保存了上一步处理得到的组件构造函数,props,事件等

创建组件实例

根组件执行更新函数时,会递归创建子元素和子组件,入口 createElm。

createEle() core/vdom/patch.js line751

首次执行 _update() 时,patch() 会通过 createEle() 创建根元素,子元素创建研究从这里开始。

createComponent core/vdom/patch.js line144

自定义组件创建:

// 组件实例创建、挂载
if (isDef(i = i.hook) && isDef(i = i.init)) {
  i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
  // 元素引⽤指定vnode.elm,元素属性创建等
  initComponent(vnode, insertedVnodeQueue)
  // 插⼊到⽗元素
  insert(parentElm, vnode.elm, refElm)
  if (isTrue(isReactivated)) {
    reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
  }
  return true
}

总结

Vue源码学习使我们能够深入理解原理,解答很多开发中的疑惑,规避很多潜在的错误,写出更好的代码。学习大神的代码,能够学习编程思想,设计模式,训练基本功,提升内力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342