组合式API
通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup> 搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行转换,来减少使用组合式 API 时的样板代码
<script setup>
import { ref, onMounted } from 'vue'
//定义响应式数据
const count = ref(0)
// 用来修改状态 触发更新
function increment() {
count.value++
}
// 生命周期钩子函数
onMounted(() => {
console.log(count.value)
})
</script>
创建一个应用
import { createApp } from 'vue'
// 根组件
import App from './App.vue'
const app = createApp(App)
// 应用级错误处理
app.config.errHandler = err => {
/* 处理错误 */
}
// 注册全局组件
app.component(组件名,组件)
模板语法
// 动态绑定多个值
const objectOfAttrs = {
name: "bob",
age: 24
}
<div v-bind="objectOfAttrs"></div>
// 相当于
<div :name="objectOfAttrs.name" :age="objectOfAttrs.age"></div>
响应式
可以使用 reactive()函数创建一个响应式对象或数组
import { reactive } from 'vue'
cosnt state = reactive({count: 0})
缺点:
- 仅对对象类型有效(对象、数组、Map、Set这样的集合类型),对string,number,boolean这样的原始类型无效
- 因为Vue的响应式系统是通过property访问进行追踪的,因此必须始终保持对该响应式对象的相同引用 这意味着不可以随意替换一个响应式对象
let state = reactive({count: 0})
// err
state = reactive({count:1})
将响应式对象的property赋值或解构至本地变量时 或是将该property传入一个函数时会失去响应性
const state = reactive({count: 0})
// 失去响应式连接
let n = state.count
n++ // 不影响原始的state
// 失去响应式连接
let { count } = state
count++ // 不影响原始的state
ref()定义响应式变量
ref()方法允许创建任何类型的响应式ref,ref()从参数中获取到值将其包装成一个带.value property的ref对象
import { ref } from 'vue'
const count = ref(0)
console.log(count) // {value: 0}
count.value++
consloe.log(count.value) // 1
当值为对象类型时,会用reactive()自动转换它的.value
const objRef = ref({count: 0})
// 响应式替换
objRef.value = {count: 0}
ref被传递给函数或是从对象上被解构是 不会失去响应性
const obj = {
name: ref('bob'),
age: ref(24)
}
// 接收一个ref 需要通过.value来取值 会保持响应性
callSomeFunction(obj.name)
// 仍然是响应式的
const {name,age} = obj
ref在模板中作为顶层property被访问时 会自动解包 不需要使用.value
<script setip>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{count}} <!-- 无需.value -->
</button>
</template>
仅当ref是模板渲染的顶层才会自动解包
const obj = {foo: ref(1)}
{{obj.foo + 1}} //[object object]
// 可以通过让foo成为顶层 或使用.value
{{obj.foo.value + 1}} // 2
const { foo } = obj
{{foo + 1}} // 2
// 如果一个ref是文本插值 也将被解包
{{obj.foo}} // 1
ref在响应式对象中的解包
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
// 将一个新的ref赋值给一个关联了已有ref的值 会替换掉旧ref
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 已经失去和原有ref的关联
console.log(count.value) // 1
数组和集合类型的ref解包
但ref作为响应式数组或像map这种原生集合类型的元素被访问时 不会进行解包
const books = reactive([ref('Vue 3 Guide')])
console.log(books[0].value) // 需要.value
const map = reactive(new Map([['count',ref(0)]]))
console.log(map.get('count').value) // 需要.value
计算属性
<script setup>
import { reactive,computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const myComputed = computed(() => {
return author.books.length > 0? 'yes': 'no'
})
</script>
<template>
<span>{{ myComputed }}</span>
</template>
可写计算属性
计算属性默认只能通过计算函数得出结果 当你尝试修改一个计算属性的时 会受到一个运行警告 可写的计算属性 可以通过同时提供 getter 和 setter来创建
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
cosnt lastName = ref('Doe')
const fullName = computed(() => {
// getter
get() {
return firstName.value + ' ' +lastName.value
}
// setter
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
//
fullName.value = 'John Bob' // setter会被调用 firstName 和 lastName 会被更新
</script>
生命周期钩子
onBeforeMount()组件挂载前调用onMounted()组件挂载完成后执行onBeforeUpdate()组件更新前调用onUpdated()组件更新后执行onBeforeUnmount()组件卸载前调用onUnmounted()组件卸载后调用onErrorCaptured()当从下级组件抛上来的错误被捕获时调用onRenderTracked()当组件的渲染效果跟踪了一个响应性依赖时调用 仅开发模式下可用onRenderTriggered()当响应式依赖触发组件的渲染效果重新运行时调用 仅开发模式下可用onActivated()当组件实例作为树的一部分被<KeepAlive>缓存,被插入到 DOM 中时调用onDeactivated()当组件实例作为树的一部分被<KeepAlive>缓存,从DOM 中移除时调用onServerPrefetch()仅服务端渲染 在服务器上渲染前 注册一个要完成的异步函数
侦听器
import { ref, watch } from 'vue'
const name = ref('bob')
watch(name, (newValue,oldValue) => {
console.log(newValue,oldValue)
})
侦听来源类型
可以是一个ref(包括计算属性) 一个响应式对象 一个getter函数 或多个来源组成的数组
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}`)
})
// 不能侦听响应式对象的property
const obj = reactive({count: 0})
// error 这里相当于向watch() 传入了一个number
watch(obj.count, count => {
console.log(count)
})
// 可以使用getter函数
watch(
() => obj.count,
count => {
console.log(count)
}
)
深层侦听 deep
watch(
() => obj.count,
count => {
console.log(count)
},
{ deep: true }
watchEffect 创建时立即执行一次
watch vs. watchEffect
watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch只追踪明确侦听的源。它不会追踪任何在回调中访问到的东西。另外,仅在响应源确实改变时才会触发回调。watch会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式 property。这更方便,而且代码往往更简洁,但其响应性依赖关系不那么明确。
回调的刷新时机
当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post' 选项:
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect():
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
模板ref
为了通过组合式API获取到该模板的ref 需要声明一个同名的ref
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个同名的ref来存放该元素的引用
// 必须和模板ref同名
const input = ref(null)
onMonted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
使用了<script setup>的组件是默认私有的 父组件无法访问到使用了<script setup>的子组件中的任何东西 子组件可以通过defineExpose显示的暴露
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(0)
defineExpose({
a,
b
})
</script>
当父组件通过ref获取到该组件的实例时 得到的实例类型为{ a: number, b: number } (ref会自动解包)
组件
Prop
组件需要显示的声明prop 这样vue才知道哪些是prop 哪些是attribute
const props = defineProps(['foo'])
console.log(props.foo)
defineProps({
title: String,
likes: Number
})
// prop校验
defineProps({
// 基础类型检查
propA: Number,
// 多种类型检查
propB: [String,Number],
propC: {
type: String,
required: true // 必传
},
propD: {
type: Number,
default: 100 // 默认值
},
// 对象类型默认值
propE: {
type: Object,
default() {
return {name: 'bob'}
}
},
// 自定义校验函数
propF: {
validator(value) {
return ['success','error'].includes(value)
}
}
})
事件
defineEmits()
组件需要触发的事件 可以通过defineEmits()来声明
// defineEmits()
const emit = defineEmits(['inFocus'])
自定义组件实现v-model
<MyComponent :modelValue="value" @update:modelValue="newValue => value = newValue" />
// MyComponent.vue
<script setup>
defineprops(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
<template>
另一种方式实现
// MyComponent.vue
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emits = defineEmits(['update:modelValue'])
const value = computed( {
get(){
return props.modelValue
},
set(value) {
emits('update:modelValue',value)
}
})
</script>
<template>
<input v-model="value" />
</template>
v-model 的参数
默认情况下, 在组件上都是使用 modelValue 作为 prop,以 update:modelValue 作为对应的事件。我们可以通过给 v-model 指定一个参数来更改这些名字
<MyComponent v-model:title="title" />
// MyComponent.vue
<script setup>
defineprops(['title'])
defineEmits(['update:title'])
</script>
<template>
<input :value="title" @input="$emit('update:title', $event.target.value)" />
<template>
多个v-model绑定
<MyComponent v-model:name="name" v-model:age="age"/>
// MyComponent.vue
<script setup>
defineprops(['name','age'])
defineEmits(['update:name','update:age'])
</script>
<template>
<input :value="age" @input="$emit('update:age', $event.target.value)" />
<input :value="name" @input="$emit('update:name', $event.target.value)" />
<template>
v-model自定义修饰符
给v-model添加修饰符都可以通过modelModifiers prop在组件内部访问到
<MyComponent v-model:capitalize="name" />
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: {default: () => {}}
})
const emit = defineEmits(['update:modelValue'])
console.log(props.modelModifiers) //{ capitalize: true }
</script>
对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + "Modifiers"
<MyComponent v-model:title.capitalize="title" />
// MyComponent.vue
<script setup>
defineprops(['title','titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) //{ capitalize: true }
</script>
透传 Attribute
Attribute继承
// MyBtn.vue
<button class="btn1">click me</button>
<Mybtn class="btn2" style="{width: 50px}"/>
// 最后 渲染成
<button class="btn1 btn2" style="{width: 50px}">click me</button>
透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到,$attrs 对象包含了除组件的 props 和 emits 属性外的所有其他 attribute
<div>
<button v-bind="$attrs">click me</button>
</div>
想要所有像 class 和 v-on 监听器这样的透传 attribute 都应用在内部的 <button> 上而不是外层的 <div> 上。我们可以通过设定 inheritAttrs: false 和使用 v-bind="$attrs" 来实现
在js中访问Attribute
可以在 <script setup> 中使用 useAttrs() API 来访问一个组件的所有透传 attribute
import{ useAttrs } from 'vue'
const attrs = useAttrs()
依赖注入
Provide(供给)
为后代组件提供数据
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */'message', /* 值 */'hello')
// 注入名 可以是一个字符串或者是一个Symbol
</script>
Inject(注入)
注入祖先组件供给的数据
import { inject } from 'vue'
const message = inject('message')
// 如果注入的是响应式ref 不会自动解包 且保持响应式
如果需要在后代组件中修改注入的值 推荐在provide提供一个修改值的方法
/* provide */
<script setup>
import { ref,provide } from 'vue'
const name = ref('bob')
function updateName() {
name.value = 'John'
}
provide('name',{
name,
updateName
})
</script>
/* inject */
<script setup>
import { inject } from 'vue'
const { name,updateName } = inject('name')
</script>
<template>
<button @click="updateName">{{ name }}</button>
</template>
如果不想让后代组件修改注入的值 可以使用readonly()
<script setup>
import { ref,provide, readonly } from 'vue'
const name = ref('bob')
provide('readOnlyName', readonly(name))
</script>
组合式函数(hook)
实现鼠标追踪的功能
/* useMouse.js */
import { ref,onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {window.addEventListenter('mousemove',update)})
onUnmounted(() => {window.removeEventListenter('mousemove',update)})
}
// 使用
<script setup>
import { useMouse } from './useMouse.js'
const { x, y } = useMouse()
</script>
<template>
<span>Mouse position is at : {{x}}, {{y}} </span>
</template>