Vue学习笔记之基础篇

devtools/2025/2/19 3:27:22/

本文为Vue学习笔记,内容主要来源于Vue官方教程。过程中将某些API与React做了对比,方便更好地理解以及加深记忆。

创建一个Vue应用

应用实例

Vue中有应用实例的概念,并且在应用实例上开放了很多接口,在应用配置部分会提到。React中并没有应用实例的概念。

import { createApp } from 'vue'const app = createApp({/* 根组件选项 */
})

根组件

createApp中传入根组件就可以生成应用实例。

const app = createApp(App)

挂载应用

调用应用实例的mount方法挂载应用。在React中,使用createRoot(document.getElementById('app')).render(<App />)方式来渲染应用。视角会有点差别,一个是挂载,一个是渲染。

app.mount('#app')

应用配置

应用实例上开放了config配置接口,可以处理错误;

app.config.errorHandler = (err) => {/* 处理错误 */
}

应用实例还开放了其他方法接口,如下面的注册全局组件。

app.component('TodoDeleteButton', TodoDeleteButton)

查看了完整的API列表,该实例上共有11个方法可供使用。

多个应用实例

Vue允许用户在一个页面中创建多个应用实例,而且每个应用都拥有自己的用于配置和全局资源的作用域。教程中提到主要用于服务端渲染。

const app1 = createApp({/* ... */
})
app1.mount('#container-1')const app2 = createApp({/* ... */
})
app2.mount('#container-2')

模板语法

文本插值

使用双大括号绑定变量。React中使用大括号。

<span>Message: {{ msg }}</span>

原始 HTML

想要插入原始html,这里用到了Vue的指令即v-xxx。在React中则是使用<div dangerouslySetInnerHTML={{ __html:'<p>some raw html</p>' }} />的方式。

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

Attribute 绑定

<div v-bind:id="dynamicId"></div>
// 简写
<div :id="dynamicId"></div>
// 同名简写
<div :id></div>

动态绑定多个值

Vue的属性绑定相较于React稍微有些特别,React中文本插值和属性绑定都是用大括号,比较统一。Vue中在双引号内写表达式比较特殊。

const objectOfAttrs = {id: 'container',class: 'wrapper',style: 'background-color:green'
}
<div v-bind="objectOfAttrs"></div>

使用 JavaScript 表达式

这里仅支持单一表达式,一个简单的判断方法是是否可以合法地写在 return 后面。这个和React是一致的。

{{ number + 1 }}{{ ok ? 'YES' : 'NO' }}{{ message.split('').reverse().join('') }}<div :id="`list-${id}`"></div>

调用函数

<time :title="toTitleDate(date)" :datetime="date">{{ formatDate(date) }}
</time>

受限的全局访问​

模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math 和 Date。没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。

指令 Directives

Vue中的指令可以用来处理属性绑定、事件绑定、条件语句等等。在React中使用{ seen && <div>see</div>}

<p v-if="seen">Now you see me</p>

参数 Arguments

<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a><a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>

动态参数

Vue中提供了动态参数的API。React中没有,只能通过解构对象的方式处理,即{...{id: 'url'}}

<!--
注意,参数表达式有一些约束,只允许字符串和null
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a><a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>

另外要注意如果写在html文件里的模板,动态参数名需要完全小写,因为浏览器会自动转换成小写。

修饰符 Modifiers

以下表示调用event.preventDefault()方法。在React中需要在事件回调中显式调用。

<form @submit.prevent="onSubmit">...</form>

响应式基础

声明响应式状态

代码中使用ref时,注意使用.value获取和修改值。设计.value是为了方便通过getter和setter函数来获取和相应值的变化,因为原生JS是很难监听变量的变化的。

import { ref } from 'vue'const count = ref(0)console.log(count) // { value: 0 }
console.log(count.value) // 0count.value++
console.log(count.value) // 1

在模板中使用时,Vue会自动解包,不需要使用.value

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

setup()选项式API

