Vue 学习笔记(一)

了解 Vue 是什么,认识组合式 API、响应式基础和常用指令

#Vue #学习笔记

前言

这段时间用 Vue 写了包括本站在内的一些项目,或多或少地跟 Vue 代码混了个眼熟,虽然在这个大 AI Coding 时代,写项目很多时候并不需要熟悉一个语言的语法、特性或者优势,但秉承着来都来了的道理,我还是决定把 Vue 基础给学了。

Vue 是什么

介绍

Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助我们高效地开发用户界面。同时也是一个采用声明式渲染、组件化开发、响应式数据绑定和渐进式架构的现代前端框架。

单文件页面

在 Vue 项目中,有单文件组件(也被称为 *.vue 文件,英文 Single-File Components,缩写为 SFC)这种组件书写方式,它将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。

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

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<style scoped>
button {
  font-weight: bold;
}
</style>

组合式 API

组合式 API(Composition API)是 Vue3 的核心特性,相比于 Vue2 的选项式 API,它提供了更灵活的代码组织方式和逻辑复用能力,组合式API允许我们按照逻辑关注点组织代码,而不是按照选项类型。

setup 函数

setup() 函数是在组件中使用组合式 API 的入口,我们可以使用响应式 API 来声明响应式的状态,在 setup() 函数中返回的对象会暴露给模板和组件实例。其他的选项也可以通过组件实例来获取 setup() 暴露的属性。

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      count.value++
    }
    
    return {
      count,
      increment
    }
  }
}

setup 函数的第一个参数是组件的 props。和标准的组件一致,一个 setup 函数的 props 是响应式的,并且会在传入新的 props 时同步更新。

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

传入 setup 函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup 中可能会用到的值:

export default {
  setup(props, context) {
    // 透传 Attributes (非响应式的对象,等价于 $attrs)
    console.log(context.attrs)

    // 插槽(非响应式的对象,等价于 $slots)
    console.log(context.slots)

    // 触发事件(函数,等价于 $emit)
    console.log(context.emit)

    // 暴露公共属性(函数)
    console.log(context.expose)
  }
}

script setup 语法糖

在 Vue3.2 中引入了 <script setup> 语法糖, 我们可以通过在单文件组件(SFC)中使用 <script setup> 来大幅度地简化代码:

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

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

创建 Vue 应用

在创建 Vue 项目之前先安装好 Node.js,安装后在命令行输入 npm create vue@latest,这一指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。接下来按照提示输入项目名并选择各项配置以完成项目创建。

创建项目后在命令行中依次输入:

cd <your-project-name>
npm install
npm run dev

完成依赖安装并启动项目,此时就成功运行了一个 Vue 项目。

main.js 文件中有如下代码:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)
app.mount('#app')

其中,createApp 函数通过接收 App 这个根组件创建一个应用实例,应用实例必须在调用了 .mount() 方法后才会渲染出来,该方法接收一个“容器”参数,应用根组件的内容将会被渲染在容器元素里面。

响应式基础

Vue3 中有两个核心响应式 API,分别是 ref()reactive()

ref()

在组合式 API 中,推荐使用 ref() 函数来声明响应式状态,它可以创建任何类型值的响应式引用。ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

import { ref } from 'vue'

const count = ref(0)

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

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

在模板中使用 ref 时,我们不需要附加 .value,当在模板中使用时,ref 会自动解包:

<div>{{ count }}</div>
<button @click="count++">
  {{ count }}
</button>

reactive()

另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

在模板中使用:

<button @click="state.count++">
  {{ state.count }}
</button>

reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的,为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身。这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:

const raw = {}
const proxy = reactive(raw)

// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

const proxy = reactive({})

proxy.nested = raw

console.log(proxy.nested === raw) // false

两者对比

1、ref() 可以包装任何类型的值,reactive() 只能用于对象类型(如 Map、Set)。

2、ref() 需要使用 .value 访问值,reactive() 可直接访问属性。

3、ref() 可以单独传递和解构并保持响应性,reactive() 对象解构后会失去响应性。

// 使用 ref
const user = ref({ name: '张三', age: 30 })

// 可以保持响应性地传递
function updateUser(userRef) {
  userRef.value.age = 31
}
updateUser(user)

// 使用 reactive
const state = reactive({
  user: { name: '张三', age: 30 }
})

// 解构后会失去响应性
const { user } = state
user.age = 31 // 不会触发更新

// 正确的做法是使用计算属性或直接访问
const userAge = computed(() => state.user.age)
// 或者
function updateAge() {
  state.user.age = 31 // 会触发更新
}

常用指令

v-text 与 v-html

v-text 用于更新元素的文本内容,v-text 通过设置元素的 textContent 属性来工作,因此它将覆盖元素中所有现有的内容。

<span v-text="msg"></span>
<!-- 等同于 -->
<span>{{msg}}</span>

