组件相关

函数式组件

functional attribute 在单文件组件 (SFC) <template> 已被移除
{ functional: true } 选项在通过函数创建组件已被移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用 <dynamic-heading> 组件,负责提供适当的标题 (即:h1,h2,h3,等等),在 2.x 中,这可能是作为单个文件组件编写的:
// Vue 2 函数式组件示例
export default {
functional: true,
props: ['level'],
render(h, { props, data, children }) {
return h(`h${props.level}`, data, children)
}
}

// Vue 2 函数式组件示例使用 <template>
<template functional>
<component
:is="`h${props.level}`"
v-bind="attrs"
v-on="listeners"
/>
</template>

<script>
export default {
props: ['level']
}
</script>

现在在 Vue 3 中,所有的函数式组件都是用普通函数创建的,换句话说,不需要定义 { functional: true } 组件选项。
他们将接收两个参数:props 和 context。context 参数是一个对象,包含组件的 attrs,slots,和 emit property。
此外,现在不是在 render 函数中隐式提供 h,而是全局导入 h。
使用前面提到的 组件的示例,下面是它现在的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// vue3.0
import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
// vue3.0单文件写法
<template>
<component
v-bind:is="`h${$props.level}`"
v-bind="$attrs"
/>
</template>

<script>
export default {
props: ['level']
}
</script>

主要区别在于

1
2
functional attribute 在 <template> 中移除
listeners 现在作为 $attrs 的一部分传递,可以将其删除

异步组件的写法与 defineAsyncComponent 方法

现在使用 defineAsyncComponent 助手方法,用于显示的定义异步组件
component 选项重命名为 loader
Loader 函数本身不再接受 resolve 和 rejuct 参数,必须返回一个 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// vue2.x
// 以前异步组件是通过将组件定义为返回Promise的函数来创建
const asyncPage = () => import('./NextPage.vue')
// 或者以选项方式创建
const asyncPage = {
component: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}

// vue3.x
在vue3.x中,需要使用defineAsyncComponent来定义
import{ defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

// 不带选项的定义方法
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))

// 带选项的异步组件
constasyncPageWithOptions = defineAsyncCopmonent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
LoadingComponent: LoadingComponent
})

loader 函数不再接收 resolve 和 reject 参数,且必须始终返回 Promise

1
2
3
4
5
6
// vue2.x
const oldAsyncComponent = (resolve, reject) => {};
// vue3.x
const asyncComponent = defineAsyncComponent(
() => new Promise((resolve, reject) => {})
);

组件事件需要在 emits 选项中声明

vue3 中现在提供了一个 emits 选项,类似 props 选项
此选项可以用于定义组件向其父对象发出的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- vue2.x -->
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ["text"],
};
</script>

<!-- vue3.x -->
<!-- 现在和prop类似,可以用emits来定义组件发出的事件 -->
<!-- 这个选项还接收已给对象,用来向props一样对传递的参数进行验证 -->
<!-- 强烈建议记录下每个组件发出的所有emits,因为去掉了.native修饰符,未使用声明的事件的所有监听器都将包含在组建的$attr中,默认情况下,该监听器将绑定到组件的根节点 -->
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ["text"],
emits: ["accepted"],
};
</script>

全局 API 变化

Vue2 的全局 Api

vue2 的全局 Api 可以全局改变 vue 的行为,这种操作容易意外污染其他测试用例

1
2
3
4
5
6
7
import { createLocalVue, mount } from "@vue/test-utils";
// 扩展vue的构造函数
const localVue = createLocalVue();
// 在`local`Vue构造函数上全局安装插件
localVue.use(Myplugin);
// 通过localVue来挂载选项
mount(Component, { localVue });

全局配置会使同一个页面上的多个 app 之间共享一个 Vue 副本非常困难

1
2
3
4
5
6
7
// 这里的mixin将会影响两个根实例
Vue.mixin({
/* ... */
});

const app1 = new Vue({ el: "#app1" });
const app2 = new Vue({ el: "#app2" });

Vue3 中新的全局 Api:createApp

createApp 返回了一个应用实例,

1
2
3
4
5
6
import { createApp } from "vue";
const app = createApp({});

// 如果使用的是Vue的CDN,那么createApp是通过全局的Vue对象暴露的
const { createApp } = Vue;
const app = createApp({});

Vue3 中对比 Vue2 全局 Api 的变化

