0%

vue2文档查漏补缺

即将去一家 vue 技术栈的公司干活了,其实对于 vue2 的掌握,我还是有不少欠缺的,自己通读 vue2 的文档还是大一的时候,后面 vue2 的版本经历了多次更新,这些更新的内容我基本就没怎么看过了,这次就来查漏补缺一下,这份笔记只记录自己不熟悉的内容。

不同构建版本

这里有几个概念

  • 完整版,同时包含运行时和编译器的版本
  • 编译器,如果需要使用 template 选项,需要使用带编译器的完整版。如果你仍然希望使用完整版,则需要在打包工具里配置一个别名:
  • 运行时,除去编译器的一切
  • UMD,统一模块规范,提供了一个 AMD,CMD,ES Module 的兼容层
  • CMD,这是 nodejs 的模块化规范,配合老的打包工具如 Browserify 或 webpack 1。
  • ES Module 从 2.6 开始 Vue 会提供两个 ES Modules (ESM) 构建文件:
    • 为打包工具提供的 ESM:为诸如 webpack 2 或 Rollup 提供的现代打包工具。ESM 格式被设计为可以被静态分析,所以打包工具可以利用这一点来进行“tree-shaking”并将用不到的代码排除出最终的包。为这些打包工具提供的默认文件 (pkg.module) 是只有运行时的 ES Module 构建 (vue.runtime.esm.js)。
    • 为浏览器提供的 ESM (2.6+):用于在现代浏览器中通过 script type=”module” 直接导入。

CSP 环境

在某些环境,如 Google Chrome Apps,会强制应用内容安全策略 CSP,不能使用 new Function() 对表达式求值。这时可以用 CSP 兼容版本。运行时版本是完全兼容 CSP 的,当通过 webpack + vue-loader 或者 Browserify + vueify 构建时,模板将被预编译为 render 函数,可以在 CSP 环境中完美运行。

Vue 实例

Object.freeze

这里唯一的例外是使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。这里 Vue 3 直接提供了一个 api markRaw

v-once 可以执行一次性的插值,当数据改变时,插值处的内容不会更新。

模板中的全局变量

模板中的表达式都被放在沙盒中,只能访问全局变量的一个白名单

1
2
3
4
5
6
const allowedGlobals = makeMap(
"Infinity,undefined,NaN,isFinite,isNaN," +
"parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent," +
"Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl," +
"require" // for Webpack/Browserify
);

动态参数

动态参数,2.6.0 新增,可以使用 方括号括起来的 js 表达式作为一个指令的参数

1
2
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>

对动态参数值的约束

动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。

对动态参数表达式的约束

动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:

1
2
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>

变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。

在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:

1
2
3
4
5
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>

计算属性和侦听器

计算属性

计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖。

1
2
3
4
5
computed: {
now: function () {
return Date.now()
}
}

计算属性的 setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}

侦听器

当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

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
var watchExampleVM = new Vue({
el: "#watch-example",
data: {
question: "",
answer: "I cannot give you an answer until you ask a question!",
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = "Waiting for you to stop typing...";
this.debouncedGetAnswer();
},
},
created: function () {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500);
},
methods: {
getAnswer: function () {
if (this.question.indexOf("?") === -1) {
this.answer = "Questions usually contain a question mark. ;-)";
return;
}
this.answer = "Thinking...";
var vm = this;
axios
.get("https://yesno.wtf/api")
.then(function (response) {
vm.answer = _.capitalize(response.data.answer);
})
.catch(function (error) {
vm.answer = "Error! Could not reach the API. " + error;
});
},
},
});

class 与 style 绑定

当在一个自定义组件上使用 class 属性时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。

当 v-bind 使用需要添加浏览器引擎前缀的 CSS 属性值时,Vue.js 会自动侦测并添加响应的前缀。

多重值

从 2.3.0 起你可以为 style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

条件渲染

v-if vs v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

列表渲染

v-for 中使用对象

你也可以用 v-for 来遍历一个对象的属性。
你也可以提供第二个的参数为 property 名称 (也就是键名):
还可以用第三个参数作为索引:

1
2
3
<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>

在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。

维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。

数组更新检测

Vue 将被侦听的数组变异方法进行了包裹,所以它们也会触发视图更新,这些方法包括:

1
2
3
4
5
6
7
push();
pop();
shift();
unshift();
splice();
sort();
reverse();

替换数组

filter()、concat() 和 slice()。它们不会改变原始数组,而总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组。

1
2
3
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/);
});

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。

显示过滤/排序后的结果

有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际改变或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法:

v-for 里使用值范围

v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。

1
2
3
<div>
<span v-for="n in 10">{{ n }} </span>
</div>

template 上使用 v-for

类似于 v-if,你也可以利用带有 v-for 的 template 来循环渲染一段包含多个元素的内容。比如:

1
2
3
4
5
6
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

组件上使用 v-for

在 2.2.0+的版本里,在组件上使用 v-for 时,key 现在是必须的

事件处理

当需要访问原始 DOM 事件时,可以用特殊变量 $event 把它传入方法:

1
2
3
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>

事件修饰符

1
2
3
4
5
6
.stop
.prevent
.capture
.self
.once 事件只会触发一次,可以被用到自定义的组建事件上
.passive 对应于 addEventListener 的 passive 属性,表明这个函数不能使用 preventDefault。如果和 .prevent一起使用,浏览器会给予警告

按键修饰符

vue 允许为 v-on 在监听键盘事件时添加修饰符。

直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

1
2
3
4
.ctrl
.alt
.shift
.meta

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

1
2
3
4
5
6
7
8
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>

鼠标按钮修饰符

1
2
3
.left
.right
.middle

表单输入绑定

v-model

可以用 v-model 指令在表单 input、textarea 及 select 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model 本质上是语法糖,他负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

text 和 textarea 元素使用 value 属性和 input 事件;
checkbox 和 radio 使用 checked 属性和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。

在 textarea 处使用插值语法不会生效。
单个复选框,绑定到布尔值,多个复选框,应绑定到数组。
单选按钮,选项应绑定到同一个值。

select 单选时,如果 v-model 表达式的初始值未能匹配任何选项,select 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

1
2
3
4
5
6
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>

select 多选时,应绑定到一个数组。

修饰符

.lazy 默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步。

.number 自动将用户输入转为 number 类型,如果这个值无法被 parseFloat 解析,就会返回原始值。

.trim 自动过滤用户输入的首尾空白字符

在组件上使用 v-model