v-html 用来更新元素的 innerHTML,v-html 的内容直接作为普通 HTML 插入—— Vue 模板语法是不会被解析的。

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

<!-- 结果为: 
  Using text interpolation: <span style="color: red">This should be red.</span>
  Using v-html directive: This should be red.
-->

v-show 与 v-if

v-show 基于表达式值的真假性,来改变元素的可见性,v-show 通过设置内联样式的 display CSS 属性来工作,当元素可见时将使用初始 display 值。

v-if 基于表达式值的真假性,来条件性地渲染元素或者模板片段。当 v-if 元素被触发,元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假,那么其内部的内容根本都不会被渲染。可用于 <template> 表示仅包含文本或多个元素的条件块。当条件改变时会触发过渡效果。

此外,还有 v-elsev-else-if 指令可以配合 v-if 使用,实现更复杂的条件渲染。两者必须紧跟在 v-if 后面使用,不然将不会被识别。

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

v-for

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

const items = ref([{ message: 'A' }, { message: 'B' }])
<li v-for="item in items">
  {{ item.message }}
</li>

v-for 块中可以完整地访问父作用域内的属性和变量。v-for 也支持使用可选的第二个参数表示当前项的位置索引。

const parentMessage = ref('Parent')
const items = ref([{ message: 'A' }, { message: 'B' }])
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

结果:

  • Parent - 0 - A

  • Parent - 1 - B

v-for 也可以用来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.values() 的返回值来决定。

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
<ul>
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>

<!-- 可以通过提供第二个参数表示属性名 -->
<li v-for="(value, key) in myObject">
  {{ key }}: {{ value }}
</li>

<!-- 第三个参数表示位置索引 -->
<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

v-for 也可以直接接受一个整数值。在这种用例中,取值范围是 1...n

<span v-for="n in 10">{{ n }}</span>

Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。我们可以为每个元素对应的块提供一个唯一的 key attribute,key 会帮 Vue 识别节点,从而高效地更新 DOM。

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

v-on

v-on 用来给元素绑定事件监听器,可缩写为 @

事件类型由参数来指定。表达式可以是一个方法名,一个内联声明,如果有修饰符则可省略。当用于普通元素,只监听原生 DOM 事件。当用于自定义元素组件,则监听子组件触发的自定义事件。

当监听原生 DOM 事件时,方法接收原生事件作为唯一参数。如果使用内联声明,声明可以访问一个特殊的 $event 变量:v-on:click="handle('ok', $event)"

v-on 还支持绑定不带参数的事件/监听器对的对象。当使用对象语法时,不支持任何修饰符。

<!-- 方法处理函数 -->
<button v-on:click="doThis"></button>

<!-- 动态事件 -->
<button v-on:[event]="doThis"></button>

<!-- 内联声明 -->
<button v-on:click="doThat('hello', $event)"></button>

<!-- 缩写 -->
<button @click="doThis"></button>

<!-- 使用缩写的动态事件 -->
<button @[event]="doThis"></button>

<!-- 停止传播 -->
<button @click.stop="doThis"></button>

<!-- 阻止默认事件 -->
<button @click.prevent="doThis"></button>

<!-- 不带表达式地阻止默认事件 -->
<form @submit.prevent></form>

<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>

<!-- 按键用于 keyAlias 修饰符-->
<input @keyup.enter="onEnter" />

<!-- 点击事件将最多触发一次 -->
<button v-on:click.once="doThis"></button>

<!-- 对象语法 -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>

v-bind

v-bind 用来动态的绑定一个或多个 attribute,即 HTML 属性,可缩写为 :

当用于绑定 classstyle attribute,v-bind 支持额外的值类型如数组或对象。当不带参数使用时,可以用于绑定一个包含了多个 attribute 名称-绑定值对的对象。

<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />

<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>

<!-- 缩写 -->
<img :src="imageSrc" />

<!-- 缩写形式的动态 attribute 名 (3.4+),扩展为 :src="src" -->
<img :src />

<!-- 动态 attribute 名的缩写 -->
<button :[key]="value"></button>

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />

<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]"></div>

<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>

<!-- 绑定对象形式的 attribute -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

<!-- prop 绑定。“prop” 必须在子组件中已声明。 -->
<MyComponent :prop="someThing" />

<!-- 传递子父组件共有的 prop -->
<MyComponent v-bind="$props" />

<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>

v-model

v-model 用于在表单输入元素或组件上创建双向绑定。仅限:<input><select><textarea> 及 components 上使用。

<input v-model="message">

v-once

v-once 用来说明只渲染元素和组件一次,并跳过之后的更新。在随后的重新渲染,元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。

<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
  <h1>Comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

v-pre

v-pre 用来跳过该元素及其所有子元素的编译。当元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。

<span v-pre>{{ this will not be compiled }}</span>