【前端技术】Vue3 02:基础语法

接下来介绍下 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.jsmain.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,我们可以用包含多个选项的对象来描述组件的逻辑,例如 datamethodsmounted。选项所定义的属性都会暴露在函数内部的 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() 的局限性

  1. 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型无效。
  2. 因为 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>

还有其他一些钩子,会在实例生命周期的不同阶段被调用,最常用的是 onMountedonUpdated 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>

📌 页面效果

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹煜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值