默认会使用组件的 value 和 input 事件,可以使用 model 属性修改默认行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component("base-checkbox", {
model: {
prop: "checked",
event: "change",
},
props: {
checked: Boolean,
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`,
});

组件基础

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的:

1
2
3
Vue.component("my-component-name", {
// ... options ...
});

动态组件

上述内容可以通过 Vue 的 component 元素加一个特殊的 is attribute 来实现:

1
<component v-bind:is="currentTabComponent"></component>

请留意,这个 attribute 可以用于常规 HTML 元素,但这些元素将被视为组件,这意味着所有的 attribute 都会作为 DOM attribute 被绑定。对于像 value 这样的 property,若想让其如预期般工作,你需要使用 .prop 修饰器。

dom property 和 attribute 的区别

当写 html 代码的时候,你可以在 html 标签上定义 attr,当浏览器解析你的 html 代码的时候,一个相应的 dom 节点会被创建,这个节点是一个对象,因此他具有他自己的属性。对于一个给定的 DOM 节点对象,属性是对象的属性,attributes 是对象的 attributes 属性里的元素。

当一个 DOM 节点被创建,dom 对象的许多 preperty 和 它的 attributes 里面的属性是相关联的

1
<input id="the-input" type="text" value="Name:" />

相应的 dom 节点会有 id,type 和 value 属性

id 属性是 id attribute 的反射,对其进行读写会直接改变 attribute 中的 id 属性。
type 属性 对其进行读写操作会改变 attribute 中的 type,但是它并不是一个纯的映射,因为它的值被限定在运行的列表内。
value 属性则不是一个映射,它是 input 控件元素的当前值,对其进行写入不会更改 attribute 中的 value。

解析 dom 模板的注意事项

有些 HTML 元素,诸如 ul、ol、table 和 select,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 li、tr 和 option,只能出现在其它某些特定的元素内部。
使用以下来源的模板,这种限制是不存在的。
字符串 (例如:template: ‘…’)
单文件组件 (.vue)
script type=”text/x-template”

深入了解组件

prop 类型检查

type 可以是下列原生构造函数中的一个

String
Number
Boolean
Array
Object
Date
Function
Symbol

额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。

非 prop 的 attribute

一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 prop 定义的 attribute。这些 attribute 会被添加到这个组件的根元素上。

替换/合并已有的 Attribute

对于大多数 attribute 来说,从外部提供给组件的值会替换掉组件内部设置好的值。class 和 style attribute 会稍微智能一些,即两边的值会被合并起来。

禁用 Attribute 继承

如果不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。
这尤其适合配合实例的 $attrs 属性使用,该属性包含了传递给一个组件的 attribute 名和 attribute 值,例如:

1
2
3
4
{
required: true,
placeholder: 'Enter your username'
}

自定义事件

如果希望监听原生事件,需要使用.native 修饰符。

.sync 语法糖 是以下方式的简写,带有.sync 修饰符的 v-bind 不能和表达式一起使用。

1
2
3
4
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>

插槽 编译作用域

1
2
3
4
5
6
7
8
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为 "/profile" 是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>

后备内容

有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。例如在一个 submit-button 组件中:

1
2
3
<button type="submit">
<slot>Submit</slot>
</button>

具名插槽

在向具名插槽提供内容的时候,我们可以在一个 template 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>

<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>

注意 v-slot 只能添加在 template 上

作用域插槽

绑定在 slot 元素上的 attribute 被称为插槽 prop。

1
2
3
<span>
<slot v-bind:user="user"> {{ user.lastName }} </slot>
</span>

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 slot 元素的一个 attribute 绑定上去:

1
2
3
<span>
<slot v-bind:user="user"> {{ user.lastName }} </slot>
</span>

在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

1
2
3
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>

还可以更简单

1
<current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user>

只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法:

1
2
3
4
5
6
7
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>

<template v-slot:other="otherSlotProps"> ... </template>
</current-user>

解构插槽 prop

插槽 prop 可以使用解构语法

1
<current-user v-slot="{ user }"> {{ user.firstName }} </current-user>

可以定义默认值

1
2
3
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>

动态插槽名

和动态属性名一样

具名插槽缩写

使用 #

动态组件 & 异步组件

使用动态组件的时候,有时会向保持这些组件的状态

1
2
3
4
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

处理边界情况

程式化的事件侦听器

如此可以避免在实例上添加不必要的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mounted: function () {
this.attachDatepicker('startDateInput')
this.attachDatepicker('endDateInput')
},
methods: {
attachDatepicker: function (refName) {
var picker = new Pikaday({
field: this.$refs[refName],
format: 'YYYY-MM-DD'
})

this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
}

递归组件

可以在自己的模板中调用自身,只能通过 name 选项来做这件事

模板定义的替代品

当 inline-template 这个特殊的 attribute 出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。

1
2
3
4
5
6
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>

X-Template

另一个定义模板的方式是在一个 script 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去。例如:

1
2
3
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
1
2
3
Vue.component("hello-world", {
template: "#hello-world-template",
});

强制更新

使用 forceUpdate 来做这件事,只有很少的情况下才需要做这件事

过渡

css过渡

初始渲染的过渡

可以在节点上设置 appear,也可以设置自定义的类名和钩子函数

1
2
3
<transition appear>
<!-- ... -->
</transition>

过渡模式

默认是旧元素的离开和新元素的进入同时发生,可以设置 mode

in-out 新元素先进性过渡,完成后当前元素过渡离开
out-in 当前元素先进行过渡,完成后新元素过渡进入

多个组件的过渡,使用动态组件

1
2
3
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>