在setup函数中定义状态和函数,返回给模板使用。

import { ref } from 'vue'export default {setup() {const count = ref(0)function increment() {// 在 JavaScript 中需要 .valuecount.value++}// 不要忘记同时暴露 increment 函数return {count,increment}}
}

<script setup>组合式API

单文件组件中,使用setup标识可以避免上述写法,将所有状态和函数暴露给模板。

<script setup>
import { ref } from 'vue'const count = ref(0)function increment() {count.value++
}
</script><template><button @click="increment">{{ count }}</button>
</template>

深层响应性

非原始值将通过 reactive() 转换为响应式代理。也可以通过 shallow ref 来放弃深层响应性。

import { ref } from 'vue'const obj = ref({nested: { count: 0 },arr: ['foo', 'bar']
})function mutateDeeply() {// 以下都会按照期望工作obj.value.nested.count++obj.value.arr.push('baz')
}

DOM 更新时机

当修改了响应式状态时,DOM 会被自动更新。DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。这个就如同React中的状态更新批处理。

要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API。这个在React中没有对应的API,一般就是使用setTimeout将要更新的状态放到下一个宏任务中更新。

import { nextTick } from 'vue'async function increment() {count.value++await nextTick()// 现在 DOM 已经更新了
}

侦听器

Vue中使用watch监听ref变化执行副作用,类似于React中的useEffect。不同的是:

  1. watch默认不会立即执行副作用,而useEffect会;
  2. watch的执行时机默认是在DOM更新之前(即同步执行),而useEffect始终在DOM更新之后(即异步执行);
  3. 监听的值或对象会作为参数传入到副作用函数中,且包含新旧两个参数值。
<script setup>
import { ref, watch } from 'vue'const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {if (newQuestion.includes('?')) {loading.value = trueanswer.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} finally {loading.value = false}}
})
</script><template><p>Ask a yes/no question:<input v-model="question" :disabled="loading" /></p><p>{{ answer }}</p>
</template>

侦听数据源类型

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。

watch函数可以监听getter函数,在React中,相当于useMemo与useEffect结合使用。

