Vue3 总结篇

416 阅读3分钟

Vue3 TypeScript Setup

近一年来写vue3的总结篇,包括props,父子组件通信,emit,expose等等相关总结。

Props && defineProps && withDefaults

当使用基本的defineProps的时候,其声明类型如下

interface Props {
    name: string
    age?: number
}
defineProps<Props>({/** ... */})
// or
defineProps<{/** ... */}>({/** ... */})

如果按照这样的写法,我们就失去了声明默认值的能力,这时候就需要通过编译器宏来解决 -> withDefaults

interface Props {
    name: string
    age?: number
}
const props = withDefaults(defineProps<Props>(), {
    name: 张三,
    age: 18
})
// or
const props = withDefaults(defineProps<{
/** ... */
}>(), {
    name: 张三,
    age: 18
})

template中使用

<template>
    // 可直接省略props. 如props.name
    <div>{{ name }}-{{ age }}</div>
</template>

Script中使用

const z3 = props.name
// 解构会失去响应式
const { name, age } = props

试验性写法响应式语法糖

vue version -> >= 3.2.25

vite(@vitejs/plugin-vue) version -> >= 2.0.0

如何使用⬇️

// vite.config.ts
export default ({ mode }: ConfigEnv): UserConfigExport => {
    return defineConfig({
        plugins: [
            vue({
                reactivityTransform: true
            })
        ]
    })
}

// 对 defineProps() 的响应性解构
// 默认值会被编译为等价的运行时选项
const { name, age = 18 } = defineProps<Props>()

Emit && defineEmit

<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])

// 基于类型
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

如何使用emit更新父组件参数或者调用传递的方法

// in father.vue
<template>
    <child
    v-model:name="name"
    :age="age"
    @update:age="age = $event"
    @updateName="updateName"
    ></child>
</template>
<script>
import Child from './child.vue'

const name = ref('张三')
const age = ref(18)
const updateName = () => {
    name.value = '李四'
}
</script>
// in child.vue
<script>
const props = withDefaults(defineProps<{
    name: string,
    age: number
}>(), {
    name: '',
    age: 0
})
const emit = defineEmits(['update:name', 'update:age', 'updateName'])

// how to use
const name2wFive = (name: sting = '王五') => {
    emit('update:name', name)
}
const addAge = (age: number = 19) => {
    emit('update:age', age)
}
const useUpdateName = () => {
    emit('updateName')
}
</script>

v-model:XXXXXXX = $event的升级版,不同于vue2的sync,vue3为了让v-model更好的针对多个属性进行双向绑定,对于自定义组件使用v-model指令的时候,绑定的默认值有原来的value变成了modelValue,时间名由input改为update:modelValue,去嗲了sync修饰符,由v-model的参数代替

expose && defineExpose && 模版引用 Ref

在某些情况下,我们需要直接访问DOM元素,比如说 计算元素宽高等等。我们可以使用特殊的refattribute

ref 是一个特殊的 attribute。它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。

如何使用?

<input ref="input">
<child ref="child"></child>

我们需要声明一个与使用同名input child的ref:

// in father.vue
<script setup lang="ts">
import { ref } from 'vue'
import Child from './child.vue'

const input = ref<HTMLInputElement | null>(null)
/**
* 为了获取Child的类型,通过typeof 获得其类型,再使用TypeScript内置InstanceType 工具类型来获取其实例类型
*/
const child = ref<InstanceType<typeof Child> | null>(null)
</script>

如何使用子组件方法以及变量

通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性

// in child.vue
<script setup lang="ts">
import { defineExpose } from 'vue'

const count = ref<number>(0)
const addCount = () => {
    count.value++
}

// 将其暴露出去
defineExpose({
    count,
    addCount
})
</script>

当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { count: number, addCount: void } (ref 会和在普通实例中一样被自动解包)

使用

// in father.vue
<script setup lang="ts">
import { ref } from 'vue'
import Child from './child.vue'

const input = ref<HTMLInputElement | null>(null)
/**
* 为了获取Child的类型,通过typeof 获得其类型,再使用TypeScript内置InstanceType 工具类型来获取其实例类型
*/
const child = ref<InstanceType<typeof Child> | null>(null)

const setChildCount = () => {
    // 自动解包 所以没有count.value
    console.log(child.value?.count)
    child.value?.addCount()
}
</script>

provide && inject

用以解决Prop 逐级透传问题

以下来自官网的讲解

通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:

image.png

注意,虽然这里的 <Footer> 组件可能根本不关心这些 props,但为了使 <DeepChild> 能访问到它们,仍然需要定义并向下传递。如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为“prop 逐级透传”,显然是我们希望尽量避免的情况。

provide 和 inject 可以帮助我们解决这一问题。 [1] 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。

image.png

通俗来说,是为了解决复杂结构props逐级传递

provide(arg1, arg2) 提供

接收两个参数

arg1: 注入名称,可以是一个字符串或者是一个Symbol他的后代组件会用注入名来查找注入的值,一个组件可以使用多次provide(),使用不同的注入名,注入不同的依赖值。

arg2: 提供的值,可以是任意类型,包括响应式的状态,如ref,reactive等,如果加上readonly(arg2),代表这项值不可改变

inject(arg1, arg2) 注入

接收两个参数

arg1: 注入的名称(你provide的注入名称

arg2: 如果没有祖先提供该名称的值,这将会是默认值,默认值可以为注册函数

// ancestors.vue 祖先
<script setup>
import { provide } from 'vue'

const age = ref<number>(18)
provide('name', '张三') // string
provide('age', readonly(age)) // readonly number
</script>
// no.N grandson.vue 第N代孙子
<script setup>
import { inject } from 'vue'

const name = inject<sting>('name') // string | undefined
const age = inject<number>('age') // readonly  number | undefined
const name2 = inject<number>('name') // error provide('name') is string
const age2 = inject<number>('age', 0) // readonly number
const age3 = inject('age') as number // readonly number
</script>