VUE笔记

前言:前面期末周突然加了一个项目作业,感觉不是开发能力而是vibecoding能力,想写一个工具与vue有关的,但之前没有系统学习过,在微信公众号里面看了几篇好文章,然后真自己实际上手起来的话还是挺困难的,因此本篇作为一个按照官方给的一个快速上手教程来,默认是.vue结尾的设置,在官方基础上进行的一些个人的尝试和思考

开始

在教程最开始的时候给出了一段代码:

这里的h1好理解,就是html里面的一级标题,然后有一个template

在 Vue.js 中,<template> 是组件的模板容器,用于包裹该组件所有的 HTML 结构:

它不是原生 HTML 标签:虽然原生 HTML5 也有一个 <template> 标签(用于存放客户端不渲染的惰性内容),但在 Vue 的单文件组件(.vue)中,它是 Vue 编译器专属的语法标记

并且不会被渲染到 DOM 中:Vue 编译后,<template> 标签本身会消失,只有里面的内容(如 <h1>Hello World!</h1>)才会被真正渲染到页面上

那么在此基础上可以增加多级标题或具体内容,可以自行尝试:


声明式渲染

reactiveref

教程中直接给出了相应的解释:

Vue 的核心功能是声明式渲染:通过扩展于标准 HTML 的模板语法,我们可以根据 JavaScript 的状态来描述 HTML 应该是什么样子的。当状态改变时,HTML 会自动更新

能在改变时触发更新的状态被称作是响应式的,这里可以看官方教程或者我下面的代码:

js 复制代码
// 普通对象:改了数据,页面不动
let user = { name: 'Shorekiten' }
user.name = 'New Name' // 页面无变化,需操作DOM

// 响应式对象:改了数据,页面自动刷新
import { reactive } from 'vue'
const user = reactive({ name: 'Shorekiten' })
user.name = 'New Name' // 页面上显示的名字自动变成 "New Name"

代码的细节放到后面再做解释,先继续跟着教程往下走,可以看到一句话:由 reactive() 创建的对象都是 JavaScript Proxy

Proxy 是JavaScript ES6 的原生特性,相当于给对象加了一层"拦截器/代理"。当访问或修改对象的属性时,Proxy 会先拦截这个操作,然后执行额外的逻辑(比如通知 Vue 更新页面),最后再把操作传递给原始对象。

然后reactive调用的是对象类型 (Object、Array、Map、Set),刚开始看到这个很懵,那就先来了解一下基本类型,即单个的、不可再分的值,直接存在内存栈里

  • string(字符串):'hello'
  • number(数字):42
  • boolean(布尔值):true / false
  • null / undefined
  • symbol / bigint

这些基本类型就是通过ref来进行调用,ref 会返回一个包裹对象,并在 .value 属性下暴露内部值。

直接这么说有点抽象,那就分开来,首先得知道什么是包裹对象

JavaScript 的 基本类型是按值传递的,无法被 Proxy 拦截。

js 复制代码
// 基本类型无法被监听
let count = 0
count = 1 // Vue 根本感知不到这个赋值操作

// 必须包一层对象,才能用 .value 的 setter 来触发更新
const count = ref(0) 
// 内部等价于: { _value: 0, get value(){...}, set value(v){...} }

ref() 就是把基本类型装进去,让它变成可以被 Vue 监控的对象。

.value就是访问的入口,然后在模板中会自动解包所以无需再加value

下面的是对象类型【引用】:

类型 说明 示例
Object 普通对象/字面量对象 { name: 'Shorekiten', age: 0 }
Array 数组(本质上是特殊的对象) ['apple', 'banana']
Map 键值对集合(key 可以是任意类型) new Map([['name', 'Shorekiten']])
Set 不重复值的集合 new Set([1, 2, 3])

了解了reactive ref之后就是动态渲染文本,即双花括号语法动态渲染文本

结合

将教程中的语言结合起来构造出的结果就是这样:

js 复制代码
<script setup>
import { reactive, ref } from 'vue'

const counter = reactive({ count: 0 })
const message = ref('Hello World!')
</script>

<template>
  <h1>{{ message }}</h1>
  <p>Count is: {{ counter.count }}</p>
</template>

首先这里的const即常量,在 Vue 3 <script setup> 中,我们几乎总是用 const 来声明响应式状态,因为:

reactive() 返回的对象引用不应该被替换(替换会丢失响应性)

ref() 返回的包裹对象引用也不应该被替换(修改值应该通过 .value)

举例如下:

js 复制代码
// 用 const,之后通过 .value 或 .属性 修改内部值
const message = ref('Hello')
message.value = 'World' // 改的是内部值,不是 message 这个变量本身

// 用 let 然后整体替换,会导致模板失去响应性
let message = ref('Hello')
message = ref('World') // 模板绑定的还是旧的 ref

然后reactive里的花括号即键值对:

js 复制代码
{ count: 0 }
// ↑ key(键): value(值)
//等价于
const obj = new Object()
obj.count = 0

reactive({ count: 0 }) 的意思就是:把一个包含 count 属性的普通对象,交给 Vue 变成响应式的。我们可以放任意多个键值对进去:

js 复制代码
const state = reactive({
  name: 'Shorekiten',   // string
  age: 0,              // number
  hobbies: ['coding'],  // array
  isOnline: true        // boolean
})

那么最后的counter.count就是访问响应式对象的属性

知道了这些我们就能创造自己的页面内容了:

js 复制代码
<script setup>
import { reactive, ref } from 'vue'

const state = reactive({
  name: 'Shorekiten',   // string
  age: 0,              // number
  hobbies: ['coding'],  // array
  isOnline: true        // boolean
})
const message = ref('ShoreKiten の cottage')
const title = ref('123')
</script>

<template>
  <h1>{{ message }}</h1>
  <p>State is: {{ state.isOnline}}</p>
  <h2>{{ title }}</h2>
  <p>This is second paragraph</p>
</template>

然后在写的过程中其实遇到了一点问题,首先我刚开始是这么写的:

js 复制代码
JS 认为字符串在 ShoreKiten 后就结束了
const message = ref('ShoreKiten's cottage')

解决方法有三种:

js 复制代码
//用反引号,里面可以随意包含单引号和双引号,无需转义:
const message = ref(`ShoreKiten's cottage`)

//使用双引号包裹
const message = ref("ShoreKiten's cottage")

//使用转义字符 \
const message = ref('ShoreKiten\'s cottage')

还有一个是之前提到的.value的问题,在模板中是不需要加的,但是在模板外可以加上这个对原本的内容进行相应的修改,就比如:

js 复制代码
const message = ref(`ShoreKiten's cottage`)
message.value = `Tom's house`

那么最终结果就是这样显示:


Attribute 绑定

这个在快速入门里面讲的不是很详细,然后参考了网上别的师傅写的文章,首先得是理解这个标题是什么意思:

属性绑定**(Attribute Binding)**是 Vue 提供的一种将组件数据与 HTML 元素属性建立关联的方式。通过属性绑定,我们可以让元素的属性值随着数据的变化而自动更新,实现数据驱动视图的核心思想,基础语法如下:

js 复制代码
<元素 v-bind:属性名="数据"></元素>
//一般进行简写,即快速入门中描述的
<div :id="dynamicId"></div>

常见使用场景

动态图片/链接

js 复制代码
<script setup>
import { ref } from 'vue'
const avatarUrl = ref('/images/shorekiten.png')
const blogLink = ref('https://www.cnblogs.com/ShoreKiten')
</script>

<template>
  <img :src="avatarUrl" alt="头像" />
  <a :href="blogLink">访问博客</a>
</template>

动态控制 disabled / readonly 等布尔属性

js 复制代码
<script setup>
import { ref } from 'vue'
const isSubmitting = ref(false)
</script>

<template>
  <!-- isSubmitting 为 true 时按钮禁用,为 false 时恢复正常 -->
  <button :disabled="isSubmitting">提交</button>
</template>

class 和 style

这里其实更常用的是数组,对象等等,但是因为是初学所以就先不拓展了,样例给出的结果:

js 复制代码
<script setup>
import { ref } from 'vue'

// 1. 声明一个响应式变量,值为字符串 'title'
const titleClass = ref('title')
</script>

<template>
  <!-- 2. 用 :class 把 h1 的 class 属性与 titleClass 变量绑定 -->
  <h1 :class="titleClass">Make me red</h1>
</template>

Attribute 绑定 vs 文本插值 {{ }} 的区别

特性 {{ }} 文本插值 :attr 属性绑定
用在哪里 标签内部的文本内容 标签上的 HTML 属性
示例 <p>{{ message }}</p> <img :src="url" />
能否混用 ❌ 不能写在属性里 ❌ 不能写在文本位置

事件监听

就类似于HTML DOM事件,在快速入门中给出的案例就是点击按钮计数增加:

js 复制代码
<button v-on:click="increment">{{ count }}</button>
//简写
<button @click="increment">{{ count }}</button>

快速入门中在文本中已经给出了相应答案,我们可以在此基础上添加相应变种:

js 复制代码
<script setup>
import { ref } from 'vue'

const count1 = ref(0)
const count2 = ref(0)
function increase(){
  count1.value++
}

function decrease(){
  count2.value--
}
</script>

<template>
  <!-- 使此按钮生效 -->
  <button @click="increase">Count1 is: {{ count1 }}</button>
  <h2>
  <button @click="decrease">Count2 is:{{ count2 }}</button>
  </h2>
</template>

或者更换任意鼠标事件:

Vue 写法 对应原生事件 触发时机
@mouseenter onmouseenter 鼠标进入元素边界(不冒泡)
@mouseleave onmouseleave 鼠标离开元素边界(不冒泡)
@mouseover onmouseover 鼠标移入元素(会冒泡)
@mouseout onmouseout 鼠标移出元素(会冒泡)
@mousemove onmousemove 鼠标在元素内移动(高频触发⚠️)
@mousedown onmousedown 鼠标按键按下
@mouseup onmouseup 鼠标按键抬起
@dblclick ondblclick 双击
@contextmenu oncontextmenu 右键菜单
js 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increase() {
  count.value++
}

function decrease() {
  count.value--
}
</script>

<template>
  <!-- 鼠标移入 +1,移出 -1 -->
  <button @mouseenter="increase" @mouseleave="decrease">
    Count is: {{ count }}
  </button>
</template>

表单绑定

表单绑定指的是:将表单元素 (<input>、<select>、<textarea> 等)的值与 Vue 的响应式数据进行双向同步。

简单来说就是:

数据 → 视图:JS 变量变了,输入框里的内容自动更新

视图 → 数据:用户在输入框里打字/选择,JS 变量自动更新

js 复制代码
<script setup>
import { ref } from 'vue'
const message = ref('hello')
</script>

<template>
  <!-- 推荐写法 -->
  <input v-model="message" />

  <!-- 等价于上面这行(手动实现双向绑定) -->
  <input :value="message" @input="message = $event.target.value" />

  <p>你输入了:{{ message }}</p>
</template>

条件渲染

条件渲染指的是:根据 JavaScript 表达式的真假值,动态地决定某个 DOM 元素是否显示、或者显示哪一个元素,简单来说就是:让模板具备"判断能力",而不是永远写死所有 UI

v-if:真正的条件渲染

条件为 false 时,元素直接从 DOM 中移除/销毁;为 true 时才创建并插入 DOM

适用场景:切换频率低、条件很少改变的场景(如权限控制、弹窗)

就拿文档里面的来说要调用toggle来改变awesome的真值,在 awesome 的值为真时渲染。若 awesome 更改为假,它将被从 DOM 中移除,文档中因此给出了vi-ifvi-else两个条件判断在awesome的值为真或假时的两种显示,我们需要做的就是补全toggle函数,让调用函数的按钮在被点击时能够改变awesome的真假,这里就可以直接运用:

js 复制代码
function toggle() {
  awesome.value = !awesome.value
}

或者手动进行更改

js 复制代码
<script setup>
import { ref } from 'vue'
const isLoggedIn = ref(false)
</script>

<template>
  <!-- isLoggedIn 为 false 时,这个 button 完全不存在于 DOM 中 -->
  <button v-if="isLoggedIn">退出登录</button>
  <button v-else>请登录</button>
</template>

然后这里也有多分支判断,就比如:

js 复制代码
<script setup>
import { ref } from 'vue'

const awesome = ref(100)

function toggle() {
  awesome.value -=10
}
</script>

<template>
  <input v-model= "awesome" />
  <button @click="toggle">Toggle</button>
  <h1 v-if="awesome>=80">Vue is awesome!</h1>
  <h1 v-else-if="awesome>=60">Vue is normal </h1>
  <h1 v-else>Opus! </h1>
</template>

这里就是三个条件判断,初始的值为100,每次点击按钮之后-10,最终呈现的是三种不同的效果,然后再来一个表单绑定实时显示awesome的大小,这样看的就更加清楚了。


列表渲染

列表渲染指的是:使用 v-for 指令,根据数组或对象的数据,自动生成一组结构相同的 DOM 元素。

简单来说就是:我们只需要写一次模板代码,Vue 会根据数据的长度自动"复制"出 N 个元素。它是条件渲染(控制单个元素的有无)的自然延伸------从"控制一个"变成"批量生成多个"。

文档中直接给出的例子有点复杂,先从一个简单的看起:

js 复制代码
<script setup>
import { ref } from 'vue'
const fruits = ref(['苹果', '香蕉', '橘子'])
</script>

<template>
  <!-- item 是每一项的值,index 是索引 -->
  <ul>
    <li v-for="(item, index) in fruits" :key="index">
      {{ index + 1 }}. {{ item }}
    </li>
  </ul>
</template>

这里并不是直接的todo列表,而是通过数组下标来进行创建相应的元素,当我们插入或者删除其中某个元素时,后续所有key的index都会发生改变,那么就要回到样例中的todo列表了,但这里还是拿水果来举例,改成下面这样子:

js 复制代码
<script setup>
import { ref } from 'vue'

let id = 0

const newFruit = ref('')

const fruits = ref([
  { id: id++, name: '苹果' },
  { id: id++, name: '香蕉' },
  { id: id++, name: '橘子' }
])

function addFruit() {
  fruits.value.push({ id: id++, name: newFruit.value })
  newFruit.value = ''
}

function removeFruit(fruit) {
  fruits.value = fruits.value.filter((f) => f !== fruit)
}
</script>

<template>
  <form @submit.prevent="addFruit">
    <input v-model="newFruit" required placeholder="添加水果">
    <button>添加</button>
  </form>

  <ul>
    <li v-for="fruit in fruits" :key="fruit.id">
      {{ fruit.name }}
      <button @click="removeFruit(fruit)">X</button>
    </li>
  </ul>
</template>

这里关键是用的两个方法:

js 复制代码
// 【添加方法】由表单 @submit.prevent 触发
function addTodo() {
  // 1. 向响应式数组末尾追加新对象
  //    Proxy 拦截 push → 自动通知依赖 fruits 的 v-for 重新渲染
  //    id++ 先取当前值赋给 id,再自增,确保不重复
  fruits.value.push({ id: id++, text: newFruit.value })

  // 2. 清空输入框(v-model 同步更新 DOM 中 input 的值)
  newFruit.value = ''
}
js 复制代码
fruits.value = fruits.value.filter((f) => f !== fruit)
//                                  ↑           ↑
//                            遍历的每一项    要删除的目标项

filter 会遍历数组中的每一项,对每一项执行回调函数 (f) => f !== fruit:

当 f 是要删除的那个水果时 →f !== fruit 为 false被剔除

当 f 是其他水果时 → f !== fruit 为 true 被保留

最终返回一个全新的数组,里面不包含被点击删除的那一项。

然后!== 是严格不等于,比较的是对象引用(内存地址),而不是值的内容

最后是将新得到的数组重新进行赋值,原因是filter 不会修改原数组,它返回的是一个新数组。必须将新数组赋值回 fruits.value,才能触发 Vue 的响应式更新


计算属性


生命周期和模板引用

生命周期

生命周期就是 Vue 组件从 创建 → 挂载 → 更新 → 销毁 的完整过程。Vue 在特定阶段会自动调用对应的钩子函数,让我们能在"正确的时机"执行代码

核心钩子一览

钩子 触发时机 典型用途
onBeforeMount DOM 还没生成 极少使用
onMounted DOM 已渲染到页面 获取 DOM 尺寸、初始化第三方库、发请求、自动聚焦输入框
onBeforeUpdate 数据变了,DOM 还没更新 记录更新前的滚动位置等
onUpdated DOM 已同步最新数据 操作更新后的 DOM(慎用,易死循环)
onBeforeUnmount 组件即将被移除 清除定时器、取消订阅、断开 WebSocket
onUnmounted 组件已从 DOM 移除 最终清理

模板引用

板引用是通过 ref() + 同名属性,在 <script setup> 中直接获取某个 DOM 元素或子组件实例的桥梁。它是 Vue 对原生 document.getElementById() 的响应式替代方案

二者结合

拿官方文档中的例子来看:

js 复制代码
<script setup>
import { ref, onMounted } from 'vue'

const pElementRef = ref(null)

onMounted(() => {
  pElementRef.value.textContent = 'mounted!'
})
</script>

<template>
  <p ref="pElementRef">Hello</p>
</template>

首先是声明模板引用

js 复制代码
const pElementRef = ref(null)

ref(null):创建一个响应式变量,初始值为 null。

null是因为此时组件还没有渲染,DOM 还不存在,所以暂时没有东西可以绑定。

命名约定:变量名 pElementRef 必须与下面模板中ref="pElementRef" 完全一致,Vue 靠这个名字建立关联

然后在模板中进行相应绑定

js 复制代码
<p ref="pElementRef">Hello</p>

Vue 渲染到这个 <p> 标签时,发现 ref="pElementRef"

Vue 自动将真实的 <p> DOM 节点赋值给 pElementRef.value

这个赋值动作发生在 DOM 插入页面之后、onMounted 回调执行之前

最后在生命周期中安全访问

js 复制代码
onMounted(() => {
  pElementRef.value.textContent = 'mounted!'
})

onMounted:确保回调在组件挂载完成后才执行。此时 pElementRef.value 已经不再是 null,而是真实的 <p> 元素。

.textContent = 'mounted!':直接调用原生 DOM API,将段落文本从 "Hello" 修改为 "mounted!"

结果:用户在页面上看到的是 mounted! 而不是 Hello

js 复制代码
① <script setup> 顶层代码执行
   → pElementRef = ref(null)     // 值是 null

② 模板渲染,DOM 插入页面
   → pElementRef.value = <p>元素  // Vue 自动赋值

③ onMounted 回调执行
   → pElementRef.value.textContent = 'mounted!'  // 安全访问 

侦听器

这里描述的比较详细,就不再多说了:


组件

嵌套组件概念的引入:

父组件可以在模板中渲染另一个组件作为子组件。要使用子组件,我们需要先导入它,然后就能在模板中使用组件:


Props

也类似于cpp中的继承,描述得也比较详细,注意原文档中给出的是动态传递,如果不加冒号的话便是静态,直接输出后面的字符串:


目前看的也就这些,作为一个初步的了解