const x = ref(0)
const y = ref(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}`)
})

注意,你不能直接侦听响应式对象的属性值,例如:

const obj = reactive({ count: 0 })// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {console.log(`Count is: ${count}`)
})

这里需要用一个返回该属性的 getter 函数:

// 提供一个 getter 函数
watch(() => obj.count,(count) => {console.log(`Count is: ${count}`)}
)

深层侦听器

方式一:使用reactive
const obj = reactive({ count: 0 })watch(obj, (newValue, oldValue) => {// 在嵌套的属性变更时触发// 注意:`newValue` 此处和 `oldValue` 是相等的// 因为它们是同一个对象!
})obj.count++
方式二:使用ref+deep

watch一个ref只监听ref本身的变化,不会监听其内部属性的变化。使用deep属性可以强制开启深层响应性。

const obj = ref({count: 1})// 单个 ref
watch(obj, (newObj) => {console.log(`count is ${newObj.count}`)
},{deep: true})

在 Vue 3.5+ 中,deep 选项还可以是一个数字,表示最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数。

即时回调的侦听器

immediate参数设置为true后,行为等同于React中的useEffect。

watch(source,(newValue, oldValue) => {// 立即执行,且当 `source` 改变时再次执行},{ immediate: true }
)

一次性侦听器

设置once参数为true,可以控制副作用只触发一次,具体场景后面再看。

仅支持 3.4 及以上版本

watch(source,(newValue, oldValue) => {// 当 `source` 变化时,仅触发一次},{ once: true }
)

watchEffect

watchEffect是watch(immediate:true)的简化写法,其会自动跟踪副作用函数中的响应式依赖,不需要再声明依赖。相比较深层侦听器,watchEffect的方式更加简便且高效。

watchEffect(async () => {const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)data.value = await response.json()
})

副作用清理

onWatcherCleanup只在3.5+中支持

import { watch, onWatcherCleanup } from 'vue'watch(id, (newId) => {const controller = new AbortController()fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {// 回调逻辑})onWatcherCleanup(() => {// 终止过期请求controller.abort()})
})

onCleanup可以在3.5以下版本中作为替代

watch(id, (newId, oldId, onCleanup) => {// ...onCleanup(() => {// 清理逻辑})
})watchEffect((onCleanup) => {// ...onCleanup(() => {// 清理逻辑})
})

回调的触发时机

默认情况下,侦听器回调会在父组件更新 (如有) 之后、所属组件的 DOM 更新之前被调用。这意味着如果你尝试在侦听器回调中访问所属组件的 DOM,那么 DOM 将处于更新前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM,你需要指明 flush: 'post'选项:

watch(source, callback, {flush: 'post'
})watchEffect(callback, {flush: 'post'
})

后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect()

import { watchPostEffect } from 'vue'watchPostEffect(() => {/* 在 Vue 更新后执行 */
})

同步侦听器

你还可以创建一个同步触发的侦听器,它会在 Vue 进行任何更新之前触发:

watch(source, callback, {flush: 'sync'
})watchEffect(callback, {flush: 'sync'
})

同步触发的 watchEffect() 有个更方便的别名 watchSyncEffect()

import { watchSyncEffect } from 'vue'watchSyncEffect(() => {/* 在响应式数据变化时同步执行 */
})

同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。

停止侦听器

同步创建的侦听器会在组件被卸载时自动停止,异步创建的需要手动回收。

<script setup>
import { watchEffect } from 'vue'// 它会自动停止
watchEffect(() => {})// ...这个则不会!
setTimeout(() => {watchEffect(() => {})
}, 100)const unwatch = watchEffect(() => {})// ...当该侦听器不再需要时
unwatch()
</script>

模板引用

useTemplateRef 在3.5+支持

<script setup>
import { useTemplateRef, onMounted } from 'vue'// 第一个参数必须与模板中的 ref 值匹配
const input = useTemplateRef('my-input')onMounted(() => {input.value.focus()
})
</script><template><input ref="my-input" />
</template>

v-for中的模板引用

<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'const list = ref([/* ... */
])const itemRefs = useTemplateRef('items')onMounted(() => console.log(itemRefs.value))
</script><template><ul><li v-for="item in list" ref="items">{{ item }}</li></ul>
</template>

ref 数组并不保证与源数组相同的顺序

函数模板引用

<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

组件Ref

选项式API写法的子组件,ref得到的组件实例同this,即拥有对子组件属性和方法的完全访问。而使用组合式API的,子组件属性和方法都是私有的,需要使用defineExpose进行暴露。这与React中的Component组件和函数组件处理组件实例的方式是一致的。

<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'const childRef = useTemplateRef('child')onMounted(() => {// childRef.value 将持有 <Child /> 的实例
})
</script><template><Child ref="child" />
</template>
<script setup>
import { ref } from 'vue'const a = 1
const b = ref(2)// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({a,b
})
</script>

组件

定义props

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script><template><h4>{{ title }}</h4>
</template>或者export default {props: ['title'],setup(props) {console.log(props.title)}
}

监听事件

模板中调用回调

<BlogPost...@enlarge-text="postFontSize += 0.1"/><!-- BlogPost.vue, 省略了 <script> -->
<template><div class="blog-post"><h4>{{ title }}</h4><button @click="$emit('enlarge-text')">Enlarge text</button></div>
</template>

代码中调用回调

<script setup>
const emit = defineEmits(['enlarge-text'])emit('enlarge-text')export default {emits: ['enlarge-text'],setup(props, ctx) {ctx.emit('enlarge-text')}
}
</script>

通过插槽来分配内容

<!-- AlertBox.vue -->
<template><div class="alert-box"><strong>This is an Error for Demo Purposes</strong><slot /></div>
</template><style scoped>
.alert-box {/* ... */
}
</style>

动态组件

<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

当使用 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 <KeepAlive> 组件强制被切换掉的组件仍然保持“存活”的状态。

DOM 内模板解析注意事项

注意以下几点

  • 大小写区分
// JavaScript 中的 camelCase
const BlogPost = {props: ['postTitle'],emits: ['updatePost'],template: `<h3>{{ postTitle }}</h3>`
}
<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
  • 闭合标签
<MyComponent />
<my-component></my-component>
  • 元素位置限制
<table><blog-post-row></blog-post-row>
</table>
<table><tr is="vue:blog-post-row"></tr>
</table>

http://www.ppmy.cn/devtools/159222.html

相关文章

小爱音箱控制手机和电视听歌的尝试

最近买了小爱音箱pro&#xff0c;老婆让我扔了&#xff0c;吃灰多年的旧音箱。当然舍不得&#xff0c;比小爱还贵&#xff0c;刚好还有一台红米手机&#xff0c;能插音箱&#xff0c;为了让音箱更加灵活&#xff0c;买了个2元的蓝牙接收模块Type-c供电3.5接口。这就是本次尝试起…

pytorch训练五子棋ai

有3个文件 game.py 五子棋游戏 mod.py 神经网络模型 xl.py 训练的代码 aigame.py 玩家与对战的五子棋 game.py class Game:def __init__(self, h, w):# 行数self.h h# 列数self.w w# 棋盘self.L [[- for _ in range(w)] for _ in range(h)]# 当前玩家 - 表示空 X先…

Dockerfiles 的 Top 10 常见 DevOps/SRE 面试问题及答案

1. RUN 和 CMD 之间有什么区别&#xff1f; RUN : 在镜像构建过程中执行命令&#xff0c;创建一个新的层。通常用于安装软件包。 示例: RUN apt-get update && apt-get install -y curlCMD : 指定容器启动时默认运行的命令。它在运行时执行&#xff0c;而不是在构建过程…

基于HTML5 Canvas 和 JavaScript 实现的烟花动画效果

以下是一个使用 HTML5 Canvas 和 JavaScript 实现的烟花动画效果代码盒子: <!DOCTYPE html> <html> <head><title>烟花效果

开源模型应用落地-Qwen1.5-MoE-A2.7B-Chat与vllm实现推理加速的正确姿势(一)

一、前言 在人工智能技术蓬勃发展的当下,大语言模型的性能与应用不断突破边界,为我们带来前所未有的体验。Qwen1.5-MoE-A2.7B-Chat 作为一款备受瞩目的大语言模型,以其独特的架构和强大的能力,在自然语言处理领域崭露头角。而 vllm 作为高效的推理库,为模型的部署与推理提…

RedHat8安装postgresql15和 postgis3.4.4记录及遇到的问题总结

安装包对照版本参考 UsersWikiPostgreSQLPostGIS – PostGIS 如果Red Hat系统上有旧版本的PostgreSQL需要卸载 在较新的Red Hat版本&#xff0c;使用dnf包管理器卸载&#xff1a;sudo dnf remove postgresql-server postgresql 旧版本&#xff0c;使用yum包管理器卸载 sudo y…

本地部署DeepSeek + AnythingLLM 搭建高效安全的个人知识库

环境准备: 本地部署方案请参考博客:windows平台本地部署DeepSeek大模型+Open WebUI网页界面(可以离线使用)-CSDN博客 windows平台本地部署DeepSeek大模型+Chatbox界面(可以离线使用)-CSDN博客 根据本人电脑配置:windows11 + i9-13900HX+RTX4060+DDR5 5600 32G内存 确…

软著申请(二)文档鉴别材料【2025年最新版】

软著申请(二)文档鉴别材料【2025年最新版】 一、文档鉴别材料1、文档鉴别材料要求2、相关细节二、备注本服务提供详细的软件著作权申请流程指导。申请人严格按照指导步骤完成申请,若最终未能成功获得著作权登记,可联系服务提供方进行免费咨询和指导! 软著申请须知: 1.申…