Vue 概述

Vue 是一款构建用户界面的 JS 框架。基于 HTML、CSS 和 JS 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。

Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。Vue 会自动跟踪 JS 状态并在其发生变化时响应式地更新 DOM。

Vue 可以开发单页应用(SPA)、全栈/服务器渲染(SSR)、Jamstack/静态站点生成(SSG)

API 风格

  • 选项式 API:用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。
    示例:
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
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0,
};
},

// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件处理器绑定
methods: {
increment() {
this.count++;
},
},

// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`);
},
};
</script>

<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
  • 组合式 API:使用导入的 API 函数来描述组件逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script setup>
import { ref, onMounted } from "vue";

// 响应式状态
const count = ref(0);

// 用来修改状态、触发更新的函数
function increment() {
count.value++;
}

// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`);
});
</script>

<template>
<button @click="increment">Count is: {{ count }}</button>
</template>

vue 使用

  1. 安装 node.js 18.3 以上版本
  2. 在项目创建文件安装并执行 vuenpm create vue@latest
  3. 启动
1
2
3
cd <your-project-name>
npm install
npm run dev
  1. 发布:npm run build,会在./dist 文件夹中为你的应用创建一个生产环境的构建版本。
  2. 创建一个应用实例,在 app.js 文件
1
2
3
4
5
6
7
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'
const app = createApp({
/* 根组件选项 */
return app
}).mount('#app') // 挂载应用,应用实例必须在调用了 .mount() 方法后才会渲染出来

vue 语法

基础语法

文本插值

,双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新。 `Message: ` --- 支持表达式:
1
2
3
4
5
6
7
{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

原始 HTML

双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令:

1
2
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

Attribute 绑定

v-bind:v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致

1
<div v-bind:id="dynamicId"></div>

v-bind:可简写为:
动态绑定多个值:

1
<div v-bind="objectOfAttrs"></div>
1
2
3
4
5
const objectOfAttrs = {
id: "container",
class: "wrapper",
style: "background-color:green",
};

内置指令

指令:指令的任务是在其表达式的值变化时响应式地更新 DOM,某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind 指令来响应式地更新一个 HTML attribute。
参数:同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:

1
2
<!-- 这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。 -->
<a :[attributeName]="url"> ... </a>

动态参数的限制:值的类型应该是一个字符串,或者为 null,我们可以使用计算属性替换复杂的表达式
修饰符:修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如.prevent,会告知 v-on 指令触发事件的默认行为。

响应式基础

ref()

在组合式 API 中,推荐使用 ref() 函数来声明响应式状态:

1
2
3
import { ref } from "vue";

const count = ref(0);

ref()接收参数,并将其包裹在一个有.value 属性的 ref 对象中返回:

1
2
3
4
5
6
7
const count = ref(0);

console.log(count); // { value: 0 }
console.log(count.value); // 0

count.value++;
console.log(count.value); // 1

**在组件模版中访问 ref,请从组件的 setup()函数中声明并返回它们,在模板中使用 ref 时,我们不需要附加 .value,ref 会自动解包
<div>{{ count }}</div>;

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref } from "vue";

export default {
// `setup` 是一个特殊的钩子,专门用于组合式 API。
setup() {
const count = ref(0);

// 将 ref 暴露给模板
return {
count,
};
},
};

当我们使用 ref 时,Vue 会自动监测 ref 值的变化,并且相应的更新 DOM。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。
.value 属性给予了 Vue 一个机会来检测 ref 何时被访问或修改。在其内部,Vue 在它的 getter 中执行追踪,在它的 setter 中执行触发。


Ref 的深层响应性:Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,这使得它的值具有深层的响应性,这意味着即使改变嵌套对象或数组时,变化也会被检测到:

1
2
3
4
5
6
7
8
9
10
11
12
import { ref } from "vue";

const obj = ref({
nested: { count: 0 },
arr: ["foo", "bar"],
});

function mutateDeeply() {
// 以下都会按照期望工作
obj.value.nested.count++;
obj.value.arr.push("baz");
}

当修改了响应式状态时,DOM 会自动更新,可以使用 nextTick()来等待 DOM 更新完成后在执行额外的代码。

reactive()

  • ref 是将值包装成了一个响应式对象,而 reactive()是使对象本身具有响应性,因此使用时不需要.value

  • reactive() 将深层地转换对象:当访问嵌套对象时,它们也会被 reactive() 包装。当 ref 的值是一个对象时,ref() 也会在内部调用它。

  • reactive() 返回的是一个原始对象的代理(Proxy),并不是原始对象。只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是仅使用你声明对象的代理版本。

  • 对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:


reactive()局限性:

  • 只能用于对象类型,不能持有 string、boolean 等原始类型
  • 由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失
  • 当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接

响应式对象是 JavaScript 代理,其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。


解包的细节

  • 一个 ref 会在作为响应式对象的属性被访问或修改时自动解包。它的行为就像一个普通的属性
  • 如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref
    数组和集合的注意事项:
  • 与 reactive 对象不同的是,当 ref 作为响应式数组或原生集合类型 (如 Map) 中的元素被访问时,它不会被解包