2.x 全局 Api 3.x 实例(app)Api
Vue.cofing app.config
Vue.config.productionTip * 移除
Vue.config.ignoredElements * app.config.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use * app.use
Vue.prototype * app.config.globalProperties

所有其他不全局改变行为的全局 Api,现在被命名为 exports

config.productionTip 移除

在 Vue 3.x 中,“使用生产版本”提示仅在使用“dev + full build”(包含运行时编译器并有警告的构建) 时才会显示。

对于 ES 模块构建,由于它们是与 bundler 一起使用的,而且在大多数情况下,CLI 或样板已经正确地配置了生产环境,所以本技巧将不再出现。

config.ignoredElements 替换为 config.isCustomElement

引入此配置项目目的是为了支持原生自定义元素,因此重命名可以更好的传达它的功能,新选项还需要一个比使用 String/RegExp 跟灵活的函数

1
2
3
4
5
// 2.x
Vue.config.ignoredElements = ["my-el", /^ion-/];
// 3.x
const app = createApp({});
app.config.isCustomElement = (tag) => tag.startsWith("ion-");
1
2
3
4
在 Vue 3 中,元素是否是组件的检查已转移到模板编译阶段,因此只有在使用运行时编译器时才考虑此配置选项。如果你使用的是 runtime-only 版本 isCustomElement 必须通过 @vue/compiler-dom 在构建步骤替换——比如,通过 compilerOptions option in vue-loader。

如果 config.isCustomElement 当使用仅运行时构建时时,将发出警告,指示用户在生成设置中传递该选项;
这将是 Vue CLI 配置中新的顶层选项。

Vue.prototype 替换为 config.globalProperties

在 Vue2 中,Vue.prototype 通常用于添加所有组件都能访问的 property
在 Vue3 等同于 config.globalProperties 这些 property 将被复制到应用中作为实例化组件的一部分

1
2
3
4
5
6
7
// 2.x
Vue.prototype.$http = () => {};

// 3.x
const app = createApp({});
app.config.globalProperties.$http = () => {};
// 使用provide时,也应考虑作为globaProperties的替代品

插件的使用

插件开发者通常使用 vue.use,例如官方的 vue-router 是如何在浏览器环境中自行安装的

1
2
3
4
5
var inBrowser = typeof window !== "undefined";
/* ... */
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter);
}

现在 use 全局 Api 不再使用,所以需要手动指定使用此插件

1
2
const app = createApp(MyApp);
app.use(VueRouter);

挂载 App 实例

使用 createApp 初始化之后,应用实例 app 可使用 app.mount(domTarget)挂在组件实例,
经过以上更改,完整的写法将会改写为以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createApp } from "vue";
import MyApp from "./MyApp.vue";

const app = createApp(MyApp);
app.componenet("button-counter", {
data: () => ({
count: 0,
}),
template: `<button @click="count++">Clicked {{ count }} times.</button>`,
});

app.directive("focus", {
mounted: (el) => el.focus(),
});
app.mount("#app");

Provide/inject

与 2.x 跟实例中使用 provide 选项类似,Vue3 应用实例还可以提供可由应用内的任何组件注入的依赖项

1
2
3
4
5
6
7
8
9
10
11
12
// 入口文件
app.provide("guide", "Vue 3 Guide");
// 子组件
export default {
inject: {
book: {
from: "guide",
},
},
template: `<div>{{ book }}</div>`,
};
// 使用 provide 在编写插件时非常有用,可以替代 globalProperties

应用之间共享配置

要在应用之间共享配置,如组件或指令的一种方法时创建组件工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createApp } from "vue";
import F00 from "./Foo.vue";
import Bar from "./Bar.vue";

const createMyApp = (options) => {
const app = createApp(options);
app.directive("focus" /* ... */);

return app;
};

createApp(Foo).mount("#foo");
createApp(Bar).mount("#bar");

支持 Tree-shaking 的影响

2.x 不支持 tree-shaking

tree-shaking,即死代码消除,但是由于 2.x 的一些 api,如 Vue.nextTick()方法,即使不被使用,也会被最终打包

3.x 中支持了 tree-shaking 所引起的变化

在 Vue 3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。

1
2
3
4
5
6
7
8
9
10
// 2.x
import Vue from "vue";
Vue.nextTick(() => {
// 一些和DOM有关的东西
});
// 3.x
import { nextTick } from "vue";
nextTick(() => {
// ...
});

直接调用 Vue.nextTick()将会导致 undefined is not a function
通过这一更改,Vue 应用程序中未使用的全局 api 将从最终捆绑包中消除,从而获得最佳文件大小

