接下来介绍下 Vue 的基础语法,包括渐进式框架、单文件组件、组合式 API 和选项式 API等基础要点和 v-xx 指令和其余基础语法,这和官方教程的顺序不大一致,我认为先学习 v-xx 指令可能更有助于大家的理解及学习。
目录
1 前言
1.1 单文件组件
① 计数器示例
② 单文件运行
③ 项目启动路径
1.2 组合式 API 和选项式 API
① 选项式 API
② 组合式 API
2 v-xx 指令
2.1 v-html
2.2 v-bind
2.3 v-if/show
2.4 v-for
① 列表
② 对象
2.5 v-on
① v-on/@
② 修饰符
2.6 v-model
① 基本用法
② 值绑定
③ 修饰符
3 其余基础语法
3.1 响应式基础
① reactive()
② ref()
3.2 计算属性
3.3 Class 与 Style 绑定
① :class
② :style
3.4 生命周期
① 注册周期钩子
② 生命周期图示
3.5 侦听器
① 侦听数据源类型
② 深层侦听器
③ 即时回调的侦听器
④ watchEffect()
3.6 模板引用
① 访问模板引用
② v-for 中的模板引用
3.7 组件基础
① 定义一个组件
② 使用组件
1 前言
Vue 是一个框架,也是一个生态,其功能覆盖了大部分前端开发常见的需求。有一些内容需要提前了解一下,比如单文件组件及API风格,这样才会对 Vue3 有个大致的轮廓。
1.1 单文件组件
在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件,简单来说就是 *.vue 文件。Vue 的单文件组件会将一个组件的逻辑 (JavaScript—script),模板 (HTML—template) 和样式 (CSS—style) 封装在同一个文件里。
① 计数器示例
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<style scoped>
button {
font-weight: bold;
}
</style>
单文件组件是 Vue 的标志性功能。如果你的用例需要进行构建,我们推荐用它来编写 Vue 组件。
② 单文件运行
有时我们只是想单独运行某个Vue文件,不依赖src,类似运行单独的 HTML 文件,可以采取以下方式进行:
安装全局扩展
注意:该工具目前只适配 node 17及以下版本,否则会安装失败(使用 node -v 查看 node 版本)
npm install -g @vue/cli-service-global
cd到某vue文件下运行
vue serve
③ 项目启动路径
可以看到项目启动遵循以下路径,首先是 index.html,他作为首页入口设置页面标题,然后引入 main.js,main.js 相当于是 vue 文件和 index.html 的连接器,它指定哪个 vue 文件作为项目的入口,然后就能看到具体页面了。
那我们测试一下该路径与否,首先创建一个计数器 vue 文件(代码如前边的计数器)👇
第二步在 index.html 修改入口页面的标题:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--修改标题 👇-->
<title>Yinyu App</title>
</head>
<body>
<div id="app"></div>
<!--指定/src/main.js 👇-->
<script type="module" src="/src/main.js"></script>
</body>
</html>
第三步在 main.js 指定计数器的 vue 页面:
import { createApp } from 'vue'
// 指定 count.vue文件
import App from './count.vue'
import './assets/main.css'
//创建vue应用
createApp(App).mount('#app')
最后启动该项目即可,具体页面如下👇
1.2 组合式 API 和选项式 API
Vue2 版本时只存在选项式 API,然后 Vue3 出现了组合式 API,目前这两种方案都是可用的,以下是官方的建议:
- 在学习的过程中,推荐采用更易于自己理解的风格。再强调一下,大部分的核心概念在这两种风格之间都是通用的。熟悉了一种风格以后,你也能够很快地理解另一种风格。
- 在生产项目中:
- 当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。
- 当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件。
① 选项式 API
使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。也就是说,选项式 API 有规定好的格式,更具有规律性。
<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,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup> 搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup> 中的导入和顶层变量/函数都能够在模板中直接使用。
下面是使用了组合式 API 与 <script setup> 改造后和上面的模板完全一样的组件:
<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>
以上选项式 API 和组合式 API 的简单区别,之后还会对其进行深入的了解。
2 v-xx 指令
这里看到的 v-xx attribute 被称为一个指令。指令由 v- 作为前缀,表明它们是一些由 Vue 提供的特殊 attribute,它们将为渲染的 DOM 应用特殊的响应式行为。
2.1 v-html
{{}}-双大括号会将数据解释为纯文本,而不是 HTML。若想插入并解析 HTML,你需要使用 v-html 指令。
📌 语法:
- v-html="html代码"
📌 使用举例
<script setup>
//定义rawHtml
const rawHtml = '<span style="color: red">This should be red.</span>'
</script>
<template>
<p>使用双大括号: {{ rawHtml }} <br /> </p>
<p>使用 v-html 指令:
<span v-html="rawHtml"></span>
</p>
</template>
📌 页面效果
可以看到若使用双大括号,直接会将 HTML 代码输出,而 v-html 则可以直接解析 HTML。
2.2 v-bind
双大括号不能在 HTML attributes/属性 中使用。若要给 HTML 标签的属性赋值,应该使用 v-bind 指令。
📌 语法:
- v-bind:属性名="常量-属性值":给已知属性赋值,属性值通过js动态获取
- :属性名="常量-属性值":缩写,只需要冒号
- :disabled="isDisabled":isDisabled 为 true 或一个空字符串 (即 <button disabled="">) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略
- v-bind:[常量-属性名]="常量-属性值":给未知属性名赋值,属性名以及属性值通过js动态获取
- v-bind="属性集合":动态绑定多个值
📌 使用举例
<script setup>
const url = 'http://www.baidu.com'
const isDisabled = true
const attr1 = "herf"
const attrs = {
href: url,
type: 'text/html'
}
</script>
<template>
<div id="app">
<!--1.给已知属性赋值,属性值通过js动态获取-->
<a v-bind:href="url">点击1 <br /></a>
<!--缩写-->
<a :href="url">点击1(缩写)) <br /></a>
<!--2.isDisabled为false时,该按钮才可用,否则不可用/置灰-->
<button :disabled="isDisabled">按钮</button>
<!--3.给未知属性名赋值,属性名以及属性值通过js动态获取-->
<a v-bind:[attr1]="url"><br />点击2 <br /></a>
<!--4.动态绑定多个值-->
<a v-bind="attrs">点击3(动态绑定多个值) <br /></a>
</div>
</template>
📌 页面效果
2.3 v-if/show
用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值/true时才被渲染。
📌 语法:
- v-if="expression":表达式返回真值时才渲染,也可用在 template 上
- v-else:必须跟在一个 v-if 或者 v-else-if 元素后面
- v-else-if:必须紧跟在一个 v-if 或一个 v-else-if 元素后面。
- v-show="expression":v-show 会在 DOM 渲染中保留该元素,不可用在 template 上
📌 使用举例
<script setup>
const isShow = false
const type = 'C'
</script>
<!--可以在一个<template>元素上使用v-if-->
<template v-if="true">
<h1 v-if="isShow">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>Not A/B</div>
<h1 v-show="isShow">Hello!</h1>
</template>
📌 页面效果
📌 v-if vs v-show
- v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
- v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
- 总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
2.4 v-for
我们可以使用 v-for 指令基于一个数组来渲染一个列表/对象。v-for 指令的值需要使用 item in/of items 形式的特殊语法,其中 items 是源数据的数组/对象,而 item 是迭代项的别名。
Vue 2.2.0+的版本里,当在组件中使用v-for时,key是必须的。key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key,具体如何请看下文。
(这是 Vue 为了以便跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个 key attribute 唯一的)
① 列表
items 即为定义好的列表~
📌 语法:
- <li v-for="item in items" :key="item.message">{{ item.message }}</li>:正常使用,item表示列表中的元素
- <li v-for="(item, index) in items" :key="item.message">{{ index }} - {{ item.message }}</li>:第二个参数表示当前项的位置索引
- <span v-for="n in 10" :key="n">{{ n }}</span>:在 v-for 里使用范围值
- 如何在 <template> 上使用 v-for 和多层嵌套请查看使用举例
📌 使用举例
<script setup>
import { ref ,reactive} from 'vue'
const items = ref([{ message: 'yin'}, { message: 'yu' }])
const parentMessage = ref('Parent')
</script>
<!-- 4.<template> 上使用 v-for -->
<template v-for="it in items" :key="it">
<!-- Vue 2.2.0+的版本里,当在组件中使用v-for时,key是必须的。 -->
<!-- key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key。 -->
<!-- 1.普通 -->
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
<!-- 2.第二个参数表示当前项的位置索引 -->
<li v-for="(item, index) in items" :key="item">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
<!-- 3.在 v-for 里使用范围值 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
<!-- 5.多层嵌套,假设 item有 children元素列表 -->
<li v-for="item in items" :key="item">
<span v-for="childItem in item.children" :key="childItem">
{{ item.message }} {{ childItem }}
</span>
</li>
</template>
📌 页面效果
② 对象
你也可以使用 v-for 来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.keys() 的返回值来决定。
📌 语法:
- <li v-for="value in myObject" :key="value">{{ value }}</li>:遍历对象的所有属性值
- <li v-for="(value, key) in myObject" :key="value">{{ key }}: {{ value }}</li>:第二个参数表示属性名
- <li v-for="(value, key, index) in myObject" :key="value">{{ index }}. {{ key }}: {{ value }}</li>:第三个参数表示位置索引
📌 使用举例
<script setup>
import { ref ,reactive} from 'vue'
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'yinyu',
publishedAt: '2023-05-10'
})
</script>
<template>
<!-- Vue 2.2.0+的版本里,当在组件中使用v-for时,key是必须的。 -->
<!-- 1.遍历对象的所有属性值 -->
<li v-for="value in myObject" :key="value">
{{ value }}
</li>
<!-- 2.第二个参数表示属性名 -->
<li v-for="(value, key) in myObject" :key="value">
{{ key }}: {{ value }}
</li>
<!-- 2.第三个参数表示位置索引 -->
<li v-for="(value, key, index) in myObject" :key="value">
{{ index }}. {{ key }}: {{ value }}
</li>
</template>
📌 页面效果
2.5 v-on
我们可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="handler" 或 @click="handler",主要有以下两种方式。
① v-on/@
📌 语法:
- <button @click="count++">Add 1</button>:内联事件处理器—事件被触发时执行的内联 JavaScript 语句 (与 onclick 类似),通常用于简单场景
- <button @click="方法名">Greet</button>:方法事件处理器—v-on 也可以接受一个方法名或对某个方法的调用
- <button @click="方法名('方法入参')">Say hello</button>:在内联处理器中调用方法
- <button @click="方法名('方法入参1', $event)"> Submit </button>:有时我们需要在内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的 $event 变量
方法与内联事件判断:模板编译器会通过检查 v-on 的值是否是合法的 JavaScript 标识符或属性访问路径来断定是何种形式的事件处理器。比如:foo、foo.bar 和 foo['bar'] 会被视为方法事件处理器,而 foo() 和 count++ 会被视为内联事件处理器。
📌 使用举例
<script setup>
import {ref} from "vue";
const count = ref(0)
const name = ref('yinyu')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
function say(message) {
alert(message)
}
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
</script>
<template>
<!-- 1.内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与 onclick 类似) -->
<button v-on:click="count++" >Add 1</button>
<p>Count is: {{ count }}</p>
<p>----------</p>
<!-- 2.方法事件处理器:v-on 也可以接受一个方法名或对某个方法的调用 -->
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
<p>----------</p>
<!-- 3.在内联处理器中调用方法,允许我们向方法传入自定义参数以代替原生事件 -->
<button @click="say('hello')">Say hello</button>
<p>----------</p>
<!-- 4.在内联处理器中调用方法,使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
</template>
📌 页面效果
② 修饰符
📌 事件修饰符
在处理事件时调用 event.preventDefault() 或 event.stopPropagation() 是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。为解决这一问题,Vue 为 v-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:
<!-- 单击事件将停止传递 -->
<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>
📌 按键修饰符
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
<!-- 仅会在 $event.key 为 'PageDown' 时调用事件处理。 -->
<input @keyup.page-down="onPageDown" />
📌 按键别名
Vue 为一些常用的按键提供了别名:
- .enter
- .tab
- .delete (捕获“Delete”和“Backspace”两个按键)
- .esc
- .space
- .up
- .down
- .left
- .right
📌 系统按键修饰符
你可以使用以下系统按键修饰符来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。
- .ctrl
- .alt
- .shift
- .meta
比如:
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
📌 鼠标按键修饰符
- .left
- .right
- .middle
2.6 v-model
在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,原来代码如下:
<input
:value="text"
@input="event => text = event.target.value">
v-model 指令帮我们简化了这一步骤:
<input v-model="text">
📌 提示
- 文本类型的 <input> 和 <textarea> 元素会绑定 value property 并侦听 input 事件
- <input type="checkbox"> 和 <input type="radio"> 会绑定 checked property 并侦听 change 事件
- <select> 会绑定 value property 并侦听 change 事件
① 基本用法
📌 语法:
- <input v-model="message1" />:文本
- <textarea v-model="message2" placeholder="add multiple lines"></textarea>:多行文本
- <input type="checkbox" id="checkbox" v-model="checked" />:单一的复选框,绑定布尔类型值
- 单选按钮、选择器就请看使用举例了~
📌 使用举例:
<script setup>
import {reactive, ref} from "vue";
const message1 = ref('')
const message2 = ref('')
const checked = ref('false')
const picked = ref('')
const selected1 = ref('')
const selected2 = ref([])
</script>
<template>
<!-- 1.文本 -->
<p>Message is: {{ message1 }}</p>
<input v-model="message1" placeholder="edit me" />
<p>----------</p>
<!-- 2.多行文本 -->
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message2 }}</p>
<textarea v-model="message2" placeholder="add multiple lines"></textarea>
<p>----------</p>
<!-- 3.复选框 -->
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
<p>----------</p>
<!-- 4.单选按钮 -->
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<p>----------</p>
<!-- 5.1选择器-单选 -->
<div>Selected: {{ selected1 }}</div>
<select v-model="selected1">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>----------</p>
<!-- 5.2选择器-多选 -->
<div>Selected: {{ selected2 }}</div>
<select v-model="selected2" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</template>
📌 页面效果
② 值绑定
前边 v-model 绑定的值通常是静态的字符串 (或者对复选框是布尔值),但有时我们可能希望将该值绑定到当前组件实例上的动态数据。这可以通过使用 v-bind 来实现。
📌 语法:
- <input type="checkbox" v-model="checked1" true-value="yes" false-value="no"/>:复选框,true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用
- <input type="checkbox" v-model="checked2" :true-value="dynamicTrue" :false-value="dynamicFalse"/>:可以通过 v-bind 将其绑定为其他动态值
- 单选按钮、选择器就请看使用举例了~
📌 使用举例:
<script setup>
import {ref} from "vue";
const checked1 = ref()
const checked2 = ref()
const dynamicTrue = ref('dynamicTrue')
const dynamicFalse = ref('dynamicFalse')
const pick = ref()
const first = ref('first')
const second = ref('second')
const selected = ref()
</script>
<template>
<!-- 1.1 复选框,true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。-->
<div>Checked: {{ checked1 }}</div>
<input type="checkbox" v-model="checked1" true-value="yes" false-value="no"/>
<p>----------</p>
<!-- 1.2 复选框,可以通过 v-bind 将其绑定为其他动态值-->
<div>Checked: {{ checked2 }}</div>
<input type="checkbox" v-model="checked2" :true-value="dynamicTrue" :false-value="dynamicFalse"/>
<p>----------</p>
<!--2.单选按钮,pick 会在第一个按钮选中时被设为 first,在第二个按钮选中时被设为 second。-->
<div>Picked: {{ pick }}</div>
<input type="radio" v-model="pick" :value="first" />
<input type="radio" v-model="pick" :value="second" />
<p>----------</p>
<!--3.选择器选项,v-model 同样也支持非字符串类型的值绑定!在下面这个例子中,当某个选项被选中,selected 会被设为该对象字面量值 { number: 123 }。-->
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<!-- 内联对象字面量 -->
<option :value="{ number: 123 }">123</option>
</select>
</template>
📌 页面效果
③ 修饰符
- <input v-model.lazy="msg" />:默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据:
- <input v-model.number="age" />:如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入,number 修饰符会在输入框有 type="number" 时自动启用。
- <input v-model.trim="msg" />:如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符
3 其余基础语法
除了 v-xx 指令,还有一些必要的语法需要大家掌握,比如响应式基础、计算属性、生命周期等等~
3.1 响应式基础
① reactive()
我们可以使用 reactive() 函数创建一个响应式对象或数组,能够跟踪对响应式对象属性的访问与更改操作:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
我们可以在同一个作用域下定义更新响应式状态的函数,并将他们作为方法与状态一起暴露出去:
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
📌 页面效果
📌 reactive() 的局限性
- 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型无效。
- 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。若随意地“替换”一个响应式对象,这将导致对初始引用的响应性连接丢失,也就是说响应性失败。
② ref()
基于 reactive() 的局限性,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref!
import { ref } from 'vue'
const count = ref(0)
代码如下:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- 无需 .value -->
</button>
</template>
3.2 计算属性
首先不推荐在{{}}模板中写太多逻辑,这会难以维护,因此 Vue 推荐使用计算属性来描述依赖响应式状态的复杂逻辑:
<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>
📌 页面效果
计算属性值会基于其响应式依赖被缓存,一个计算属性仅会在其响应式依赖更新时才重新计算。也就是说,只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。
📌 可写计算属性
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:
<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(' ')
}
})
function changeBlue(name) {
fullName.value = name
return firstName.value + ' ' + lastName.value
}
</script>
<template>
<p>Has published books:</p>
<span>{{ changeBlue("yin yu") }}</span>
</template>
3.3 Class 与 Style 绑定
Vue 专门为 class 和 style 的 v-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
① :class
:class (v-bind:class 的缩写) 传递一个对象来动态切换 class:
<script setup>
import { ref, computed } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
</script>
<template>
<div class="static" :class="{ active: isActive , 'text-danger': hasError}">yin yu</div>
</template>
📌 页面效果
当 isActive 或者 hasError 改变时,class 列表会随之更新。举例来说,如果 hasError 变为 true,class 列表也会变成 "static active text-danger"。
📌 绑定对象
<script setup>
import {reactive} from 'vue'
const classObject = reactive({
active: true,
'text-danger': false
})
</script>
<template>
<div :class="classObject"></div>
</template>
📌 绑定数组
我们可以给 :class 绑定一个数组来渲染多个 CSS class:
<script setup>
import {reactive, ref} from 'vue'
const activeClass = ref('active')
const errorClass = ref('text-danger')
const isActive = ref(true)
</script>
<template>
<div :class="[activeClass, errorClass]"></div>
<!--如果你也想在数组中有条件地渲染某个 class,你可以使用三元表达式:-->
<div :class="[isActive ? activeClass : '', errorClass]"></div>
</template>
② :style
:style 支持绑定 JavaScript 对象值,对应的是 HTML 元素的 style 属性:
<script setup>
import {reactive, ref} from 'vue'
const activeColor = ref('green')
const fontSize = ref(30)
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">yin yu</div>
</template>
📌 页面效果
📌 绑定对象
直接绑定一个样式对象可以使模板更加简洁:
<script setup>
import {reactive, ref} from 'vue'
const styleObject = reactive({
color: 'red',
fontSize: '13px'
})
</script>
<template>
<div :style="styleObject">yin yu</div>
</template>
📌 绑定数组
<script setup>
import {reactive, ref} from 'vue'
const baseStyles = ref({color:'green'})
const overridingStyles = ref({fontSize:'30px'})
</script>
<template>
<div :style="[baseStyles, overridingStyles]">yin yu</div>
</template>
3.4 生命周期
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。
① 注册周期钩子
比如,onMounted 钩子可以用来在组件完成初始渲染并创建 DOM 节点后运行代码:
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
还有其他一些钩子,会在实例生命周期的不同阶段被调用,最常用的是 onMounted、onUpdated 和 onUnmounted。
所有生命周期钩子的完整参考及其用法请参考:组合式 API:生命周期钩子 | Vue.js (vuejs.org)
② 生命周期图示
下面是实例生命周期的图表:
3.5 侦听器
在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数,简单来说就是侦听状态的改变并作出反应。
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
// 可以直接侦听一个 ref
// 第一个参数是侦听器的源,可以是诸多类型
// 第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
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
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
📌 页面效果
① 侦听数据源类型
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:
const x = ref(0)
const y = ref(0)
const obj = reactive({ count: 0 })
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
// 提供一个 getter 函数,监听响应式对象的属性值
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
② 深层侦听器
直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
})
obj.count++
③ 即时回调的侦听器
watch 默认是懒执行的:仅当数据源变化时,才会执行回调。我们可以通过传入 immediate: true 选项来强制侦听器的回调立即执行:
watch(source, (newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
}, { immediate: true })
④ watchEffect()
watchEffect() 允许我们自动跟踪回调的响应式依赖,此处回调会立即执行,不需要指定 immediate: true。在执行期间,它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。有了 watchEffect(),我们不再需要明确传递 todoId 作为源值。
const todoId = ref(1)
const data = ref(null)
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
3.6 模板引用
ref 是一个特殊的 attribute,和 v-for 章节中提到的 key 类似。它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。
① 访问模板引用
你只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 input,在初次渲染时会是 null。这是因为在初次渲染前这个元素还不存在!
<template>
<div>
<input ref="element">
<br /> {{ item }}
</div>
</template>
<script setup>
import { ref , watchEffect} from 'vue';
const element=ref(null)
const item = ref("yinyu");
watchEffect(()=>{
if(element.value){
console.log(element);
}else{
console.log('未加载');
}
})
</script>
📌 页面效果
② v-for 中的模板引用
需要 v3.2.25 及以上版本
当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素:
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
/* ... */
])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
3.7 组件基础
Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。
① 定义一个组件
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC):
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
② 使用组件
要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue 的文件中,这个组件将会以默认导出的形式被暴露给外部。
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
📌 页面效果