计算属性

使用计算属性来描述依赖响应式状态的复杂逻辑,示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
import { reactive, computed } from "vue";

const author = reactive({
name: "John Doe",
books: [
"Vue 2 - Advanced Guide",
"Vue 3 - Basic Guide",
"Vue 4 - The Mystery",
],
});

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? "Yes" : "No";
});
</script>

<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>

computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value

Vue 的计算属性会自动追踪响应式依赖。它会检测到 publishedBooksMessage 依赖于 author.books,所以当 author.books 改变时,任何依赖于 publishedBooksMessage 的绑定都会同时更新。


计算属性缓存:计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。

计算属性默认是只读的,若尝试修改,会出现警告,可以通过同时提供 getter 和 setter 来创建可写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup>
import { ref, computed } from "vue";

const firstName = ref("John");
const lastName = ref("Doe");

const fullName = computed({
// getter
get() {
return firstName.value + " " + lastName.value;
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(" ");
},
});
</script>

类与样式绑定

绑定 Class

绑定对象:我们可以给 :class 传递一个对象来动态切换 class:

1
<div :class="{ active: isActive }"></div>

表示 active 是否存在取决于数据属性 isActive 的真假值

示例:

1
2
const isActive = ref(true);
const hasError = ref(false);
1
2
3
4
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>

渲染的结果会是:<div class="static active"></div>;

绑定内联样式

:style 支持绑定 JavaScript 对象值,对应的是 HTML 元素的 style 属性
示例:

1
2
3
4
5
const styleObject = reactive({
color: 'red',
fontSize: '30px'
})
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

条件渲染

  • v-if:v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
  • v-else:为 v-if 添加一个“else 区块”
  • v-else-if:相应于 v-if 的“else if 区块”
  • v-show:按条件显示一个元素的指令是 v-show
1
2
3
4
<button @click="awesome = !awesome">Toggle</button>

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
  • v-if 是按条件渲染,条件区块内的事件监听器和子组件都会被销毁与重建。
  • v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
  • v-show:元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
    v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适

列表渲染

v-for 指令基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名

1
2
3
4
5
6
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])


<li v-for="item in items">
{{ item.message }}
</li>

v-for 也支持使用可选的第二个参数表示当前项的位置索引。

1
2
3
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

通过 key 管理状态:为每个元素对应的块提供一个唯一的 key attribute

1
2
3
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>

Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。

组件上使用 v-for

1
2
3
4
5
6
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>

事件处理

监听事件

  • v-on(简写@):来监听 DOM 事件,并在事件触发时执行对应的 JavaScript

用法:v-on:click=”handler” 或 @click=”handler”
事件修饰符:

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
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>

<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>

<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>

<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>

<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>

<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>

<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>

<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>

按键修饰符:在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在 v-on 或 @ 监听按键事件时添加按键修饰符。

1
2
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />

你可以直接使用 KeyboardEvent.key 暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。


鼠标按键修饰符

  • .left
  • .right
  • .middle

表单输入绑定

v-model 会根据所使用的元素自动使用对应的 DOM 属性和事件组合

1
2
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />

监听器

watch()在每次响应式状态发生变化时触发回调函数
示例:

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
<script setup>
import { ref, watch } from "vue";

const question = ref("");
const answer = ref("Questions usually contain a question mark. ;-)");
const loading = ref(false);

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.includes("?")) {
loading.value = true;
answer.value = "Thinking...";
try {
const res = await fetch("https://yesno.wtf/api");
answer.value = (await res.json()).answer;
} catch (error) {
answer.value = "Error! Could not reach the API. " + error;
} finally {
loading.value = false;
}
}
});
</script>

<template>
<p>
Ask a yes/no question:
<input v-model="question" :disabled="loading" />
</p>
<p>{{ answer }}</p>
</template>

watch 的第一个参数是数据源,可以是一个响应式对象、一个 getter 函数、或多个数据源组成的数据,直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:

1
2
3
4
5
6
7
8
9
const obj = reactive({ count: 0 });

watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
});

obj.count++;

一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调:

1
2
3
4
5
6
watch(
() => state.someObject,
() => {
// 仅当 state.someObject 被替换时触发
}
);

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。

我们可以通过传入 immediate: true 选项来强制侦听器的回调立即执行:

1
2
3
4
5
6
7
8
watch(
source,
(newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
},
{ immediate: true }
{ once: true } // 表示仅初始时监听一次
)

模版引用

使用 ref 来直接访问底层 DOM 元素,它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。

1
<input ref="input" />

要在组合式 API 中获取引用,我们可以使用辅助函数 useTemplateRef()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>
import { useTemplateRef, onMounted } from "vue";

// 第一个参数必须与模板中的 ref 值匹配
const input = useTemplateRef("my-input");

onMounted(() => {
input.value.focus();
});
</script>

<template>
<input ref="my-input" />
</template>

Props

一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props

在使用