受到影响的 Api

  • Vue.nextTick
  • vue.observable
  • Vue.version
  • Vue.compile
  • Vue.set
  • vue.delete

内部帮助器

除了公共 api,许多内部组件/帮助其现在也被导出为命名导出,只有当编译器的输出是这些特性时,才允许编译器导入这些特性

1
2
3
<transition>
<div v-show="ok">hello</div>
</transition>

将会被编译为

1
2
3
4
5
import { h, Transition, withDirectives, vShow } from 'vue

export function render() {
return h(Transition, [withDirectives(h('div', 'hello'), [[vShow, this.ok]])])
}

这意味着只有在应用程序实际使用了 Transition 组件的时候才会导入他

1
以上仅适用于 ES Modules builds,用于支持 tree-shaking 的绑定器——UMD 构建仍然包括所有特性,并暴露 Vue 全局变量上的所有内容 (编译器将生成适当的输出,以使用全局外的 api 而不是导入)。

在插件中的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 2.x
const plugin = {
install: (Vue) => {
Vue.nextTick(() => {
// ...
});
},
};
// 3.x
import { nextTick } from "vue";
const plugin = {
install: (app) => {
nextTick(() => {
// ...
});
},
};

如果使用 webpack 这样的模块捆绑包,这可能会导致 Vue 的源代码绑定到插件中,而且通常情况下,这并不是你所期望的。防止这种情况发生的一种常见做法是配置模块绑定器以将 Vue 从最终捆绑中排除。对于 webpack,你可以使用 externals 配置选项:

1
2
3
4
5
6
7
// webpack.config.js
module.exports = {
externals: {
vue: "Vue",
},
};
// 这将会告诉webpack将Vue模块视为一个外部库,而不是捆绑他

如果你选择的模块绑定器恰好是 Rollup,你基本上可以免费获得相同的效果,因为默认情况下,Rollup 会将绝对模块 id (在我们的例子中为 ‘vue’) 作为外部依赖项,而不会将它们包含在最终的 bundle 中。但是在绑定期间,它可能会发出一个“将 vue 作为外部依赖” 警告,可使用 external 选项抑制该警告:

1
2
3
4
5
// rollup.config.js
export default {
/*...*/
external: ["vue"],
};

模板指令

按键修饰符

1
从KeyboardEvent.keyCode has been deprecated 开始,Vue 3 继续支持这一点就不再有意义了。因此,现在建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称。
1
2
3
4
5
<!-- vue2.x -->
<input v-on:keyup.13="submit" />
<input v-on:keyup.enter="submit" />
<!-- vue3.x -->
<input v-on:keyup.delete="confirmDelete" />

同时废弃了全局 config.keyCodes 选项

key 属性

  1. v-if/v-else/v-else-if 的 key 不再是必须的,vue3.x 会自动生成唯一 key
    不可以通过手动提供 key 的方式,来强制重用分支
1
2
3
4
5
6
7
8
9
10
11
<!-- Vue 2.x 没有必要在vue3.x这样写 -->
<div v-if="condition" key="yes">Yes</div>
<div v-else key="no">No</div>

<!-- Vue 2.x 这在vue3.x中会出现错误 -->
<div v-if="condition" key="a">Yes</div>
<div v-else key="a">No</div>

<!-- Vue3.x 如果一定要指定key,请确保key值不重复 -->
<div v-if="condition" key="a">Yes</div>
<div v-else key="b">No</div>
  1. <template v-for>的 key 应该设置在<template>标签上,而不是设置在他的子结点上
1
2
3
4
5
6
7
8
9
10
11
<!-- Vue 2.x -->
<template v-for="item in list">
<div :key="item.id">...</div>
<span :key="item.id">...</span>
</template>

<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div>...</div>
<span>...</span>
</template>

v-if 和 v-for 的优先级调整

这个可太棒了

1
2
3
4
5
在vue3中,v-if拥有比v-for更高的优先级

官网建议:
由于语法上存在歧义,建议避免在同一元素上同时使用两者。
比起在模板层面管理相关逻辑,更好的办法是通过创建计算属性筛选出列表,并以此创建可见元素。

v-bind 现在对排序敏感(v-bind 的合并行为)

如果在一个元素上同时定义了 v-bind=”object”和一个相同的单独的 property
那么 v-bind 的绑定会被覆盖

在 vue3.x 中 v-bind 和单独的 property 有排序关系,看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- vue2.x -->
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>

