关于 Vue 模板的语法,官方文档做了比较详细的介绍,建议读者先阅读完官网文档再继续阅读本文后续内容。
下面介绍一些比较特殊的或者说需要注意的点。
v-bind 绑定对象
通过 v-bind
可以给组件传递 prop
数据,比如有组件 A :
<template>
<div>...</div>
</template>
<script>
export default {
props: {
name: String,
age: Number,
anotherProp: String
}
};
</script>
那么给该组件传递 name
和 age
属性就可以写成这样:
<A name="yibuyisheng" :age="27" :another-prop="'test'"></A>
注意,在传递 prop
数据的时候,模板里面的键名和声明处的键名对应是有讲究的:在模板里面,要么严格按照声明处的大小写,要么按照声明处的键名经过类似于 lodash
中 snakeCase 转换之后的形式书写。
比如对于声明处的 anotherProp
属性,在模板里面对应写上 <A :anotherProp="'test'"></A>
或者 <A :another-prop="'test'"></A>
,是没有问题的。
如果写成 <A :another-Prop="'test'"></A>
或者 <A another-Prop="test"></A>
,就会对应失败。(后面一种写法会在 DOM 树中看到 another-prop attribute
)
虽然,大部分时候,都不用太去关心模板标签和属性的大小写问题(毕竟绝大多数时候写的都是 HTML
模板),但是有的属性,还是要小心处理大小写问题的,比如 svg
的 viewBox
属性:
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="150" height="100" viewBox="0 0 3 2">
<rect width="1" height="2" x="0" fill="#008d46" />
<rect width="1" height="2" x="1" fill="#ffffff" />
<rect width="1" height="2" x="2" fill="#d2232c" />
</svg>
默认情况下,对于传递的未声明的属性,都会采用 setAttribute
设置到相应 DOM 元素上面去,比如:
<A some-prop="some value"></A>
这个 some-prop
会通过 setAttribute
设置到 A 组件根元素的属性上面去。
而对于一些特殊属性,是需要设置到 DOM 元素对象上去的,比如 input[type="checked"]
的 checked
属性。对于这些属性, Vue 内部做了默认配置,当然也可以通过全局的 Vue.config.mustUseProp
配置覆盖默认配置。
另外,通过 v-bind
的 prop
修饰符,可以直接给相应的 DOM 节点对象设置属性,比如:
<div v-bind:name-attr.prop="'haha'"></div>
<!-- 缩写形式 -->
<div :name-attr.prop="'haha'"></div>
会被转换成大致如下代码:
// 属性名做驼峰转换
divElement.nameAttr = 'haha';
注意,如下形式写法:
<div name-attr.prop="haha"></div>
会被转换成大致如下代码:
divElement.setAttribute('name-attr.prop', 'haha');
如果 div
换成 A 组件,那么相应的属性操作就会发生在 A 组件的根元素上面。
最后,通过 v-bind
可以批量动态地设置属性:
<div v-bind="{id: 'list', name: 'yibuyisheng'}"></div>
<!-- 等同于,但是上述优势在于“大量属性”、“动态属性键” -->
<div id="list" name="yibuyisheng"></div>
Vue diff
Vue diff 逻辑有很多文章都做了介绍,可以参看这篇文章。
此处主要强调由于 diff 算法,而造成的写模板必须注意的点。
key
官方有一些关于 key
的介绍。
在实际开发中, key
主要用于解决同级 DOM 元素复用引起的 bug ,以及做列表渲染优化。
在列表渲染中, Vue 会尽量复用已有 DOM 节点元素。默认情况下,对于被循环元素,会有 :key="index"
的设置,对于这中默认渲染模式,官网有一句非常非常重要的描述:
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
有两个需要搞清楚的点:
什么是子组件状态?
子组件状态就是不受
prop
属性控制的状态。比如组件 B :<template> <div>{{ instanceCounter }}</div> </template> <script> let counter = 1; export default { data() { return { instanceCounter: counter++ }; } }; </script>
对于组件 B 来说,
instanceCounter
就是子组件状态。什么是临时 DOM 状态?
基本所有未受
Vue prop
控制的,但是自身又能随交互变化的状态,都算作临时 DOM 状态。比如不受控制的input.value
、input.checked
等等。
从根本原理上来讲,所有没在 VNode
中的 data
属性上表达的状态,都算是相应组件或 DOM 节点的内部状态,都会存在 key 风险
。
明确了上面两个概念之后,我们看一个例子:
import Vue from 'vue';
import { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options';
let counter = 1;
const Item: ThisTypedComponentOptionsWithRecordProps<Vue, {instanceCounter: number;}, {}, {}, {}> = {
template: '<div class="item"><slot></slot> --{{ instanceCounter }}--</div>',
data() {
return {
instanceCounter: counter++
};
}
};
Vue.component('Item', Item);
const KeyComponent: ThisTypedComponentOptionsWithRecordProps<Vue, {items: Array<{id: string;}>;}, {}, {}, {}> = {
template: `
<div>
<Item v-for="item in items" @click="handle1" :key="item.id">{{ item.id }}</Item>
<button @click="add">+</button>
</div>
`,
data() {
return {
items: [
{
id: '123'
},
{
id: '456'
}
]
};
},
methods: {
add() {
this.items.splice(1, 0, {
id: '789'
});
}
}
};
Vue.component('KeyComponent', KeyComponent);
new Vue({
el: '#app',
template: '<KeyComponent />'
});
在第一次点击 KeyComponent
组件中的按钮之后,输出结果为:
123 --1--
789 --3--
456 --2--
如果去掉 KeyComponent
中 Item
的 key
属性设置,然后刷新页面,第一次点击 KeyCOmponent
组件中的按钮,输出结果为:
123 --1--
789 --2--
456 --3--
可以看出,只有加了正确的 key
,新插入的 768
组件才会对应到新创建的组件。
render
render 方法必须要返回一个 VNode 实例。
如果想返回一个文本节点怎么办呢?
可以借助于 createElement()
实现:
import Vue from 'vue';
import { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options';
const Test: ThisTypedComponentOptionsWithRecordProps<Vue, {}, {}, {}, {}> = {
render(createElement) {
const nodes = createElement('div', '文本内容').children;
if (!nodes) {
throw new Error('error');
}
return nodes[0];
}
};
Vue.component('Test', Test);