带你全面了解Vue2和3区别,读这一篇就够啦!!
详解vue2到vue3的新增特性、不兼容写法和一些删除的API。
一、新增特性
1.组合式API
vue2:选项式API
vue3:选项式API、组合式API( 推荐)
为什么要有组合式 API?
-
更好的逻辑复用
组合式 API 能够通过组合函数来实现更加简洁高效的逻辑复用。在选项式 API 中主要的逻辑复用机制是 mixins,组合式 API 解决了 mixins 的所有缺陷。与此同时组合式 API 在逻辑复用能力上孵化了一些非常棒的社区项目,比如 VueUse、VueHook Plus。
-
更灵活的代码组织
选项式API:处理相同逻辑的代码被拆分到不同的选项中,位于文件的不同部分。这样读懂代码中的一个逻辑关注点和抽象组织可复用的工具函数极为困难。
组合式API:同一个逻辑关注点相关的代码被归为了一组。此外,可以很轻松地将这一组代码移动到一个外部文件中,大大降低了重构成本,这在长期维护的大型项目中非常关键。
-
更好的类型推导
Vue2.x使用的选项式 API ,在设计时并没有把类型推导ts考虑进去,因此需要做一些复杂到夸张的类型体操才实现了对选项式 API 的类型推导。然而类型推导在处理 mixins 和依赖注入类型时依然不理想。
Vue3.x使用的组合式 API 主要利用基本的变量和函数,它们本身就是类型友好的。用组合式 API 重写的代码可以享受到完整的类型推导,不需要书写太多类型标注。
-
更小的生产包体积
选项式 API 需要依赖
this
上下文对象访问属性,对象的属性名则不能压缩。使用
<script setup>
形式书写的组件被编译的模板可以直接访问脚本中定义的变量,无需从实例中代理。本地变量的名字是可以被压缩。
2.Teleport组件
<Teleport>
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
开发中常见的一个例子就是全屏的模态框。理想情况下,我们希望触发模态框的按钮和模态框本身是在同一个组件中,因为它们都与组件的开关状态有关。但这意味着该模态框将与按钮一起渲染在应用 DOM 结构里很深的地方。这会导致该模态框的 CSS 布局代码很难写。
<Teleport>
提供了一个更简单的方式来解决此类问题,让我们不需要再顾虑 DOM 结构的问题。让我们用 <Teleport>
改写一下 <MyModal>
:
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
<Teleport>
接收一个 to
prop 来指定传送的目标。to
的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body
标签下”。
3.Fragments 片段
Vue2.x中,不支持多根节点组件,这样许多组件需要被包裹在了一个 <div>
中。增加了DOM结构的复杂性。
<!-- Layout.vue -->
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
Vue3.x 中,组件可以包含多个根节点!但是在使用透传attribute时需要显式定义其应该分布在哪里。
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
4.单文件组件中的状态驱动的 CSS 变量
Vue3.x中,单文件组件的 <style>
标签支持使用 v-bind
CSS 函数将 CSS 的值链接到动态的组件状态,自定义属性会通过内联样式的方式应用到组件的根元素上,并且在源值变更的时候响应式地更新。相较于Vue2功能更加强大了。
<script setup>
import { ref } from 'vue'
const theme = ref({
color: 'red',
})
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
5.SFC <style scoped>
新增全局规则和针对插槽内容的规则
深度选择器使用方法变化
Vue2.x中,深度选择器 >>>
,/deep/
。
Vue3.x中,深度选择器 ::v-deep()
,:deep()
。推荐使用:deep()
,::v-deep()
会控制台发出警告。
// Vue2
<style scoped>
.a >>> .b {
/* ... */
}
.a{
/deep/ .b {
/* ... */
}
}
</style>
// Vue3
<style lang="scss" scoped>
.a{
::v-deep(.b) {
/* ... */
}
:deep(.c){}
}
</style>
新伪元素 ::v-slotted() 简写 :slotted()
Vue 2.x 中,父组件通过 slot 传给子组件的内容是不受子组件局部样式的影响的。
Vue 3.x 中,可以借助新伪元素::v-slotted(),实现子组件控制slot 的样式。
::v-slotted(.foo) {}
:slotted(.foo){}
新伪元素 ::v-global() 简写 :global()
Vue 2.x 中,scoped styles 的样式只能在局部生效。
Vue 3.x 中,增加了一个全新的伪元素::v-global(),它可以让 scoped styles 中的样式到全局中生效。
::v-global(.foo){}
:global(.foo){}
6.Attribute 绑定简写
Vue2.x中,组件/HTML元素想要响应式地绑定一个 attribute,应该使用 v-bind
指令:
<!-- 虽然:id="id"属性名称和绑定的js值相同,但是不能简写 -->
<div v-bind:id="id"></div>
<MyComponent :id="id"></MyComponent>
Vue3.4+中,如果 attribute 的名称与绑定的 JavaScript 值的名称相同,那么可以进一步简化语法,省略 attribute 值:
<!-- 与 :id="id" 相同 -->
<div :id></div>
<!-- 这也同样有效 -->
<div v-bind:id></div>
7.Suspense
Suspense
是 Vue 3 中用于处理异步数据加载的特性,它使得在加载异步数据时可以提供更好的用户体验,同时让开发者更轻松地管理异步操作。
Suspense 可以等待的异步依赖有两种:
<template>
<div>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));
export default {
components: {
AsyncComponent
}
};
</script>
在上面的代码中,我们使用了 Suspense 组件来包裹异步组件 <AsyncComponent>
。在默认插槽中,我们将异步组件的引入放置在 <template>
中,这样当异步组件加载完成时,它会被渲染到页面上。在 fallback 插槽中,我们可以显示一个加载中的提示信息,以提供更好的用户体验。
此外,Suspense 还可以处理异步组件加载失败的情况。当异步组件加载出错时,fallback 插槽中的内容会被渲染,可以显示一条错误提示或者其他备用内容。
目前,<Suspense>
是一项实验性功能。它不一定会最终成为稳定功能,并且在稳定之前相关 API 也可能会发生变化。
二、非兼容性改变
1.模板指令
v-model
Vue2.0
开发者使用 v-model
指令时必须使用名为 value
的 prop,且在组件上只允许使用一个 v-model
。如果开发者出于不同的目的需要使用其他的 prop,就不得不使用 v-bind.sync
。
v-model使用:
<Child v-model="msg"/>
<template>
<div>
<p>我是model子组件: {{ value }}</p>
<button @click="$emit('input', value + 1)">value+1</button>
</div>
</template>
<script>
export default {
props: ["value"]
}
</script>
v-bind.sync使用:
<Child :num1.sync="num1" :num2.sync="num2"></Child>
<template>
<div>
<p>我是sync子组件: {{ num1 }}</p>
<p>我是sync子组件: {{ num2 }}</p>
<button @click="$emit('update:num1', num1 + 1)">num1</button>
<button @click="$emit('update:num2', num2 + 1)">num2</button>
</div>
</template>
<script>
export default {
props: ["num1", "num2"]
}
</script>
Vue3.0+
在 Vue 3.x 中,双向数据绑定的 API 已经标准化,v-model指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync。可以在同一个组件上使用多个 v-model 绑定,可以自定义 v-model修饰符,更加灵活。
Vue3.4版本之前单个v-model绑定实例:
<CustomInput v-model="searchText" />
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
Vue3.4版本之前多个v-model绑定实例:
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
<!-- UserName.vue -->
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
Vue3.4版本以后单个v-model绑定实例:
<MyComponent v-model:title="bookTitle" />
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
Vue3.4版本以后多个v-model绑定实例:
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
<!-- UserName.vue -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
v-bind 合并行为
在 Vue2.x 中,如果一个元素同时定义了 v-bind="object"
和一个相同的独立 attribute,那么这个独立 attribute 总是会覆盖 object
中的绑定,较为死板。
<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="red"></div>
在Vue3.x 中,如果一个元素同时定义了 v-bind="object"
和一个相同的独立 attribute,那么绑定的声明顺序将决定它们如何被合并。这样开发者能够按照希望方式进行合并。
<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="blue"></div>
<!-- 模板 -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- 结果 -->
<div id="red"></div>
v-if 与 v-for 的优先级对比
Vue2.x 版本中在一个元素上同时使用 v-if
和 v-for
时,v-for
会优先作用。
Vue3.x 版本中 v-if
总是优先于 v-for
生效。
移除 v-on.native
修饰符
在Vue2.x中,传递给带有 v-on
的组件的事件监听器只能通过 this.$emit
触发。要将原生 DOM 监听器添加到子组件的根元素中,可以使用 .native
修饰符:
<my-component
v-on:close="handleComponentEvent"
v-on:click.native="handleNativeClickEvent"
/>
在Vue3.x中,子组件中未被定义为组件触发的事件监听器,将把它们作为原生事件监听器添加到子组件的根元素中 (除非在子组件的选项中设置了 inheritAttrs: false
)。
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
MyComponent.vue
<script>
export default {
emits: ['close'] //没有定义click事件,这是click被当作原生事件监听器添加到子组件根元素上了
}
</script>
2.组件
函数式组件
Vue 2.x 中,函数式组件主要有两个应用场景:
- 作为性能优化,因为它们的初始化速度比有状态组件快得多
- 返回多个根节点
Vue 3.x 中,有状态组件的性能已经提高到与函数式组件之间的区别可以忽略不计的程度。此外有状态组件也支持返回多个根节点。
因此,函数式组件剩下的唯一应用场景就是简单组件,比如创建动态标题的组件。否则,建议像平常一样使用有状态组件。
异步组件
变化的总体概述:
- 新的
defineAsyncComponent
助手方法,用于显式地定义异步组件 - 选项名称的修改:
component => loader
,error=>errorComponent
,loading=>loadingComponent
- Loader 函数本身不再接收
resolve
和reject
参数,且必须返回一个 Promise
Vue 2.x中,加载异步组件定义方法:
// 不带选项的异步组件
const asyncModal = () => import('./Modal.vue')
//带有选项的更高阶的组件语法
const asyncModal = {
component: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
Vue 3 .x中,函数式组件被定义为纯函数,因此异步组件需要通过将其包裹在新的 defineAsyncComponent
助手方法中来显式地定义:
import { defineAsyncComponent } from 'vue'
// 不带选项的异步组件
const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
// 带选项的异步组件
const asyncModalWithOptions = defineAsyncComponent({
loader: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
emits 选项
Vue 2.x 中,可以定义一个组件可接收的 prop,但是无法声明它可以触发哪些事件。
Vue 3.x 中, 提供一个 emits
选项,和现有的 props
选项类似。这个选项可以用来定义一个组件可以向其父组件触发的事件。
//Vue2.x写法
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text']
}
</script>
//Vue3.x写法
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text'],
emits: ['accepted']
}
</script>
3.渲染函数
渲染函数 API
渲染函数API的更改不会影响 <template>
用户。所以不在细写。详细文档请参考:v3-migration.vuejs.org/zh/breaking…
插槽统一
Vue 3.x 中更改统一了的普通插槽和作用域插槽属性。
- Vue 2.6.0 版本以前使用
slot
和slot-scope
attribute。 - Vue 2.6.0 版本以后引入了v-slot,提供更好的支持
slot
和slot-scope
attribute 的 API 替代方案。 - Vue 3.x 版本中废弃
slot
和slot-scope
写法,统一使用v-slot
。
$listeners 合并到 $attrs
在 Vue 2.x 中,你可以通过 this.$attrs
访问传递给组件的 attribute,以及通过 this.$listeners
访问传递给组件的事件监听器。结合 inheritAttrs: false
,开发者可以将这些 attribute 和监听器应用到根元素之外的其它元素。
在 Vue 3.x 的虚拟 DOM 中,事件监听器现在只是以 on
为前缀的 attribute,这样它就成为了 $attrs
对象的一部分,因此 $listeners
被移除了。
也就是说vue3中的$attrs可以同时包括vue2中的属性传递和事件监听器功能。
$attrs 包含 class & style
Vue 2中 class
和 style
attribute 没有被包含在 $attrs
中,在使用 inheritAttrs: false
时会产生副作用,class 和 style不属于 $attrs
,它们仍然会被应用到组件的根元素中。
Vue3中$attrs
包含了所有的 attribute,这使得把它们全部应用到另一个元素上变得更加容易了。
4.其他
生命周期重命名
destroyed
生命周期选项被重命名为 unmounted
,beforeDestroy
生命周期选项被重命名为 beforeUnmount
自定义指令的 API 已更改为与组件生命周期一致
在 Vue 2 中,自定义指令通过使用下列钩子来创建,以对齐元素的生命周期,它们都是可选的:
- bind - 指令绑定到元素后调用。只调用一次。
- inserted - 元素插入父 DOM 后调用。
- update - 当元素更新,但子元素尚未更新时,将调用此钩子。
- componentUpdated - 一旦组件和子级被更新,就会调用这个钩子。
- unbind - 一旦指令被移除,就会调用这个钩子。也只调用一次。
在 Vue 3 中,我们为自定义指令创建了一个更具凝聚力的 API。相较于Vue2,自定义指令API与组件生命周期一致,便于记忆。且新增两个钩子,功能更加强大。
- created** - 新增!在元素的 attribute 或事件监听器被应用之前调用。
- bind → beforeMount
- inserted → mounted
- beforeUpdate:新增!在元素本身被更新之前调用,与组件的生命周期钩子十分相似。
- componentUpdated → updated
- beforeUnmount:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。
- unbind -> unmounted
Transition 的一些 class 被重命名
Vue 2.x 版本
在 Vue 2.1.8 版本之前,每个过渡方向都有两个过渡类:初始状态与激活状态。
在 Vue 2.1.8 版本中,引入了 v-enter-to
来定义 enter 或 leave 变换之间的过渡动画插帧。然而,为了向下兼容,并没有变动 v-enter
类名:
Vue 3.x 版本
为了更加明确易读,我们现在将这些初始状态重命名为:
-
<transition-group>
不再默认渲染包裹元素在 Vue 2.x 中,
<transition-group>
像其它自定义组件一样,需要一个根元素。默认的根元素是一个<span>
,但可以通过tag
attribute 定制。在 Vue 3.x 中,有了Fragments片段的支持,因此组件不再需要根节点。所以,
<transition-group>
不再默认渲染根节点。如果需要可以使用tag
属性定制。
5.被移除的APIs
按键修饰符
以下是变更的简要总结:
- 不再支持使用数字 (即键码) 作为
v-on
修饰符 - 不再支持
config.keyCodes
Vue 3.x 建议对任何要用作修饰符的键使用 kebab-cased (短横线) 名称。
事件API
在 Vue 2.x 中,Vue 实例可用于触发由事件触发器 API 通过指令式方式添加的处理函数 ($on
,$off
和 $once
)。这可以用于创建一个事件总线,以创建在整个应用中可用的全局事件监听器。
在 Vue 3.x 中,事件总线模式可以被替换为使用外部的、实现了事件触发器接口的库,例如 mitt 或 tiny-emitter。
在绝大多数情况下,不鼓励使用全局的事件总线在组件之间进行通信。虽然在短期内往往是最简单的解决方案,但从长期来看,它维护起来总是令人头疼。
过滤器 (filter)
在 Vue 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。
内联模板 attribute
详细文档请参考:v3-migration.vuejs.org/zh/breaking…
$children 实例 property
在 Vue 3.x 中,$children
property 已被移除,且不再支持。如果你需要访问子组件实例,我们建议使用模板引用(ref) 。
propsData选项
Vue 2.x中,propsData 选项用于创建 Vue 实例的过程中传入 prop。
Vue 3.x中,移除propsData,当应用的根组件需要传入prop时,可以使用 createApp
的第二个参数。
移除$destroy实例方法。
用户不应该再手动管理单个 Vue 组件的生命周期。
移除set和delete函数
全局函数 set 和 delete以及实例方法 set和set和 set和delete。基于代理的变化检测已经不再需要它们了。
三、TypeScript 与组合式 API
像 TypeScript 这样的类型系统可以在编译时通过静态分析检测出很多常见错误。这减少了生产环境中的运行时错误,也让大型项目在重构时更加容易。通过 IDE 中基于类型的自动补全,TypeScript 还改善了开发体验和效率。
Vue3.x 本身就是用 TypeScript 编写的,并对 TypeScript 提供了一等公民的支持。所有的 Vue 官方库都自带了类型声明文件,开箱即用。
1.为组件的 props 标注类型
当使用 <script setup>
时,defineProps()
宏函数支持从它的参数中推导类型,以下是基于类型的声明和运行时声明,可以择一使用:
基于运行时声明,传递给 defineProps()
的参数会作为运行时的 props
选项使用:
<script setup lang="ts">
const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
</script>
基于类型声明,通过泛型参数来定义 props 的类型通常更直接:
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
将 props 的类型移入一个单独的接口中,并通过 withDefaults
编译器宏解决声明默认值:
<script setup lang="ts">
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
</script>
2.为组件的 emits 标注类型
在 <script setup>
中,emit
函数的类型标注也可以通过运行时声明或是类型声明进行:
<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])
// 基于选项
const emit = defineEmits({
change: (id: number) => {
// 返回 `true` 或 `false`
// 表明验证通过或失败
},
update: (value: string) => {
// 返回 `true` 或 `false`
// 表明验证通过或失败
}
})
// 基于类型
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>
类型参数可以是以下的一种:
- 一个可调用的函数类型,但是写作一个包含调用签名的类型字面量。它将被用作返回的
emit
函数的类型。 - 一个类型字面量,其中键是事件名称,值是数组或元组类型,表示事件的附加接受参数。上面的示例使用了具名元组,因此每个参数都可以有一个显式的名称。
3.为 ref() 标注类型
ref 会根据初始化时的值推导其类型:
import { ref } from 'vue'
// 推导出的类型:Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
可以通过使用 Ref
这个类型给ref 内的值指定一个更复杂的类型:
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
year.value = 2020 // 成功!
在调用 ref()
时传入一个泛型参数,可以覆盖默认的推导行为:
const year = ref<string | number>('2020')
year.value = 2020 // 成功!
如果指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含 undefined
的联合类型:
// 推导得到的类型:Ref<number | undefined>
const n = ref<number>()
4.为 reactive() 标注类型
reactive()
会隐式地从它的参数中推导类型:
import { reactive } from 'vue'
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue 3 指引' })
可以使用接口,显式地标注一个 reactive
变量的类型:
import { reactive } from 'vue'
interface Book {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue 3 指引' })
5.为 computed() 标注类型
computed()
会自动从其计算函数的返回值上推导出类型:
import { ref, computed } from 'vue'
const count = ref(0)
// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
可以通过泛型参数显式指定类型:
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})
6.为事件处理函数标注类型
在处理原生 DOM 事件时,应该为我们传递给事件处理函数的参数正确地标注类型。没有类型标注时,这个 event
参数会隐式地标注为 any
类型。这也会在 tsconfig.json
中配置了 "strict": true
或 "noImplicitAny": true
时报出一个 TS 错误。
<script setup lang="ts">
function handleChange(event) {
// `event` 隐式地标注为 `any` 类型
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>
建议显式地为事件处理函数的参数标注类型。此外,你在访问 event
上的属性时可能需要使用类型断言:
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
7.为 provide / inject 标注类型
provide 和 inject 通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 InjectionKey
接口,它是一个继承自 Symbol
的泛型类型,可以用来在提供者和消费者之间同步注入值的类型:
import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'
const key = Symbol() as InjectionKey<string>
provide(key, 'foo') // 若提供的是非字符串值会导致错误
const foo = inject(key) // foo 的类型:string | undefined
当使用字符串注入 key 时,注入值的类型是 unknown
,需要通过泛型参数显式声明。注意注入的值仍然可以是 undefined
,因为无法保证提供者一定会在运行时 provide 这个值。:
const foo = inject<string>('foo') // 类型:string | undefined
当提供了一个默认值后,这个 undefined
类型就可以被移除:
const foo = inject<string>('foo', 'bar') // 类型:string
如果你确定该值将始终被提供,则还可以强制转换该值:
const foo = inject('foo') as string
8.为模板引用标注类型
模板引用需要通过一个显式指定的泛型参数和一个初始值 null
来创建:
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const el = ref<HTMLInputElement | null>(null)
onMounted(() => {
el.value?.focus()
})
</script>
<template>
<input ref="el" />
</template>
注意为了严格的类型安全,有必要在访问 el.value
时使用可选链或类型守卫。这是因为直到组件被挂载前,这个 ref 的值都是初始的 null
,并且在由于 v-if
的行为将引用的元素卸载时也可以被设置为 null
。
9.为组件模板引用标注类型
有时,可能需要为一个子组件添加一个模板引用,以便调用它公开的方法。举例来说,我们有一个 MyModal
子组件,它有一个打开模态框的方法:
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
defineExpose({
open
})
</script>
为了获取 MyModal
的类型,我们首先需要通过 typeof
得到其类型,再使用 TypeScript 内置的 InstanceType
工具类型来获取其实例类型:
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
modal.value?.open()
}
</script>
如果组件的具体类型无法获得,或者你并不关心组件的具体类型,那么可以使用 ComponentPublicInstance
。这只会包含所有组件都共享的属性,比如 $el
。
import { ref } from 'vue'
import type { ComponentPublicInstance } from 'vue'
const child = ref<ComponentPublicInstance | null>(null)
以上就是全部总结,如果错误,还请指教!!
转载自:https://juejin.cn/post/7376111275162468391