vue3-基本语法
文本插值 {{ 变量/表达式 }}(支持算术运算、三元判断等简单逻辑) <script setup lang="ts"> const name = "枫枫" const age = 12 cons

vue3-基本语法

发布时间:2025-12-18 (2025-12-18)

文本插值

{{ 变量/表达式 }}(支持算术运算、三元判断等简单逻辑)

<script setup lang="ts">
const name = "枫枫"
const age = 12
const gender = true
const list = [1, 2,3]
const obj = {name: name}


</script>

<template>
  <div>
    <div>name: {{ name }}</div>
    <div>age: {{ age }}</div>
    <div>age: {{ age + 18 }}</div>
    <div>gender: {{ gender }}</div>
    <div>gender: {{ gender ? '男' : '女' }}</div>
    <div>list: {{ list }}</div>
    <div>obj: {{ obj }}</div>
    <div>obj.name: {{ obj.name }}</div>
  </div>
</template>

动态绑定

v-bind(简写:,绑定属性 / 样式 / 类名)

<script setup lang="ts">
const name = "枫枫"
const className = "active"
const classObj = {
  menu: true,
  active: true
}

const styleObj = {
  fontSize: "20px",
  color: "blue"
}
</script>

<template>
 <div>
    动态绑定
    <div v-bind:name="name">动态绑定name</div>
    <div v-bind:class="className">动态绑定class</div>
    <div :class="className">动态绑定class</div>
    <div :class="classObj">动态绑定class obj</div>
    <div :style="styleObj">动态绑定style</div>
  </div>
</template>


<style scoped>
.active {
  color: red;
}
</style>

条件渲染

v-if/v-else(DOM 增删)、v-show(CSS 显示隐藏)及二者区别

<script setup lang="ts">
const gender = false
const age = 12

</script>

<template>
  <div>
    条件渲染
    <div>
      v-if v-else
      <span v-if="gender">男</span>
      <span v-else>女</span>

      <br>
      v-if v-else-if v-else
      <span v-if="age >= 35">被优化</span>
      <span v-else-if="age >= 18">已成年</span>
      <span v-else>未成年</span>
    </div>

    <div>
      v-show
      <div>
        <span v-show="gender">男</span>
      </div>
    </div>

  </div>
</template>

<style scoped>
.active {
  color: red;
}
</style>

列表渲染

v-for(遍历数组 / 对象,key属性的必要性)

<script setup lang="ts">
const array = [
    "tom", "jerry", "张三", "李四"
]

const obj = {
  name: "张三",
  age: 21,
  addr: "湖南长沙"
}

</script>

<template>
  <div>
    列表渲染

    item in array
    <ul>
      <li v-for="item in array">{{ item }}</li>
    </ul>

    (item, index) in array
    <ul>
      <li v-for="(item, index) in array">{{ index }}.{{ item }}</li>
    </ul>

    item in obj
    <ul>
      <li v-for="item in obj">{{ item }}</li>
    </ul>

    (value, key) in obj
    <ul>
      <li v-for="(value, key) in obj">{{ key }}: {{ value }}</li>
    </ul>

    (value, key, index) in obj
    <ul>
      <li v-for="(value, key, index) in obj">{{ index }}.{{ key }}: {{ value }}</li>
    </ul>

  </div>
</template>

事件绑定

v-on(简写@,绑定点击 / 输入等事件)

<script setup lang="ts">

function clickHandler(e: Event){
  console.log("点击事件", e)
}

function clickHandler1(name: string, e: Event){
  console.log("点击事件1", name)
}

function alertHandler(msg: string){
  alert(msg)
}

function enterHandler(e: KeyboardEvent){
  const target = e.target as HTMLInputElement
  const value = target.value
  console.log("enter键按下了", value)
}

function inputHandler(e: InputEvent){
  console.log("键盘输入中", e.data)
}
function focusHandler(){
  console.log("触发焦点了")
}
function blurHandler(){
  console.log("失去焦点了")
}


</script>

<template>
<div>
  事件绑定
  <div>点击事件 <button v-on:click="clickHandler">点我</button></div>
  <div>点击事件 <button @click="clickHandler">点我</button></div>
  <div>点击事件 <button @click="clickHandler1('枫枫', $event)">点我</button></div>

  <div>双击事件 <button @dblclick="alertHandler('双击')">双击</button></div>
  <div>右键 <button @click.right="alertHandler('右键')">右键</button></div>
  <div>中键 <button @click.middle="alertHandler('中键')">中键</button></div>

  <div>enter键 <input @keydown.enter="enterHandler" placeholder="enter键"></div>
  <div>input输入 <input @input="inputHandler" placeholder="input输入"></div>
  <div>input触发焦点 <input @focus="focusHandler" placeholder="input触发焦点"></div>
  <div>input失去焦点 <input @blur="blurHandler" placeholder="input失去焦点"></div>

