Vue3 核心知识点笔记(超级详细教程)

发布于:2025-08-05 ⋅ 阅读:(12) ⋅ 点赞:(0)

在 Vue 开发中,掌握响应式数据处理、模板语法、生命周期等核心知识点是构建高效应用的基础。这篇笔记将围绕 Vue 中的关键概念展开,通过问题引导、代码示例和总结帮助你梳理知识点。

一、ref和reactive创建基本类型的响应式数据

在以前的学习中,我们并没有接触过响应式数据这个概念,那么响应式数据到底是什么呢?接下来我会用一个例子来带大家了解什么在vue中什么是响应式数据:

<script setup lang="ts">
let num1 = 0  // 创建变量num1
</script>
<template>
  <div>我的数字:{{num1}}</div>  // 显示变量num1
  <button @click="num1++">点击数字+1</button>  // 设置点击事件,点击后num1+1
</template>

  

但是我们实际点击按钮的时候,num1始终是原来初始的值,始终不会改变,本质上的原因是因为在vue中分为响应式数据和非响应式数据,直接这么定义的数据是非响应数据,当然也不会被点击事件出发,所以当我们要设置响应式数据的时候就需要显性的告诉编译器。

在 Vue 开发中,掌握响应式数据处理、模板语法、生命周期等核心知识点是构建高效应用的基础。这篇笔记将围绕 Vue 中的关键概念展开,通过问题引导、代码示例和总结帮助你梳理知识点。

1.1 ref创建基本类型的响应式数据

ref 可以用来创建基本类型(如数字、字符串、布尔值)的响应式数据,也可以创建对象类型的响应式数据。它的内部会将基本类型数据包装成一个包含 value 属性的对象。​

问题:如何用 ref 创建一个响应式的计数器,并在点击按钮时更新它?

