Vue3文档学习笔记

193 阅读7分钟

组合式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})

缺点:

  1. 仅对对象类型有效(对象、数组、Map、Set这样的集合类型),对string,number,boolean这样的原始类型无效
  2. 因为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>