</div>
</template>

事件处理与修饰符

  1. 事件绑定:事件函数定义、参数传递(如@click="handleBtn(1)"
  2. 事件修饰符:.stop(阻止冒泡)、.prevent(阻止默认行为)、.once(仅触发一次)
  3. 按键修饰符:@keyup.enter(回车键触发)等表单场景常用修饰符

.prevent

比如a标签点击默认会跳转,可以使用.prevent阻止默认的跳转

<script setup lang="ts">
function clickHandler(e: PointerEvent) {
  console.log(e.target)
}

</script>

<template>
  <div>
    事件委托
    <a href="https://www.fengfengzhidao.com" @click.prevent="clickHandler">枫枫知道</a>
  </div>
</template>

.once

事件只触发一次

<script setup lang="ts">
function clickHandler(e: PointerEvent) {
  console.log(e.target)
}

</script>

<template>
  <div>
    事件委托
    <button @click.once="clickHandler">只有第一次点击才有效</button>
  </div>
</template>

事件冒泡

事件冒泡和事件委托在实际开发中遇到的挺多的

需要单独讲一下

所谓的事件冒泡很简单,当内部的button按钮点击之后,会将点击事件向上传递

classB的div先会收到点击事件,然后是classA的div收到点击事件

<script setup lang="ts">
function clickHandler(e: Event, name: string){
  console.log(e.target, name)
}

</script>

<template>
  <div>
    事件冒泡
    <div class="A" @click="clickHandler($event, 'A')">
      A
      <div class="B" @click="clickHandler($event, 'B')">
        B
        <button @click="clickHandler($event, 'Button')">button</button>
      </div>
    </div>

  </div>
</template>

<style scoped>
.A {
  background-color: #ee4a4a;
  padding: 20px;
}

.B {
  background-color: #10d291;
  padding: 20px;
}
</style>

可以使用事件修饰符 .stop 阻止事件冒泡

<template>
  <div>
    事件冒泡
    <div class="A" @click.stop="clickHandler($event, 'A')">
      A
      <div class="B" @click.stop="clickHandler($event, 'B')">
        B
        <button @click.stop="clickHandler($event, 'Button')">button</button>
      </div>
    </div>

  </div>
</template>

这样,点击button,就只有button的点击事件触发了

.self

作用是仅当事件触发源是当前元素自身时,才执行事件处理函数

<template>
  <div>
    事件冒泡
    <div class="A" @click="clickHandler($event, 'A')">
      A
      <div class="B" @click.self="clickHandler($event, 'B')">
        B
        <button @click="clickHandler($event, 'Button')">button</button>
      </div>
    </div>

  </div>
</template>

比如,给B绑定了.self,那么点击button的时候,由于事件冒泡,本来B是要收到事件的,但是因为.self的存在,把它给过滤了,然后A收到事件冒泡,没有.self,所以它收到了事件冒泡

点击B的时候,.self本身是不会阻止事件冒泡的,所以A也会收到事件

事件修饰符是可以连用的

<div class="A" @click="clickHandler($event, 'A')">
  A
  <div class="B" @click.stop.self="clickHandler($event, 'B')">
    B
    <button @click="clickHandler($event, 'Button')">button</button>
  </div>
</div>

但是这个顺序是有讲究的,以B为例

  1. @click.stop.self:点击 button → 事件冒泡到 B → .stop 先触发(阻断冒泡到 A)→ 再判断.self(触发源是 button≠B,所以 B 的事件不执行)→ 最终:A 无响应,B 无响应,只有 button 自己执行。
  2. @click.self.stop:点击 button → 事件冒泡到 B → 先判断.self(触发源是 button≠B,B 的事件直接不执行)→ .stop 因 “事件未执行” 也不会触发 → 事件继续冒泡到 A → 最终:button 执行,B 无响应,A 执行。

事件委托

现在有一个列表,列表下面有很多元素,现在需要给每个元素都绑定点击事件

如果这样写,就很浪费性能了

<template>
  <div>
    事件委托
    <div class="list">
      <div class="item" v-for="item in 20" @click="clickHandler($event, item)">{{ item }}</div>
    </div>
  </div>
</template>

可以使用事件委托,给父元素绑定事件,通过e.target判断是否是子元素点击

<script setup lang="ts">
function clickHandler(e: PointerEvent) {
  const target = e.target as HTMLDivElement
  if (target.classList.contains("item")){
    console.log(target.dataset["item"])
  }
}

</script>

<template>
  <div>
    事件委托
    <div class="list" @click="clickHandler($event)">
      <div class="item" v-for="item in 20" :data-item="item">{{ item }}</div>
    </div>
  </div>
</template>

<style scoped>
.list {
  background-color: #ee4a4a;
  padding: 20px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-row-gap: 20px;
  grid-column-gap: 20px;

  .item {
    background-color: #084bc7;
    width: 40px;
    height: 40px;
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 5px;
  }
}
</style>

双向数据绑定

在输入框这些需要与用户进行交互的地方,使用双向数据绑定可以非常方便的绑定和获取数据

<script setup lang="ts">
import {ref} from "vue";

const msg = ref("")

</script>

<template>
  <div>
    双向数据绑定

    <div>
      <input v-model="msg" placeholder="输入内容">
    </div>
    <div>
      你输入的内容 {{ msg }}
    </div>
  </div>
</template>

这里引入了 ref和v-model

  • ref(""):创建一个响应式数据容器(存储 msg 的值),保证数据变化时视图自动更新 ;
  • v-model="msg":把这个响应式数据和输入框做双向绑定,实现「用户输入 → msg 自动更新」「msg 手动修改 → 输入框内容自动更新」。

很多输入组件都可以使用v-model

<script setup lang="ts">
import {reactive} from "vue";

const data = reactive({
  text: "",
  age: 18,
  gender: true,
  like: [],
})

</script>

<template>
  <div>
    双向数据绑定

    <div>
      <input v-model="data.text" placeholder="输入内容">
      <br>
      <input v-model.number="data.age" type="number" placeholder="输入年龄">
      <br>
      选择性别
      <input type="radio" v-model="data.gender" :value="true"> 男
      <input type="radio" v-model="data.gender" :value="false"> 女
      <br>
      选择爱好
      <input type="checkbox" v-model="data.like" value="羽毛球"> 羽毛球
      <input type="checkbox" v-model="data.like" value="篮球"> 篮球
      <input type="checkbox" v-model="data.like" value="乒乓球"> 乒乓球
      <input type="checkbox" v-model="data.like" value="足球"> 足球
    </div>

    <div>
      你输入的内容
      <div>text: {{ data.text }}</div>
      <div>age: {{ data.age }}</div>
      <div>gender: {{ data.gender }}</div>
      <div>like: {{ data.like }}</div>
    </div>
  </div>
</template>

这里我又引入了reactive,一般用reactive给对象设置响应式

双向数据绑定原理

v-model 本质是语法糖,如下

<script setup lang="ts">
import {ref} from "vue";

const msg = ref("");

// 手动监听输入事件,更新 msg
const handleInput = (e: Event) => {
  // TS 类型断言:确保 e.target 是 HTMLInputElement
  const target = e.target as HTMLInputElement;
  msg.value = target.value;
};
</script>

<template>
  <div>
    手动实现双向绑定

    <div>
      <!-- 等价于 v-model:value 绑定 + input 事件监听 -->
      <input :value="msg" @input="handleInput" placeholder="输入内容">
    </div>
    <div>
      你输入的内容 {{ msg }}
    </div>
  </div>
</template>

计算属性

模板语法痛点:模板中允许写简单表达式(如 {{ a + b }}),但复杂逻辑会导致模板臃肿、可读性差。

<template>
  <div>已完成任务数:{{ list.filter(item => item.done).length }}</div>
</template>

<script setup>
import { ref } from 'vue'
const list = ref([{ done: true }, { done: false }, { done: true }])
const firstName = ref('张')
const lastName = ref('三')
const price = ref(100)
const count = ref(2)
const discount = ref(0.1)
</script>

计算属性 —— 专门封装「基于响应式数据派生的逻辑」,让模板回归 “展示” 本质。

<template>
  <div>已完成任务数:{{ doneCount }}</div>
</template>

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

// 响应式数据
const list = ref([{ done: true }, { done: false }, { done: true }])

// 计算属性
const doneCount = computed(() => {
  return list.value.filter(item => item.done).length
})
</script>

计算属性核心特性:缓存—— 只有依赖的响应式数据(如 list.value)变化时,计算属性才会重新计算,否则复用上次结果(对比 methods 无缓存)。

<script setup>
import {ref} from "vue";
import {computed} from "vue";

const list = ref([{done: true}, {done: false}, {done: true}])
const doneCount = computed(()=>{
  console.log("doneCount")
  return list.value.filter((item)=>item.done).length
})

function doneCount2() {
  console.log("doneCount2")
  return list.value.filter((item)=>item.done).length
}

</script>

<template>
  <div>
    任务完成数量: {{ list.filter((item)=>item.done).length }}
    <br>
    任务完成数量(计算属性) : {{ doneCount }} - {{ doneCount }}
    <br>
    任务完成数量(函数) : {{ doneCount2() }} - {{ doneCount2() }}
  </div>
</template>

带参数的计算属性

通过返回函数的方式就好了,但是这种方式会失去计算属性缓存的功能

<script setup>
import { ref, computed } from 'vue'
const list = ref([1, 2, 3, 4])

// 看似“带参数”的计算属性:实际是计算属性返回一个函数
const getDouble = computed(() => {
  return (num) => { // 内层函数接收参数
    console.log('执行计算逻辑')
    return num * 2
  }
})
</script>

<template>
  <!-- 每次调用都会执行内层函数,失去缓存 -->
  <div>{{ getDouble(2) }}</div>
  <div>{{ getDouble(2) }}</div>
</template>

可写的计算属性

默认的计算属性是只读的(仅包含 get 方法),而可写计算属性通过 set 方法定义 “修改逻辑”:当你直接给计算属性赋值时,Vue 会调用 set 方法,并将赋值的内容作为参数传入,你可以在 set 中处理数据更新,最终同步到响应式数据源。

<template>
  <div>
    <p>全名:{{ fullName }}</p>
    <input v-model="fullName" placeholder="输入全名" />
  </div>
</template>

<script setup>
import { ref, computed } from "vue";

// 响应式数据源
const firstName = ref("张");
const lastName = ref("三");

// 可写的计算属性
const fullName = computed({
  // 读取逻辑
  get() {
    return firstName.value + lastName.value;
  },
  // 修改逻辑
  set(newValue) {
    const [first, last] = newValue.split(" ");
    firstName.value = first || "";
    lastName.value = last || "";
  },
});
</script>

监听器

  • 基本语法:watch(监听目标, (新值, 旧值) => { 执行逻辑 }, 配置项)
  • 清理函数:返回一个函数,会在「下一次回调执行前 / 监听器停止时」执行,用于清理定时器、取消请求等(解决异步竞态问题);
  • 触发时机:只有监听目标变化时才执行(首次渲染不执行,可通过配置项修改)。

监听单个ref数据

<script setup>
import { ref, watch } from 'vue'
const keyword = ref('')

// 监听 keyword 变化:参数1=监听目标,参数2=回调函数(新值、旧值)
watch(keyword, (newVal, oldVal) => {
  console.log('关键词从', oldVal, '变成', newVal)
})
</script>

监听多个数据

<script setup>
import { ref, watch } from 'vue'
const name = ref('张三')
const age = ref(18)

// 监听多个目标:数组形式
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
  console.log('姓名变化:', oldName → newName)
  console.log('年龄变化:', oldAge → newAge)
  // 比如:姓名/年龄变化时,同步更新用户信息
})
</script>

