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:XXXX 是 XXX = $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 则必须将其沿着组件链逐级传递下去,这会非常麻烦:
注意,虽然这里的 <Footer> 组件可能根本不关心这些 props,但为了使 <DeepChild> 能访问到它们,仍然需要定义并向下传递。如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为“prop 逐级透传”,显然是我们希望尽量避免的情况。
provide 和 inject 可以帮助我们解决这一问题。 [1] 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
通俗来说,是为了解决
复杂结构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>