本篇为个人学习笔记记录
创建一个vue应用
- 一个应用实例
- 应用实例:createApp函数创建一个应用(实际是一个组件)
- 根组件:每一个应用都需要一个根组件
- 挂载应用:需要调用.mount()方法应用才会被渲染
// 1.createApp函数创建一个新的实例
import { createApp } from 'vue'
// 2.app根组件
const app = createApp({
// 根组件的选项
data() {
return {
count: 0
}
}
})
// 3.应用渲染
app.mount('#app')
- 应用配置
- 可暴露一个.config对象允许配置应用级选项
- 提供方法可以注册应用范围内的可用资源,如app.component
// 配置应用级错误处理器
app.config.errorHandler = (err) => {
/* 处理错误 */
}
// 注册组件
app.component('TodoDeleteButton', TodoDeleteButton)
- 多个应用创建
- 允许在同一个页面中创建多个共存的Vue应用,每个应用都有自己的用于配置和全局资源的作用域
const app1 = createApp({
/* ... */
})
app1.mount('#container-1')
const app2 = createApp({
/* ... */
})
app2.mount('#container-2')
模板语法
- 文本插值,大括号
- 原始的html,使用v-html指令
- attribute绑定v-bind
- 布尔型attribute
- 动态绑定多个值,绑定一个对象
- 指令directives
- v-bind/v-html/v-if/v-for/v-on/v-slot
- 参数arguments,某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识
- 同名可简写:id ==> :id=id
- 仅支持单一的表达式,可使用javaScript表达式(判断方式:是否可以合法的写在return后面)
- 调用函数,表达式的方法在每次更新都会被重新调用,不会产生任何副作用
- 受限的全局访问,不能访问用户附加在window上的属性,但可以在app.config.globalProperties上显示添加
- 动态参数,可以绑定一个计算属性,计算得到的值会被用作一个参数,事件绑定类似,@focus
- 动态参数值会被限制,必须是一个字符串,可以是null,表示没有,undefind会报错
- 动态参数语法限制,传入复杂的地台参数,推荐使用计算属性替换复杂的表达式,模版字符串是区分大小写的
- 修饰符modifiers, - .prevent
// 文本插值
<span>Message: {{ msg }}</span>
// 原始HTML
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
// attribute绑定v-bind
<div v-bind:id="dynamicId"></div>
//简写
<div :id="dynamicId"></div>
// 与 :id="id" 相同 仅支持3.4以上版本
<div :id></div>
//布尔型attribute
<button :disabled="isButtonDisabled">Button</button>
// 动态绑定多个值
const objectOfAttrs = {
id: 'container',
class: 'wrapper',
style: 'background-color:green'
}
<div v-bind="objectOfAttrs"></div>
// 使用javaScript表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
// 仅支持表达式
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}
<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}
// 调用函数
<time :title="toTitleDate(date)" :datetime="date">
{{ formatDate(date) }}
</time>
// 参数Arguments
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>
// 动态参数
someAttr 复杂的属性使用计算属性表达式 不推荐使用 + -符号
<a :[someAttr]="value"> ... </a>
<a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>
//修饰符modifiers
<form @submit.prevent="onSubmit">...</form>
响应式基础
- 声明响应式状态
- ref(),接收参数,对象中有value属性返回,声明一个属性count,ref(0),count.value == 0 ,进行修改
- 在setup()函数中声明并返回,模版使用ref时,不需要附加.value
- 组件渲染时,通过追踪ref的变化,触发组件重新渲染,底层用到的是getter和setter方法来拦截对象属性进行get和set操作
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// 在 JavaScript 中需要 .value
count.value++
}
// 不要忘记同时暴露 increment 函数
return {
count,
increment
}
}
}
// 暴露的方法可以用作事件监听
<button @click="increment">
{{ count }}
</button>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
- 深层响应性
- 可以改变深层嵌套的对象、数组,内置的数据结果,dom更新不同步,需要用nextTick()全局api
- 可通过shallow ref 来放弃深层响应性
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都会按照期望工作
// dom 更新不同步
obj.value.nested.count++
obj.value.arr.push('baz')
}
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
- reactive()
- 非原始值,转换为响应式代理,使对象具有响应性,有shallowReactive()退出深层次响应
- reactive返回的是原始对象的proxy,和原始对象不相等
- 局限性:有限的值类型,只能用于对象、数组 map set这样的集合类型,不支持string\number\boolean
- 局限性不能替换整个对象,因为是通过属性访问实现的
- 对解构操作不友好,会丢失连接
import { reactive } from 'vue'
const state = reactive({ count: 0 })
// 能够拦截对响应式对象所有属性的访问和修改,进行依赖追踪和触发更新
<button @click="state.count++">
{{ state.count }}
</button>
const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
// 返回的是一个原始对象Proxy
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
// 不能替换整个对象
let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
// 解构操作不友好
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
- 额外的ref解包细节
- 嵌套在深层次响应对象属性,才会发生ref的解包
- 作为响应式对象被访问时会自动解包
- 一个新的值赋值给一个关联的ref属性,那会替换掉就的ref
// 作为reactive的对象属性,自动解包
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
// 已有的ref属性替换掉就的ref
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1
- 数组和集合的注意事项
- reaxtive()
- ref()
- 作为响应式数组或原生集合类型(如Map)被访问时,不会被解包
- 在模版渲染上下文中,只有顶级的ref属性才会被解包
// ref作为响应式数组或原生集合类型(如Map)元素被访问时,不会被解包
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
// 模版解包 count object为顶级属性 object.id不是
const count = ref(0)
const object = { id: ref(1) }
{{ count + 1 }}
{{ object.id + 1 }} // 没有解包,错误表达,没有效果
解决方式
const { id } = object
{{ id + 1 }} //渲染结果 2
{{ object.id }} //如果这样 渲染结果 1
计算属性
- 计算属性基于其响应式依赖被缓存
- 计算属性,可通过getter和setter可写,setter被调用会更新
- getter只做计算,不要改变其他状态,也不要做一步请求
- 避免直接修改计算属性,可用侦听器
<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(' ')
}
})
</script>
// 获取计算属性的上一个的值
<script setup>
import { ref, computed } from 'vue'
const count = ref(2)
const alwaysSmall = computed({
get(previous) {
if (count.value <= 3) {
return count.value
}
return previous
},
set(newValue) {
count.value = newValue * 2
}
})
</script>
类与样式绑定
- 绑定HTML class
- 可绑定对象、计算属性、数组、在组件上使用
const classObject = reactive({
active: true,
'text-danger': false
})
<div :class="classObject"></div>
// 渲染结果
<div class="active"></div>
// 绑定计算属性
const isActive = ref(true)
const error = ref(null)
const classObject = computed(() => ({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal'
}))
// 渲染结果
<div :class="classObject"></div>
// 绑定数组
const activeClass = ref('active')
const errorClass = ref('text-danger')
<div :class="[activeClass, errorClass]"></div>
// 渲染结果
<div class="active text-danger"></div>
// 在组件上使用
<div :class="classObject"></div>
<!-- 在使用组件时 -->
<MyComponent class="baz boo" />
<p class="foo bar baz boo">Hi!</p>
- 绑定内敛样式
:style支持绑定js对象
// 绑定对象
const styleObject = reactive({
color: 'red',
fontSize: '30px'
})
<div :style="styleObject"></div>
// 绑定数组
<div :style="[baseStyles, overridingStyles]"></div>
// 数组仅会渲染浏览器支持的最后一个值
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
条件渲染
- v-if
- 值为
true时渲染,渲染多次,更高开销 - 渲染多个时,使用
<template />
- 值为
- v-show
- 与v-if不同,初级渲染开销,一直存在,与
dispaly:none
- 与v-if不同,初级渲染开销,一直存在,与
- v-for
- 不推荐与v-if使用,v-if会先执行,可使用
template
- 不推荐与v-if使用,v-if会先执行,可使用
列表渲染
- v-for:遍历数组
- 可以完整的访问父作用域的属性和变量,index表示索引的位置
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
// 页面结果
Parent-0-Foo
Parent-0-Bar
- v-for:遍历对象
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
// 页面结果
- 0. title: How to do lists in Vue
- 1. author: Jane Doe
- 2. publishedAt: 2016-04-10
- v-for 使用范围值
<templiate />包裹,在v-for与v-if一起使用时
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
- 通过key值管理状态
- 当使用
<template v-for>时,key应该被放置在这个<template>容器上: key绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为v-for的 key。
- 当使用
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
// key值管理状态
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
- 组件上使用v-for
- 不会将任何数据传递给组件,组件有独立的作用域
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
- 数组变化侦测
- 能够侦听响应数组的变更方法,js不会更改原数组的,会返回一个新的数组
- 变更:
push(),pop(),shift(),unshift(),splice(),sort(),reverse() - return 一个新的数组
filter(),concat(),slice()
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>
事件处理
- 内联事件处理
- 访问原生dom事件是,可以传入一个$event 变量,或者箭头函数
const count = ref(0)
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
- 方法事件处理
- 模版编译器会通过检查v-on的值来判断是什么事件处理器,
const name = ref('Vue.js')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
- 事件修饰符
<!-- 单击事件将停止传递 -->
<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>
- 按键修饰符
- 按键别名
.enter,.tab,delete,.esc,.space,.up,.down,.left,.right - 系统按键修饰符,只有当按键被按下时,才会被触发
.ctrl,.alt,.shift,.meta
- .exact 修饰符
- 按键别名
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
<input @keyup.page-down="onPageDown" />
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>
- 鼠标按键修饰符
.left,.right,.middle限定由鼠标按键触发的事件,基于右手鼠标布局设定,分别指代事件触发器的主、次、辅助,并非实际的物理按键
表单的输入与绑定
v-model可以用于不同类型的表单输入,会根据使用的元素自动对于dom属性和事件
- 基本用法
<p>Message is: {{ message }}</p>
<!-- 输入框 -->
<input v-model="message" placeholder="edit me" />
<!-- 文本框 不能直接使用大括号 -->
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
<!-- 单选框 -->
input type="radio" id="one" value="One" v-model="picked" /> <label for="one">One</label>
<!-- 选择器 -->
const selected = ref('A')
const options = ref([
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
])
<select v-model="selected">
<option v-for="option in options" :value="option.value">
{{ option.text }}
</option>
</select>
<div>Selected: {{ selected }}</div>
- 值绑定
<!-- `picked` 在被选择时是字符串 "a" -->
<input type="radio" v-model="picked" value="a" />
<!-- `toggle` 只会为 true 或 false -->
<input type="checkbox" v-model="toggle" />
<!-- `selected` 在第一项被选中时为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
<!-- 复选框 -->
<!-- `true-value` 和 `false-value` 是 Vue 特有的 attributes,仅支持和 `v-model` 配套使用。这里 `toggle` 属性的值会在选中时被设为 `'yes'`,取消选择时设为 `'no'`。你同样可以通过 `v-bind` 将其绑定为其他动态值 -->
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no" />
- 修饰符
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
<!-- 输入为空时,返回空字符串,如果无法被parseFloat处理,将返回原始值 -->
<input v-model.number="age" />
<!-- 去除首尾空格 -->
<input v-model.trim="msg" />
生命周期钩子
- created初始化编译
- mounted 可以访问dom节点
- updated 重新渲染布丁完毕
- unmounted 取消挂载
graph TD
setup组合式api --> beforeCreate-->created--> beforeMounted --> mounted --> beforeUpdate --> updated --> beforeUnmount --> unmounted
侦听器
- 组合式api
watch函数- 侦听数据源类型,ref(包括计算属性),一个响应式对象,一个getter函数,不能直接侦听对象的属性
- {deep: true}深度监听,遍历对象嵌套的属性级数,开销很大
- {immediate: true},立即监听,数据源发生变化时立即回调
- {once: true } 一次性侦听器,回调只触发一次
const obj = reactive({ count: 0 })
// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
console.log(`Count is: ${count}`)
})
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`Count is: ${count}`)
}
)
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
},
{ deep: true } // 深度监听后,`newValue` 此处和 `oldValue` 是不相等,开销较大,慎用
{ immediate: true } // 即时监听
{ once: true } // 3.4以上版本 回调时至触发一次
)
- watchEffect
- 可以直接监听,自动追踪对象的属性做为依赖,当值发生变化时,会立即执行回调函数
- 相对于watch较小,监听潜逃的数据结构的属性特别适合,只追踪被使用到的属性
const todoId = ref(1)
const data = ref(null)
watch(
todoId,
async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
},
{ immediate: true }
)
// 仅会在同步执行期间,追踪依赖,第一个await正常工作犬访问到的属性才会被追踪
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
-
watchvswatchEffectwatch只追踪明确侦听的数据源。它不会追踪对象里的属性。仅在数据源确实改变时才会触发回调。watch会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect,则会在副作用发生期间追踪依赖。可追踪对象属性的变化,它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
-
副作用清理
- onWatcherCleanup(),Vue3.5+ 版本支持,注册清理函数,终止过期请求,不能在异步await后调用,调用时间与watchEffect和watch回调函数是同步执行的
- onCleanup
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// 回调逻辑
})
onWatcherCleanup(() => {
// 终止过期请求
controller.abort()
})
})
// onCleanup作为第三个参数传给侦听器回调
watch(id, (newId, oldId, onCleanup) => {
// ...
onCleanup(() => {
// 清理逻辑
})
})
// onCleanup 作为第一参数
watchEffect((onCleanup) => {
// ...
onCleanup(() => {
// 清理逻辑
})
})
- 回调触发的时机
- 父组件更新后,所有组件的dom更新之前被调用,在侦听器回调小红访问所有组件的dom,则dom将处于更新状态
- 如果侦听器回调中想要访问vue更新之后的所属组件的dom,需指明
flush: 'post'
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
在vue更新之前触发的侦听器,慎用,可以监听布尔值,避免多次修改数据源
watch(source, callback, {
flush: 'sync'
})
watchEffect(callback, {
flush: 'sync'
})
import { watchSyncEffect } from 'vue'
watchSyncEffect(() => {
/* 在响应式数据变化时同步执行 */
})
- 停止侦听器 会在宿主组件卸载时,侦听器会自动停止,但需要注意的是,异步回调创建侦听器时,不会绑定到组件上,必须手动停止它,异步创建很少,推荐使用同步创建,用条件判断
import { watchSyncEffect } from 'vue'
watchSyncEffect(() => {
/* 在响应式数据变化时同步执行 */
})
// 手动停止侦听器
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 数据加载后执行某些操作...
}
})