监听reactive对象

<script setup>
import { reactive, watch } from 'vue'
const user = reactive({ name: '张三', info: { age: 18 } })

// 监听 reactive 对象:默认深度监听(无需配置 deep: true)
watch(user, (newVal, oldVal) => {
  console.log('user 任意属性变化', newVal)
})

// 监听 reactive 对象的单个属性(需用函数返回)
watch(() => user.info.age, (newAge) => {
  console.log('年龄变化:', newAge)
})
</script>

watch核心配置项

配置项 作用 适用场景
immediate 首次渲染立即执行回调 初始化时就执行监听逻辑(如默认加载数据)
deep 深度监听对象 / 数组的嵌套属性变化 监听 ref ({})、ref ([]) 时
flush 回调执行时机:pre(默认)/post/ 同步 post = 需要操作更新后的 DOM

watchEffect隐式监听

watchEffect 是「隐式监听」—— 无需指定监听目标,自动收集回调中用到的响应式数据作为依赖,代码更简洁,但可读性稍弱。

const we = ref("")
watchEffect(()=>{
  console.log("we:", we.value)
})

watch

可以暂停、恢复监听

stop是永久停止监听

const handler = watchEffect(()=>{
  console.log("we:", we.value)
})

<button @click="handler.pause">暂停监听</button>
<button @click="handler.resume">恢复监听</button>