<!-- vue3.x -->
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>

<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>

v-on 的 .native 修饰符已被移除

1
2
vue3.x中新增了emits选项
对于子组件中未被定义为组件触发的所有事件监听器,Vue 现在将把它们作为原生事件监听器添加到子组件的根元素中 (除非在子组件的选项中设置了 inheritAttrs: false)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- vue2.x -->
<my-component
v-on:close="handleComponentEvent"
v-on:click.native="handleNativeClickEvent"
/>

<!-- vue3.x -->
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
<script>
export default {
emits: ["close"],
};
</script>

v-for 中的 ref 不再注册 ref 数组

在 Vue 2 中,在 v-for 里使用的 ref attribute 会用 ref 数组填充相应的 $refs property。当存在嵌套的 v-for 时,这种行为会变得不明确且效率低下。

在 Vue 3 中,这样的用法将不再在 $ref 中自动创建数组。要从单个绑定获取多个 ref,请将 ref 绑定到一个更灵活的函数上 (这是一个新特性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<div v-for="item in list" :ref="setItemRef"></div>
<!-- 结合选项式API -->
<script>
export default {
data() {
return {
itemRefs: [],
};
},
methods: {
setItemRef(el) {
this.itemRefs.push(el);
},
},
beforeUpdate() {
this.itemRefs = [];
},
updated() {
console.log(this.itemRefs);
},
};
</script>
<!-- 结合组合式API -->
<script>
import { ref, onBeforeUpdate, onUpdated } from "vue";
export default {
setup() {
let itemRefs = [];
const setItemRef = (el) => {
itemRefs.push(el);
};
onBeforeUpdate(() => {
itemRefs = [];
});
onUpdated(() => {
console.log(itemRefs);
});
return {
itemRefs,
setItemRef,
};
},
};
</script>

itemRefs 不必是数组:它也可以是一个对象,其 ref 会通过迭代的 key 被设置。

如果需要,itemRef 也可以是响应式的且可以被监听。

渲染函数

渲染函数 API 变更

此更改不会影响到<template>用户

  • h现在全局导入,而非作为参数传递给渲染函数
  • 渲染函数参数更改为在有状态组件和函数组件之间更加一致
  • vnode 现在又一个扁平的 prop 结构

Render 函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
// 2.0 渲染函数
export default {
render(h) {
return h('div')
}
}

// 3.x语法
export default {
render() {
return h('div')
}
}

渲染函数签名更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 2.x
export default {
render(h) {
return h('div')
}
}

// 3.x
import { h, reactive } from 'vue'
export default {
setup(prop, {slots, attrs, emit}) {
const state = reactive({
count: 0
})
function increment() {
state.count++
}
// 返回render函数
return () => h(
'div',
{
onClick: increment
},
state.count
)
}
}

VNode Props 格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 2.x
{
class: ['button', 'is-outlined'],
style: {color: '#fffff'},
attr: {id: 'submit'},
domProps: {innerHTML: ''},
on: {click: submitForm},
key: 'submit-button'
}
// 3.x VNode的结构是扁平的
{
class: ['button', 'is-outlined'],
style: { color: '#34495E' },
id: 'submit',
innerHTML: '',
onClick: submitForm,
key: 'submit-button'
}

slot 统一

更改了普通 slot 和作用域 slot

  • this.$slots现在将 slots 作为函数公开
  • 移除this.$scopedSlots
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 2.x
h(LayoutComponent, [
h("div", { slot: "header" }, this.header),
h("div", { slot: "header" }, this.header),
]);
// 作用域slot:

// 3.x
h(
LayoutComponent,
{},
{
header: () => h("div", this.header),
content: () => h("div", this.content),
}
);
// 需要以编程方式引入作用域slot时,他们现在被统一在了$slots选项中
// 2.x的作用域slot
this.$scopedSlots.header;
// 3.x的写法
this.$slots.header;

移除$listeners

$listeners对象在 vue3 中已经移除,现在事件监听器是$attrs的一部分

在 vue2 中,可以使用 this.$attrs和this.$listeners 分别访问传递给组件的 attribute 和时间监听器,结合 inheritAttrs: false,开发者可以将这些 attribute 和监听器应用到其他元素,而不是根元素

1
2
3
4
5
6
7
8
9
10
<template>
<label>
<input type="text" v-bind="$attrs" v-on="$listeners" />
</label>
</template>
<script>
export default {
inheritAttrs: false,
};
</script>

在 vue 的虚拟 DOM 中,事件监听器现在只是以 on 为前缀的 attribute,这样就成了$attrs对象的一部分,这样$listeners 就被移除了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
// 如果这个组件接收一个 id attribute 和一个 v-on:close 监听器,那么 $attrs 对象现在将如下所示
{
id: 'my-input',
onClose: () => console.log('close Event Triggered')
}
</script>

$attrs 现在包括 class 和 style

现在的$attr 包含所有的 attribute,包括 class 和 style

在 2.x 中,虚拟 dom 会对 class 和 style 进行特殊处理,所以他们不包括在$attr 中
在使用 inheritAttr: false 的时候会产生副作用

  • $attrs 中的 attribute 不再自动添加到根元素中,而是由开发者决定在哪添加。
  • 但是 class 和 style 不属于 $attrs,仍然会应用到组件的根元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false,
};
</script>
<!-- 写入 -->
<my-component id="my-id" class="my-class"></my-component>
<!-- vue2 将生成 -->
<label class="my-class">
<input type="text" id="my-id" />
</label>
<!-- vue3 将生成 -->
<label>
<input type="text" id="my-id" class="my-class" />
</label>