<template>
  <div>
    <p>计数器:{{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

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

// 创建基本类型的响应式数据
const count = ref(0)

// 点击事件:更新数据(注意在脚本中需要通过 .value 访问)
const increment = () => {
  count.value++
}
</script>

运行结果:

说明:​

  • 模板中使用 ref 创建的数据时,不需要加 .value,Vue 会自动解析。​
  • 脚本中修改数据时,必须通过 .value 访问,因为 ref 包装后的数据是一个响应式对象。

1.2 reactive创建基本类型的响应式数据

当我们遇到对象类型的数据,数据的层级很深的时候,最后还需要.value来访问,这时候使用我吗的reactive定义响应式数据的话就会简单很多,reactive 主要用于创建对象类型的响应式数据,它返回一个响应式的代理对象。注意:reactive 不能直接用于基本类型数据,否则无法实现响应式。例如:

<template>
  <div>
    <!-- 直接用 reactive 包裹基本类型,数据更新后视图不会响应 -->
    <p>错误示例(非响应式):{{ wrongNum }}</p>
    <button @click="updateWrong">更新错误示例</button>

    <!-- 正确:将基本类型放在对象中 -->
    <p>正确示例(响应式):{{ rightData.num }}</p>
    <button @click="updateRight">更新正确示例</button>
  </div>
</template>

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

// 错误用法:reactive 直接包裹基本类型,不具备响应式
const wrongNum = reactive(10)

// 正确用法:将基本类型放在对象中
const rightData = reactive({
  num: 10
})

const updateWrong = () => {
  wrongNum = 20 // 视图不会更新
}

const updateRight = () => {
  rightData.num = 20 // 视图会响应更新
}
</script>

1.3 ref和reactive的对比和总结

特性

ref

reactive

适用类型

基本类型、对象类型

对象类型(对象、数组等)

访问方式

脚本中需用 .value,模板中不用

直接访问属性(无需 .value)

本质

包装成含 value 的对象

返回对象的响应式代理

推荐场景

基本类型、简单对象或数组

复杂对象(如嵌套对象)

二、toRefs和toRef响应式转换

当我们从 reactive 创建的响应式对象中解构属性时,解构后的属性会失去响应式。如何解决这个问题?toRefs 和 toRef 就是用来处理这种场景的工具。

错误示例:

    2.1 toRefs

    toRefs 可以将一个响应式对象(reactive 创建的)转换为一个普通对象,其中每个属性都是一个 ref 类型的响应式数据。​

    问题:如果直接解构 reactive 创建的对象,属性会失去响应式,如何用 toRefs 解决这个问题?

    <template>
      <div>
        <p>姓名:{{ name }}</p>
        <p>年龄:{{ age }}</p>
        <button @click="updateInfo">更新信息</button>
      </div>
    </template>
    
    <script setup>
    import { reactive, toRefs } from 'vue'
    
    // 创建响应式对象
    const user = reactive({
      name: '张三',
      age: 20
    })
    
    // 用 toRefs 转换后解构,属性仍保持响应式
    const { name, age } = toRefs(user)
    
    // 更新数据
    const updateInfo = () => {
      name.value = '李四' 
      age.value = 21
      console.log(user.name, user.age) // 更新解构后的数据,会改变原来对象的值  
    }
    </script>
    

    说明:​

    • toRefs 会为对象的每个属性创建一个 ref,因此解构后仍能保持响应式,并且解构后的的数据还和原来的对象形数据保持关联。​
    • 适用于需要批量解构响应式对象属性的场景。

    2.2 toRef

    toRef 用于为响应式对象的单个属性创建一个 ref,与原对象保持关联。​

    问题:如果只需要响应式对象中的某一个属性,如何用 toRef 单独提取?

    基本语法:

    toref(对象名,单独提取的属性名)

    <template>
      <div>
        <p>年龄:{{ ageRef }}</p>
        <button @click="increaseAge">年龄+1</button>
      </div>
    </template>
    
    <script setup>
    import { reactive, toRef } from 'vue'
    
    const user = reactive({
      name: '张三',
      age: 20
    })
    
    // 单独提取 age 属性为 ref
    const ageRef = toRef(user, 'age')
    
    // 更新属性
    const increaseAge = () => {
      ageRef.value++ // 原对象的 age 也会同步更新
      console.log(user.age)  // 输出原来对象的 age
    }
    </script>
    

    说明:​

    • toRef 的第一个参数是响应式对象,第二个参数是属性名。​
    • 提取的 ref 与原对象属性联动:修改 ref 的值,原对象属性会同步更新,反之亦然。

    2.3.toRefs和toRef的对比和总结

    特性

    toRefs

    toRef

    作用

    批量转换响应式对象的所有属性

    单独转换响应式对象的单个属性

    输入

    响应式对象

    响应式对象 + 属性名

    输出

    包含多个 ref 的普通对象

    单个 ref 对象

    适用场景

    需要解构多个属性时

    只需要单个属性时

    总结
    • toRefs 和 toRef 都用于reactive定义的响应式对象解包的时候来保留响应式对象属性的响应式,避免解构后失去联动。
    • 两者创建的 ref 都与原对象属性关联,修改时会相互影响。

    三、模板语法

    Vue 的模板语法是声明式的,用于将数据渲染到 DOM 中。常用的模板渲染方式有文本插值、原始 HTML 插入纯文本插入

    3.1 文本插值

    文本插值是最基础的模板语法,使用双大括号 {{ }} 将数据插入到模板(HTML标签)中。

    问题:如何在模板中显示响应式数据,并在数据更新时自动刷新?

    <template>
      <div>
        <p>用户名:{{ username }}</p>
        <p>当前时间:{{ new Date().toLocaleString() }}</p> <!-- 支持表达式 -->
      </div>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    const username = ref('小宁爱Vue!')
    </script>

     

    3.2 原始HTML

    如果需要插入 HTML 片段(而非纯文本),可以使用 v-html 指令。​

    问题:如何在模板中渲染一段包含标签的 HTML 字符串?

    <template>
      <div>
        <!--  直接插值会显示纯文本  -->
        <p>{{htmlStr}}</p>
        <!--  v-html 插入HTML片段,会解析标签  -->
        <p v-html="htmlStr"></p>
      </div>
    </template>
    
    <script setup>
    const htmlStr = '<strong style="color:red">这是红色的文本</strong>'
    </script>

    注意:​

    • 动态渲染 HTML 存在 XSS 风险,仅在信任的内容中使用 v-html。​
    • v-html 会覆盖元素的子节点。

    3.3 插入纯文本

    除了双大括号,v-text 指令也可以用于插入纯文本,功能与 {{ }} 类似,但优先级更高。​

    问题:v-text 和 {{ }} 有什么区别?

    <template>
      <div>
        <p v-text="message+fg"></p> <!-- 等价于 {{ message }} -->
        <p>{{ message }} 后面的内容</p> <!-- 插值可以与其他内容混合 -->
      </div>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    const fg = ref('fg')
    const message = ref('Hello Vue')
    </script>

     

      说明:​

      • v-text 会替换整个元素的内容,而 {{ }} 可以嵌入到元素的其他内容中。​
      • 两者都会将内容作为纯文本处理,不会解析 HTML。

      四、computed计算属性

      在模板中,我们有时需要对数据进行处理后再显示(如格式化、计算)。如果直接在模板中写复杂逻辑,会导致模板臃肿。computed 计算属性就是用来解决这个问题的。​

      基础语法1:(只读)

      const 变量名 = conpeted(回调函数)

      基础语法2:(改)

      const 变量名 = competed(

      {

              get:()=>{return 要修改的值}

              ste:(变量名(也就是接受要修改的值))=>{函数体}

      }

      )

      问题:如何根据两个响应式数据(商品单价和数量)计算总价,并在数据变化时自动更新?

      <template>
        <div>
          <p>单价:{{ price }} 元</p>
          <p>数量:{{ quantity }} 个</p>
          <p>总价:{{ totalPrice }} 元</p> <!-- 计算属性 -->
          <button @click="price++">提高单价</button>
          <button @click="quantity++">增加数量</button>
        </div>
      </template>
      
      <script setup>
      import { ref, computed } from 'vue'
      
      const price = ref(10)
      const quantity = ref(2)
      
      // 计算属性:依赖 price 和 quantity,自动追踪依赖并缓存结果
      const totalPrice = computed(() => {
        return price.value * quantity.value
      })
      </script>

       

      特性:​

      • 缓存性:计算属性会缓存结果,只有当依赖的响应式数据(其实就是回调函数的返回值发生变化的时候)变化时,才会重新计算。​
      • 只读性:默认情况下,计算属性是只读的。如果需要修改,可通过 get 和 set 定义:
      <template>
        <p>{{firstName}}</p>
        <p>{{lastName}}</p>
        <button @click="fullName = '小妹'">修改名字为小妹</button>
        <p>{{fullName}}</p>
      </template>
      
      <script setup>
      import {ref, reactive, computed} from "vue";
        let firstName = ref("李")
        let lastName = ref("小妹")
        let fullName = computed({
          get:()=> {  // 获取fullName的时候调用
            return firstName.value + lastName.value
          },
          set:(value)=>{   // 修改fullName的时候调用
            let names = value.split(" ")
            firstName.value = '小'
            lastName.value = '宁'
          }
        }
        )
      </script>

       注意:

      这种写法competed的参数实际上是一个对象(键值对)类型,这个对象类型包含了两个回调函数,一个是get回调函数,另外一个是set回调函数,get函数在访问计算属性的时候调用,set(参数)在修改就算属性的时候调用,set中的参数实际上指的是get函数中的返回内容,这个名字可以自己随意取。

      总结:​

      • 计算属性适用于依赖其他数据派生的场景,避免模板中的复杂逻辑。​
      • 与方法相比,计算属性的缓存特性能提升性能(方法每次渲染都会重新执行)。

      五、v-bind:属性绑定

      在 Vue 中,我们需要动态绑定 HTML 属性(如 src、class、style 等)时,使用 v-bind 指令。

      5.1 v-bind:属性名="变量名"

      v-bind:属性名 用于将 HTML 属性与响应式数据绑定,属性值会随数据变化而更新。​

      问题:如何动态绑定图片的 src 属性,实现点击切换图片(图片为网络地址可以直接使用)?

      <template>
        <div>
          <img v-bind:src="imgUrl" alt="图片" width="200px">
          <button @click="toggleImg">切换图片</button>
        </div>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const imgList = [
        'https://picsum.photos/id/1/200/200',
        'https://picsum.photos/id/2/200/200'
      ]
      
      const imgUrl = ref(imgList[0])
      
      const toggleImg = () => {  
        // 使用三元运算符:对图片的切换
        imgUrl.value = imgUrl.value === imgList[0] ? imgList[1] : imgList[0]
      }
      </script>

      5.2 简写  :属性名="变量名"

      v-bind 有一个简写形式:省略 v-bind,直接用 :属性名。​

      问题:如何用简写形式绑定 a 标签的 href 属性?

      <template>
        <a :href="linkUrl" target="_blank">访问 Vue 官网</a>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      const linkUrl = ref('https://vuejs.org/')
      </script>

       

      总结:​

      • v-bind 用于动态绑定属性,简写为 :。​
      • 绑定的值可以是变量、表达式或对象(如 :class="{ active: isActive }")。

      六、v-on:事件绑定

      在 Vue 中,处理用户交互(如点击、输入)需要绑定事件,v-on 指令用于事件绑定。

      6.1 v-on:事件类型="函数名称"

      v-on:事件类型 用于绑定事件,值为事件处理函数。​

      问题:如何为按钮绑定点击事件,实现弹窗提示?

      <template>
        <button v-on:click="showMessage">点击弹窗</button>
      </template>
      
      <script setup>
      const showMessage = () => {
        alert('Hello, v-on!')
      }
      </script>

      6.2 @事件类型="函数名称"

      v-on 的简写形式是 @,即 @事件类型="函数名称",更常用。​

      问题:如何用简写形式绑定输入框的 input 事件,实时获取输入内容?

      <template>
        <div>
          <input type="text" @input="handleInput" placeholder="输入内容">
          <p>实时监视你输入的内容:{{ inputValue }}</p>
        </div>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const inputValue = ref('')
      
      // 事件处理函数接收事件对象
      const handleInput = (e) => {
        inputValue.value = e.target.value
      }
      </script>

       

      6.3 常见的事件类型

      1. 鼠标事件

      • @click:单击元素时触发,适用于按钮点击、菜单选择等场景。
      • @dblclick:双击元素时触发,可用于文件打开、特殊操作等场景。
      • @mouseenter:鼠标进入元素时触发,常用于悬停效果、下拉菜单显示等。
      • @mouseleave:鼠标离开元素时触发,适用于悬停效果消失、菜单隐藏等。
      • @mouseover:鼠标悬停在元素上时触发,类似mouseenter但会冒泡。
      • @mouseout:鼠标移出元素时触发,类似mouseleave但会冒泡。
      • @mousedown:鼠标按钮按下时触发,可用于拖拽操作开始等场景。
      • @mouseup:鼠标按钮释放时触发,适用于拖拽操作结束等场景。

      2. 键盘事件

      • @keydown:按下任意键时触发,常用于键盘快捷键、游戏控制等。
      • @keyup:释放按键时触发,适用于表单提交、字符输入确认等。
      • @keypress:按下字符键时触发,用于字符输入处理(已不推荐使用)。
      • @keyup.enter:按下回车键时触发,可用于表单提交、换行等场景。
      • @keyup.tab:按下 Tab 键时触发,适用于焦点切换等场景。
      • @keyup.delete:按下删除键时触发,用于删除操作等场景。
      • @keyup.esc:按下 ESC 键时触发,适用于取消操作、关闭弹窗等场景。

      3. 表单事件

      • @input:输入框内容改变时触发,可实时获取输入值(如实时显示用户输入内容)。
      • @change:表单元素值改变并失去焦点时触发,适用于下拉选择、复选框状态改变等场景。
      • @focus:元素获得焦点时触发,常用于输入框激活状态处理等。
      • @blur:元素失去焦点时触发,可用于表单验证、自动保存等场景。
      • @submit:表单提交时触发,适用于表单数据提交等场景。

      4. 窗口事件

      • @resize:窗口大小改变时触发,常用于响应式布局调整等场景。
      • @scroll:滚动条滚动时触发,适用于滚动监听、懒加载等场景。
      • @load:页面或资源加载完成时触发,可用于图片加载完成、初始化操作等。
      • @unload:页面卸载时触发,适用于数据保存、清理工作等场景。

      5. 触摸事件

      • @touchstart:触摸开始时触发,适用于移动端触摸操作的起始阶段。
      • @touchmove:触摸移动时触发,常用于手势操作、滑动等场景。
      • @touchend:触摸结束时触发,适用于触摸操作完成的场景。

      超级适用的输入框提交事件(必学): 

      <template>
         <input type="text" @keyup.enter="handleEnter" placeholder="按Enter提交">
      </template>
      
      <script setup>
        const handleEnter = (e)=>{
          console.log(e.target.value)
          alert('提交成功')
        }
      </script>

      总结:​

      • 事件绑定用 v-on:事件类型 或简写 @事件类型。​
      • 事件处理函数可以接收事件对象

      七、v-model:表单输入双向绑定

      在处理表单时,我们常常需要将表单元素的值与响应式数据双向绑定,即数据变化时表单更新,表单输入时数据也同步更新。v-model 就是用于实现这种双向绑定的指令。

      7.1 v-model 双向数据绑定

      v-model 可以在表单元素(如输入框、复选框、下拉框等)上创建双向数据绑定,它会根据表单元素的类型自动选择合适的绑定方式。

      问题:如何用 v-model 实现输入框与数据的双向绑定,使得输入内容实时同步到数据,且数据修改时输入框也随之更新?

      <template>
        <div>
          <input type="text" v-model="message">
          <p>你输入的是:{{message}}</p>
          <button @click="clear">清空</button>
        </div>
      </template>
      
      <script setup>
      import {ref} from "vue";
      
      let message = ref('')
      function clear() {
        message.value = ''
      }
      </script>

      说明:​

      • v-model 本质上是语法糖,它会根据表单元素类型绑定 value 属性和 input 事件(或其他对应事件)。​
      • 对于不同的表单元素,v-model 的绑定逻辑不同:​
      • 文本框 / 文本域:绑定 value 和 input 事件​
      • 复选框:绑定 checked 和 change 事件​
      • 单选按钮:绑定 checked 和 change 事件​
      • 下拉框:绑定 value 和 change 事件

      7.2 三种方法阻止form表单的默认提交

      当表单使用 submit 按钮提交时,默认会触发页面刷新,这在单页应用中通常是不需要的。以下是三种阻止表单默认提交行为的方法:

      方法一:使用 @submit.prevent 修饰符(推荐)

      <template>
        <form @submit.prevent="handleSubmit">
          <input type="text" v-model="username">
          <button type="submit">提交</button>
        </form>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const username = ref('')
      
      const handleSubmit = () => {
        console.log('提交的用户名:', username.value)
        // 自定义提交逻辑(如接口请求)
      }
      </script>

      方法二:在事件处理函数中调用 e.preventDefault()​ 

      <template>
        <form @submit="handleSubmit">
          <input type="text" v-model="username">
          <button type="submit">提交</button>
        </form>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const username = ref('')
      
      const handleSubmit = (e) => {
        e.preventDefault() // 阻止默认行为
        console.log('提交的用户名:', username.value)
      }
      </script>

       方法三:将按钮类型改为 button,手动触发提交

      <template>
        <form>
          <input type="text" v-model="username">
          <button type="button" @click="handleSubmit">提交</button>
        </form>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const username = ref('')
      
      const handleSubmit = () => {
        console.log('提交的用户名:', username.value)
      }
      </script>

      总结:​

      • @submit.prevent 是最简洁的方法,直接在模板中通过修饰符阻止默认行为。​
      • 方法二适合需要在阻止默认行为前执行额外逻辑的场景。​
      • 方法三通过改变按钮类型避免触发表单提交,适合完全自定义提交时机的场景。

      八、Class Style绑定

      在 Vue 中,我们可以通过 v-bind 动态绑定元素的 class 和 style,实现样式的动态变化

      8.1 Class绑定

      动态绑定 class 时,可以使用对象数组字符串形式,根据条件切换类名。​

      问题:如何根据不同的条件为元素动态添加或移除类名(如激活状态、错误状态)?

      方式一:对象语法(根据条件添加类名)

      基本语法:

      :class = "{变量名2:布尔值变量1,变量名1:布尔值变量2.......}"

      <template>
          <h1 :class="{active:isActive,error:hasError}">我是对象型语法的class绑定</h1>
          <button @click ="changeActive">点我激活</button>
          <button @click = changeError>点我失活</button>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      const isActive = ref(true)
      const hasError = ref(false)
      function changeActive(){
        isActive.value = !isActive.value
      }
      function changeError(){
        hasError.value = !hasError.value
      }
      </script>
      
      <style>
      .active{
        color:red;
        border: #3a5fc6 1px solid;
      }
      .error{
        background-color: burlywood;
      }
      
      </style>

       方式二:数组语法(列表形式添加类名)

      基本语法:

      :class = "[变量1,变量2.......]"(注意:变量绑定是属性的类名,可以通过改变类名来改变样式)

      <template>
        <div :class="[baseClass, activeClass]">
          数组形式Class绑定
        </div>
        <button @click="activeClass = activeClass === 'active' ? '' : 'active'">改变动态的样式</button>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const baseClass = ref('box')
      const activeClass = ref('active')
      </script>
      
      <style>
      .box {
        color: red;
      }
      .active {
        background-color: blue;
      }
      </style>

      点击按钮后: 

       方式三:数组 + 对象结合(条件性添加数组中的类名)

      <template>
              <!--    数组类型            对象类型的class绑定    -->
        <div :class="[baseClass, isActive ? activeClass : '']">
          混合形式Class绑定
        </div>
        <button @click="changeActive">点击切换成动态样式</button>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const baseClass = ref('box')
      const activeClass = ref('active')
      const isActive = ref(true)   // 设置默认值为true
      function changeActive(){
        isActive.value = !isActive.value
      }
      </script>
      
      <style>
      .box{
        background-color: burlywood;
      }
      .active{
        color:red;
        border: #3a5fc6 1px solid;
      }
      </style>

      点击切换动态的样式:

      8.2 Style 绑定

      动态绑定 style 时,同样可以使用对象或数组形式,直接设置内联样式。​

      问题:如何根据响应式数据动态修改元素的内联样式(如颜色、字体大小)?​

      方式一:对象语法(键为 CSS 属性,值为样式值)

      基本语法:

      :style = "{属性1:变量1,属性2:变量2.......}

      <template>
        <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
          动态Style示例
        </div>
        <button @click="changeStyle">改变样式</button>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const textColor = ref('black')
      const fontSize = ref(16)
      
      const changeStyle = () => {
        textColor.value = 'red'
        fontSize.value = 20
      }
      </script>

       方式二:数组语法(多个样式对象合并

      <template>
        <div :style="[baseStyle, activeStyle]">
          数组形式Style绑定
        </div>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const baseStyle = ref({
        padding: '10px',
        border: '1px solid #ccc'
      })
      
      const activeStyle = ref({
        backgroundColor: 'yellow'
      })
      </script>

      总结:​

      • class 绑定适合通过预定义的类名切换样式,维护性更好。​
      • style 绑定适合直接动态设置具体样式值,灵活性更高。​
      • 两者都支持对象和数组语法,可根据场景选择使用。

      九、条件渲染

      在开发中,我们经常需要根据条件显示隐藏元素,Vue 提供了 v-if 和 v-show 两种指令实现条件渲染。

      9.1 v-if / v-else-if / v-else

      v-if 用于根据条件渲染元素,条件为 false 时,元素会被从 DOM 中移除;v-else-if 和 v-else 用于搭配 v-if 实现多条件判断。​

      问题:如何根据用户角色(管理员、普通用户、游客)显示不同的内容?

      <template>
        <div>
          <div v-if="role === 'admin'">
            <h3>管理员面板</h3>
            <p>拥有所有权限</p>
          </div>
          <div v-else-if="role === 'user'">
            <h3>用户面板</h3>
            <p>拥有部分权限</p>
          </div>
          <div v-else>
            <h3>访客面板</h3>
            <p>请先登录查看更多权限</p>
          </div>
          <button @click="changeRole">切换角色</button>
        </div>
      </template>
      
      <script setup>
        import { ref } from 'vue'
        const role = ref("admin")
        function changeRole() {
          if(role.value === "admin"){
            role.value = "user"
          }
          else if(role.value === "user"){
            role.value = "guest"
          }
          else{
            role.value = "admin"
          }
        }
      </script>

      说明:​

      • v-else-if 和 v-else 必须紧跟在 v-if 或 v-else-if 之后,否则无法识别。​
      • 当条件切换时,v-if 会销毁和重建元素,因此其内部的组件也会经历销毁和创建的生命周期。

      9.2 v-show

      v-show 也用于根据条件显示元素,但它不会移除元素,而是通过设置 display: none 来隐藏元素。​

      问题:如何实现一个可以显示 / 隐藏的提示框,且切换时性能更好?

      <template>
        <div>
          <div v-show="isVisible" class="alert">
            这是一个提示框
          </div>
          <button @click="toggleVisible">显示/隐藏</button>
        </div>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const isVisible = ref(true)
      
      const toggleVisible = () => {
        isVisible.value = !isVisible.value
      }
      </script>

      说明:​

      • v-show 不支持 v-else,也不能用于 <template> 标签。​
      • 元素始终存在于 DOM 中,只是通过 CSS 控制显示 / 隐藏。

      9.3 v-if和v-show的区别

      特性 v-if v-show
      渲染方式 条件为 false 时移除 DOM 元素 条件为 false 时设置 display: none
      初始渲染成本 条件为 false 时,无初始渲染成本 无论条件如何,都有初始渲染成本
      切换成本 较高(涉及 DOM 操作) 较低(仅修改 CSS)
      适用场景 条件很少切换的场景 条件频繁切换的场景
      其他 支持 v-else 不支持 v-else

      十、v-for列表渲染

      v-for 用于基于数组或对象渲染列表,它可以遍历数组、对象、字符串或数字。​

      问题:如何遍历一个商品列表,并显示每个商品的名称和价格?

      <template>
        <div>
          <h3>商品列表</h3>
          <ul>
            <li v-for="(product, index) in products" :key="product.id">
              {{ index + 1 }}. {{ product.name }} - ¥{{ product.price }}
            </li>
          </ul>
        </div>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      
      const products = ref([
        { id: 1, name: '手机', price: 3999 },
        { id: 2, name: '电脑', price: 5999 },
        { id: 3, name: '平板', price: 2999 }
      ])
      </script>

      说明:​

      • v-for 的语法为 (item, index) in 数组,index 为可选的索引值。​
      • 必须为 v-for 渲染的元素添加 :key 属性,且 key 的值需唯一(通常使用数据的唯一标识,如 id),目的是帮助 Vue 识别元素的身份,提高列表渲染性能。

       遍历对象:

      <template>
        <div>
          <h3>用户信息</h3>
          <ul>
            <!--三个参数:     值     键名   下标(0开始)     -->
            <li v-for="(value, key, index) in user" :key="key">
               {{index + 1}}.{{ key }}: {{ value }}
            </li>
            <!--两个参数:     值     键名     -->
            <li v-for="(value, key) in user" :key="key">
               {{ key }}: {{ value }}
            </li>
            <!--一个参数:     值         -->
            <li v-for="(value) in user" :key="key">
               {{ value }}
            </li>
          </ul>
        </div>
      </template>
      <script setup>
      import { ref } from 'vue'
      const user = ref({
        name: '张三',
        age: 20,
        gender: '男'
      })
      </script>

       

      注意事项:​

      • 不要在同一元素上同时使用 v-if 和 v-for(v-for 的优先级更高,可能导致逻辑混乱)。​
      • 当数组发生变化时,Vue 会通过 “就地更新” 的策略更新列表,只重新渲染变化的元素,提高性能。

      十一、watch监听器

      在 Vue 中,当我们需要在数据变化时执行一些副作用操作(如日志记录、数据请求等),可以使用 watch 监听器。它能监听指定的响应式数据,并在数据变化时触发回调函数。

      11.1 情况一:监视ref定义的基本类型数据

      基础语法:

      watch(监听的数据, (新值, 老值) => {
        函数体
      })
      <template>
        <div>
          <p>计数器:{{ count }}</p>
          <button @click="count++">+1</button>
        </div>
      </template>
      
      <script setup>
      import { ref, watch } from 'vue'
      
      const count = ref(0)
      
      // 监视ref定义的基本类型数据
      watch(count, (newValue, oldValue) => {
        console.log(`count变化了:旧值${oldValue},新值${newValue}`)
      })
      </script>

      • watch 的第一个参数是要监听的目标(这里是 count)。​
      • 第二个参数是回调函数,接收两个参数:newValue(变化后的值)和 oldValue(变化前的值)。

      11.2 情况二:监视ref定义的对象类型数据

      基础语法:

      watch(监听的对象, (新值, 老值) => {
        函数体
      }, { deep: true })

      当 ref 包裹的是对象类型数据时,watch 默认不会深度监听对象内部属性的变化,需要通过 deep: true 开启深度监听。​

      问题:如何监听 ref 定义的对象类型数据中某个属性的变化?

      <template>
        <div>
          <p>用户信息:{{ user }}</p>
          <button @click="user.age++">年龄+1</button>
          <button @click="user.name = '李四'">修改姓名</button>
        </div>
      </template>
      
      <script setup>
      import { ref, watch } from 'vue'
      
      const user = ref({
        name: '张三',
        age: 20
      })
      
      // 监视ref定义的对象类型数据(默认浅监听,对象内部属性变化不会触发)
      // 需要添加deep: true开启深度监听
      watch(user, (newValue, oldValue) => {
        console.log('user变化了', newValue, oldValue)
      }, { deep: true })
      </script>

      注意:​

      • 对于 ref 定义的对象类型数据,watch 监听的是对象的引用变化。若要监听对象内部属性变化,必须设置 deep: true。​
      • 开启深度监听后,对象任何深层属性变化都会触发回调,但 oldValue 可能不准确(与 newValue 指向同一对象)。

      11.3 情况三:监视reactive定义的对象类型数据

      基础语法:

      watch((监听的对象, (新值, 老值) =>

      { 函数体

      })

      reactive 定义的对象类型数据,watch 默认会进行深度监听,无需手动设置 deep: true。​

      问题:如何监听 reactive 定义的对象类型数据的变化?

      <template>
        <div>
          <p>用户信息:{{ user }}</p>
          <button @click="user.age++">年龄+1</button>
        </div>
      </template>
      
      <script setup>
      import { reactive, watch } from 'vue'
      
      const user = reactive({
        name: '张三',
        age: 20
      })
      
      // 监视reactive定义的对象类型数据(默认深度监听)
      watch(user, (newValue, oldValue) => {
        console.log('user变化了', newValue, oldValue)
      })
      </script>

      说明:​

      • reactive 定义的对象,watch 会自动深度监听,任何属性变化都会触发回调。​
      • 此时 oldValue 同样可能不准确,因为对象是引用类型。

      11.4 情况四:监视对象中的某个属性

      基础语法:

      watch(对象,属性, (新值, 老值) =>

      { 函数体

      })

      当只需要监听对象中的某个特定属性时,可以通过函数形式指定要监听的属性。​

      问题:如何只监听对象中 age 属性的变化,忽略其他属性?

      <template>
        <div>
          <p>用户信息:{{ user }}</p>
          <button @click="user.age++">年龄+1</button>
          <button @click="user.name = '李四'">修改姓名</button>
        </div>
      </template>
      
      <script setup>
      import { reactive, watch } from 'vue'
      
      const user = reactive({
        name: '张三',
        age: 20
      })
      
      // 监视对象中的某个属性(用函数返回要监听的属性)
      watch(() => user.age, (newValue, oldValue) => {
        console.log(`age变化了:旧值${oldValue},新值${newValue}`)
      })
      </script>

      说明:​

      • 第一个参数是一个函数,返回要监听的具体属性(user.age)。​
      • 只有该属性变化时,才会触发回调,其他属性变化不会影响。

      11.5 情况五:监视多个数据

      基础语法:

      watch([监听数据1,监听数据2....] ,([监听数据1的新值,监听数据2的新值],[监听数据1的旧值,监听数据2的旧值]) =>

      {

      函数体

      })

      watch 可以同时监听多个数据,当其中任何一个数据变化时,都会触发回调。​

      问题:如何同时监听 count 和 user.age 的变化?

      <template>
        <div>
          <p>计数器:{{ count }}</p>
          <p>年龄:{{ user.age }}</p>
          <button @click="count++">count+1</button>
          <button @click="user.age++">age+1</button>
        </div>
      </template>
      
      <script setup>
      import { ref, reactive, watch } from 'vue'
      
      const count = ref(0)
      const user = reactive({
        age: 20,
        name: '张三',
      })
      
      // 监视多个数据(数组形式)
      watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => {
        console.log(`count变化:${oldCount}→${newCount}`)
        console.log(`age变化:${oldAge}→${newAge}`)
      })
      </script>

      总结:​

      • watch 适用于需要知道数据变化前后值的场景,或需要执行异步操作的场景。​
      • 根据监听目标的类型(基本类型 / 对象类型、ref/reactive),需选择不同的监听方式,必要时开启深度监听。

      十二、watchEffect

      watchEffect 是另一种监听响应式数据变化的方式,它不需要明确指定监听的目标,会自动追踪回调函数中使用的响应式数据,当这些数据变化时,回调函数会重新执行。​

      问题:如何实现当 count 或 user.age 变化时,自动打印它们的当前值,而无需手动指定监听目标?

      <template>
        <div>
          <p>计数器:{{ count }}</p>
          <p>年龄:{{ user.age }}</p>
          <button @click="count++">count+1</button>
          <button @click="user.age++">age+1</button>
        </div>
      </template>
      
      <script setup>
      import { ref, reactive, watchEffect } from 'vue'
      
      const count = ref(0)
      const user = reactive({
        age: 20
      })
      
      // watchEffect自动追踪回调中使用的响应式数据
      watchEffect(() => {
        console.log(`当前count: ${count.value}, 当前age: ${user.age}`)
      })
      </script>

      与 watch 的区别:​

      特性​

      watch​

      watchEffect​

      监听目标​

      需要明确指定​

      自动追踪回调中使用的数据​

      新旧值​

      可以获取新旧值​

      只能获取当前值​

      执行时机​

      数据变化时执行​

      初始化时会执行一次(收集依赖),之后数据变化时执行​

      适用场景​

      需要知道数据变化前后值的场景​

      只需根据数据变化执行副作用,无需关心旧值的场景​

      十三、生命周期

      Vue 组件从创建到销毁的过程中,会经历一系列特定的阶段,这些阶段被称为生命周期。Vue 提供了生命周期钩子函数,允许我们在这些阶段执行自定义逻辑。​

      常见的生命周期钩子:​

      钩子函数​

      说明​

      onMounted​

      组件挂载到 DOM 后执行​

      onUpdated​

      组件更新后执行(DOM 重新渲染后)​

      onUnmounted​

      组件从 DOM 中卸载后执行​

      官网晚上的生命周期函数(地址)

      生命周期选项 | Vue.js

      问题:如何在组件挂载后请求数据,更新时打印日志,卸载前清理定时器?

      <template>
        <div>
          <p>{{ data }}</p>
          <button @click="count++">更新组件</button>
        </div>
      </template>
      
      <script setup>
      // onMounted	组件挂载到 DOM 后执行
      // onUpdated	组件更新后执行(DOM 重新渲染后)
      // onUnmounted	组件从 DOM 中卸载后执行
      import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
      
      const data = ref('加载中...')
      const count = ref(0)
      let timer = null
      
      // 组件挂载后:请求数据、开启定时器
      onMounted(() => {
        console.log('组件挂载完成')
        // 模拟数据请求
        setTimeout(() => {
          data.value = '请求到的数据'
        }, 1000)
        // 开启定时器
        timer = setInterval(() => {
          console.log('定时器运行中...')
        }, 1000)
      })
      
      // 组件更新后:打印日志
      onUpdated(() => {
        console.log('组件更新完成')
      })
      
      // 组件卸载前:清理定时器
      onUnmounted(() => {
        console.log('组件即将卸载')
        clearInterval(timer)
      })
      </script>

       没有挂载:

       挂载之后:

       没有更新和卸载(不会执行onUpdated和onUnmounted):

      说明:​

      • onMounted 常用于初始化操作(如数据请求、事件监听)。​
      • onUpdated 可用于在组件更新后执行依赖 DOM 的操作,但应避免在此修改数据(可能导致无限循环)。​
      • onUnmounted 用于清理资源(如定时器、事件监听),防止内存泄漏。

      十四、自定义 hooks

      在 Vue 开发中,我们经常会遇到一些重复的逻辑(比如表单处理、数据请求、定时器管理等)。如果每个组件都单独实现这些逻辑,不仅代码冗余,还会增加维护成本。这时候,自定义 hooks 就派上用场了。

      以下就通过结合一个防抖来示例:

      useDebounce.ts

      import {ref} from 'vue'
      // 导入Vue的reactive函数,用于创建响应式数据
      import { reactive } from "vue";
      // 导入axios库,用于发送HTTP请求
      import axios from "axios";
      
      // 创建一个响应式的字符串数组,用于存储狗狗图片的URL
      let dogList = reactive<string[]>([])
      
      async function getDog() {
          // 发送GET请求获取随机狗狗图片的URL
          let res = await axios.get("https://dog.ceo/api/breed/pembroke/images/random")
          // 将获取到的图片URL添加到dogList数组中
          dogList.push(res.data.message)
      }
      
      function useDebounce<T>(callback: () => void, delay: number) {
          // 使用 Vue 的 ref 函数创建一个响应式的计时器变量
          const timer = ref<number | null>(null)
      
          // 返回一个函数,当其被调用时,将执行防抖动逻辑
          const fd= () => {
              // 如果计时器存在,则清除之前的计时器,以确保在延迟时间内只执行一次回调
              if (timer.value) {
                  console.log("clear当前的计时器",timer.value)
                  // 根据id清除计时器
                  clearTimeout(timer.value)
              }
              // 设置一个新的计时器,如果在延迟时间后没有再次调用此函数,则执行回调
              timer.value = setTimeout(() => {
                  callback()  // 执行getDog()
              }, delay)
          }
          return fd
      }
      // 导出dogList和getDog函数,供其他模块使用
      export {
          dogList,
          getDog,
          useDebounce
      }

       useDebounce.vue

      <script setup lang="ts">
      // 导入自定义的防抖钩子函数,用于在指定时间内部分更新函数的执行
      import { useDebounce,dogList, getDog } from "@/hooks/useDebounce2"
      
      // 定义防抖动的延迟时间,单位为毫秒,此处设置为2秒
      const debounceDelay = 2000
      
      // 使用防抖钩子函数封装获取狗狗图片的函数
      // 这样可以避免在用户频繁点击按钮时发送过多的请求
      // 函数将在最后一次调用后的 debounceDelay 毫秒后执行
      const debouncedGetDog = useDebounce(() => {
        getDog()
      }, debounceDelay)
      </script>
      
      <template>
        <div class="hooksdemo">
          <h2>useDebounce 示例2</h2>
          <!-- 使用 v-for 指令循环渲染狗狗图片 -->
          <img v-for="(u, index) in dogList" :key="index" :src="u" alt="Dog Image">
          <!-- 当用户点击按钮时,将调用 debouncedGetDog 函数 -->
          <!-- 防抖技术确保在频繁点击时不会发送过多的请求 -->
          <button @click="debouncedGetDog">再来一只狗(防抖)</button>
        </div>
      </template>
      

      App.vue

      <script setup lang="ts">
        // import one from "./components/one.vue";
        // import two from "./components/two.vue";
        import useDebounce from "@/components/useDebounce.vue";
      </script>
      
      <template>
      <!--  <two></two>-->
      <!--  <hr>-->
      <!--  <br>-->
      <!--&lt;!&ndash;  <one></one>&ndash;&gt;-->
        <useDebounce></useDebounce>
      </template>


      网站公告

      今日签到

      点亮在社区的每一天
      去签到