Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue的template中无法使用扩展运算符? #1

Open
Hilbertangers opened this issue Jan 28, 2021 · 0 comments
Open

vue的template中无法使用扩展运算符? #1

Hilbertangers opened this issue Jan 28, 2021 · 0 comments
Labels
good first issue Good for newcomers

Comments

@Hilbertangers
Copy link
Owner

导火索

某天需要完成一个从0~4的v-for循环:

// 🌰
<template>
  <div>
    <p>0</p>
    .
    .
    .
    <p>4</p>
  </div>
</template>

本着一行代码搞定的想法,遂写入如下代码:

<template>
  <div>
   <p v-for="item in [...Array(5)].map((t,i) => i)">{{item}}</p>
  </div>
</template>

却只得到了一个div标签,一个p标签都没有渲染出来!事实上把这段代码放到chrome中打印可以得到如下:

> [...Array(5)].map((t,i) => i)
> [0, 1, 2, 3, 4]

那么哪一步出问题了呢?

排查

1.理解语法

[...Array(5)]这个代码的含义是扩展一个长度为5的空数组,得到一个

var arr = [undefined, undefined, undefined, undefined, undefined]

换成es5代码则为:

Array.apply(null, Array(5));
[].concat.apply([], Array(5));

再将arr执行map循环,得到[0, 1, 2, 3, 4]

2.找到罪魁祸首

既然语法没有错误,我决定深入vue源码,找到罪魁祸首的源代码,看看它对我的[...Array(5)]做了什么。

a.谁在编译

我出错的项目是使用webpack打包的vue项目:

// main.js
import Vue from 'vue';

首先我们需要知道vue的编译器和运行时是分开的(vue构建版本)。那么我们导入的是什么包呢?

因为vue包的package.json设置了main属性为dist/vue.runtime.common.js ,故webpack默认使用的

vue.runtime.common.js运行时版本,所以需要借助vue-loader来编译我们*.vue文件内的template。

b.template编译概括

在追责vue-loader之前,我们先简单介绍下vue的模板是怎么变成真实DOM的。

template => AST => optimize => code => render => vnode => dom

简单概况下一共有6个步骤:

1. Template => AST

第一步将对模板做解析,使用一个parse函数,生成AST。

2.AST => optimize

第二步将AST代码进行优化optimize,深度遍历AST树,为其中的静态节点进行标记staticstaticRoot属性,为以后模板的更新起到优化作用。

3.optimize => code

第三步将优化后的AST树转换成运行时执行的code字符串,const code = generate(ast, options)

4.code => render

第四步利用compileToFunctions将code字符串转换为render函数。

5.render => vnode

第五步在$mount挂载的时候会新建一个watcher实例,初始化或监听到数据变化时就会执行_update,它内部会执行_render,从而调用我们的render函数生成vnode。

6.vnode => dom

最后一步,_update拿到vnode之后执行patch,利用patch进行比较,再借助createElm创建真实dom。

c.vue-loader 哪错了

打开vue-loader,在example/source.vue里输入我们的🌰,运行它的dev进行调试,。

按照编译步骤概况思路走,我们很容易的就能找到各阶段的编译结果并打印。

(为了不浪费时间,我们用二分的思路来查找问题发生在哪一阶段!)

1.code代码串(lib/loaders/templateLoader.js)

.

.

emmm找不到!

可以看到vue-loader并没有把模板编译器放到自己代码里,而是写在了一个插件vue-template-compiler

  // allow using custom compiler via options
  const compiler = options.compiler || require('vue-template-compiler')

然后将compiler传入一个@vue/component-compiler-utils工具中,主要的步骤都在这里面执行。

const { compileTemplate } = require('@vue/component-compiler-utils')
.
const finalOptions = {
	compiler,
	...
}
.
const compiled = compileTemplate(finalOptions)

打开@vue/component-compiler-utils:

很容易的就能找到lib/compileTemplate.ts内的actuallyCompile,编译操作都在这里面。

很容易的找到:

const { render, staticRenderFns, tips, errors } = compile(
  source,
  finalCompilerOptions
)

这里的render就是我们要找的code,让我们把它打印出来看看:

// code
with(this){return _c('div',_l(([...Array(5)].map((t,i) => i)),function(item){return _c('p',[_v(_s(item))])}),0)}

可以看到code中[...Array(5)]还没有执行,那么问题就不在code代码串,让我们继续找下一个~

2.render函数

继续看actuallyCompile,得到code以后,通过定义一个toFunction将它转换成render函数,然后又使用了一个插件:

const transpile = require('vue-template-es2015-compiler')

let code = transpile(
`var __render__ = ${toFunction(render)}\n` +
`var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`,
finalTranspileOptions
) + `\n`

此时的这个code就是我们可以交给挂载去执行的render函数,让我们打印它看一看:

// render
var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    _vm._l(
      [].concat(Array(5)).map(function(t, i) {
        return i
      }),
      function(item) {
        return _c("p", [_vm._v(_vm._s(item))])
      }
    ),
    0
  )
}
var staticRenderFns = []
render._withStripped = true

注意!!!我们的[...Array(5)]被转换成[].concat(Array(5))这个东西了!!

> [].concat(Array(5))
> []

因为是空数组,所以map出来也是空数组,所以渲染不出p标签。

那么transpile也就是vue-template-es2015-compiler为什么会这么做呢?github由readme可以看出这个包的功能是:

1.使用Buble库来支持模板中的ES2015语法;

2.删除code代码块的with;

好!凶手已经呼之欲出,就是Buble!这是一个受babel启发的ES6语法编译器,introduction

我去找了它的issues81作者完全没有想改的意图😡。

结案!解决方案的话暂时把[...Array(5)]放到js里利用babel把~

d.template编译详细解读

黄轶vue源码分享
言川源码分析
@Hilbertangers Hilbertangers added the good first issue Good for newcomers label Jan 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

1 participant