自定义元素

自主定制元素

如果我们先添加在 Vue 外部定义的自定义元素,如使用 Web 组件 API,我们需要指示 Vue 将其视为自定义元素:

1
<plastic-button></plastic-button>
1
2
3
4
// 在2.x中,将标记作为自定义元素白名单是通过Vue.config.ignoredElements
// 这将使Vue忽略在Vue外部定义的自定义元素
// (例如:使用 Web Components API)
Vue.config.ignoredElements = ["plastic-button"];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在Vue3中,此检查在模板编译期间执行指示编译器将<plastic-button>视为自定义元素
// 如果使用生成步骤:将 isCustomElement 传递给 Vue 模板编译器,如果使用 vue-loader,则应通过 vue-loader 的 compilerOptions 选项传递:
// webpack配置
rules: [
{
test: /\.vue$/,
use: "vue-loader",
options: {
compilerOptions: {
isCustomElement: (tag) => tag === "plastic-button",
},
},
},
// ...
];
// 如果使用动态模板编译,请通过 app.config.isCustomElement 传递
// 运行时配置只会影响运行时模板编译——它不会影响预编译的模板。
const app = Vue.createApp({});
app.config.isCustomElement = (tag) => tag === "plastic-button";

定义内置元素

自定义元素规范提供了一种将自定义元素用作自定义内置模板的方法,方法是向内置元素添加 is 属性:

1
<button is="plastic-button">点击我!</button>

Vue 对 is 特殊 prop 的使用是在模拟 native attribute 在浏览器中普遍可用之前的作用,但是在 2.x 中,它被解释为一个名为 plastic-button 的 Vue 组件,浙江组织上面提到的自定义内置元素的原生使用
在 3.0 中,Vue 对 is 属性的特殊处理被限制到<component>标签上
在保留的 <component> tag 上使用时,它的行为将与 2.x 中完全相同

  • 在普通组件上使用时,他的行为将类似于普通 prop
1
2
3
<foo is="bar" />
在vue2中,将会渲染bar组件
在vue3中,会通过is属性渲染foo组件
  • 在普通元素上使用时,它将作为 is 选项传递给 createElement 调用,并作为原生属性渲染
1
2
3
4
<button is="plastic-button">点击我!</button>
在vue2中,渲染plastic-button组件
在vue3中,渲染原生button:
document.createElement('button', { is: 'plastic-button' })

v-is 用于 DOM 内模板解析解决方案

仅影响直接在页面的 HTML 中写入 Vue 模板的情况,在 DOM 模板中使用时,模板受原生 HTML 解析规则的约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 2.x -->
<table>
<tr is="blog-post-row"></tr>
</table>

<!-- 2.x -->
<!-- 随着is的行为变化,新的指令v-is用来解决当前情况 -->
<!-- 注意:v-is 函数像一个动态的 2.x :is 绑定——因此,要按注册名称渲染组件,其值应为 JavaScript 字符串文本 -->
<table>
<tr v-is="'blog-post-row'"></tr>
</table>
<!-- v-is绑定的是一个Javascript变量 -->
<!-- 不正确,不会渲染任何内容 -->
<tr v-is="blog-post-row"></tr>
<!-- 正确 -->
<tr v-is="'blog-post-row'"></tr>