vite3+vue3+ts+pinia + Naive UI 项目实战 —— 使用 ref 引用组件时如何标注类型
接前四篇 《项目创建与初始配置》 《国际化配置》 《数据表格与渲染函数》 《区分运行环境并定义 baseURL》, 本篇来说说在后台项目中遇到的一个小问题及解决办法。
问题阐述
在构建的项目中,有时我们需要在父组件中调用子组件通过 defineExpose()
对外暴露的方法。比如我在做登录页面时,将登录按钮放在父组件中,然后把登录表单封装成了子组件 LoginForm.vue,并在里面定义了登录相关方法 validateFormModel
。示意图如下:
也就是说,当父组件中的登录按钮被点击时,需要触发子组件的 validateFormModel
方法。实现思路即为给子组件 LoginForm
添加一个引用 ref="loginFormRef"
,接着给按钮绑定一个方法 handleSubmit
,然后在方法内通过 loginFormRef.value?.validateFormModel()
调用:
<!-- 父组件,省略部分代码 -->
<script lang="ts" setup>
import { ref } from 'vue'
import LoginForm from './components/LoginForm.vue'
const loginFormRef = ref() // 这里还有问题,下文中解决
function handleSubmit() {
loginFormRef.value?.validateFormModel()
}
</script>
<template>
<n-dialog-provider>
<LoginForm ref="loginFormRef" />
</n-dialog-provider>
<n-button @click="handleSubmit">
登录
</n-button>
</template>
在给 loginFormRef
赋值时,因为 ref()
是没有传入参数的,所以无法直接像 const str = ref('hello')
这样可以通过类型推导得知 str
为字符串类型,然后限制 str
拥有的属性和可以使用的方法。查看 ref
的类型声明:
// node_modules\@vue\reactivity\dist\reactivity.d.ts
export declare function ref<T = any>(): Ref<T | undefined>;
得知我们可以通过指定具体的泛型类型的方式来标注 loginFormRef
的类型。那么问题来了,loginFormRef
指向的 loginForm
是我们自己定义的一个组件,如何获取它的类型呢?
解决办法
查阅 vue3 的官方文档,可以看到下面这样的内容:
依葫芦画瓢,我就知道在定义
loginFormRef
时可以写成
const loginFormRef = ref<InstanceType<typeof LoginForm> | null>()
如此,ts 就会知道 loginFormRef
的具体类型,会给我们提供提示与检查了:
虽然知道了解决办法,为了知其所以然,我们再来看看所谓的 ts 内置的工具类型 InstanceType
到底是啥?
InstanceType
ts 为我们封装了一些工具类型,它们都是可以全局使用的,所以无需进行导入,InstanceType<Type>
就是其中之一。ts 官方文档的介绍如下:
Constructs a type consisting of the instance type of a constructor function in Type.
直译的话有点绕,我大概理解为,构造一个类型,这个类型是由类型为 Type
的构造函数的实例类型组成的。
借助 infer 手写实现一个 InstanceType
我们可以自己实现一个 InstanceType
叫做 MyInstanceType
,这其实也是 ts 的一个类型体操了:
type MyInstanceType<T extends new (...args: any[]) => any> = T extends new (
...args: any[]
) => infer Return
? Return
: never
注释:
<T extends new (...args: any[]) => any>
:是使用extends
对泛型类型T
做一个约束,让其必须为构造函数;- 等号右边的
T extends new (...args: any[]) => infer Return ? Return : never
是使用了infer
关键字推断出构造函数的返回值,因为等号左边已经对T
进行了约束,所以T extends new (...args: any[]) => any ? any : never
的结果必然是返回any
,那么我们就可以用infer Return
来推断返回值的类型,然后返回Return
。关于infer
的使用可详见文档。
总结归纳
现在我们知道了,InstanceType<Type>
只要给 Type
传入一个构造函数的类型,就能得到该构造函数的实例的类型,那么 LoginForm
明明是我们定义的组件,为啥 typeof LoginForm
也能作为 Type
传入呢?
其实在 vue 中,我们使用了组件,比如 <LoginForm />
,就可以看成是 LoginForm
创建了一个实例,在 setup 语法糖出现之前,我们创建组件是这样写的:
export default defineComponent({
// ...
setup() {}
})
即组件的类型其实都是 DefineComponent
,如果查看源码会发现 DefineComponent
的定义相对比较复杂:
// node_modules\@vue\runtime-core\dist\runtime-core.d.ts
export declare type DefineComponent<PropsOrPropOptions = {}, RawBindings = {}, D = {}, C extends ComputedOptions = ComputedOptions, M extends MethodOptions = MethodOptions, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, EE extends string = string, PP = PublicProps, Props = Readonly<PropsOrPropOptions extends ComponentPropsOptions ? ExtractPropTypes<PropsOrPropOptions> : PropsOrPropOptions> & ({} extends E ? {} : EmitsToProps<E>), Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>> = ComponentPublicInstanceConstructor<CreateComponentPublicInstance<Props, RawBindings, D, C, M, Mixin, Extends, E, PP & Props, Defaults, true> & Props> & ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE, Defaults> & PP;
最终是等于几个类型的联合类型,其中有一个是 ComponentPublicInstanceConstructor
:
// node_modules\@vue\runtime-core\dist\runtime-core.d.ts
declare type ComponentPublicInstanceConstructor<T extends ComponentPublicInstance<Props, RawBindings, D, C, M> = ComponentPublicInstance<any>, Props = any, RawBindings = any, D = any, C extends ComputedOptions = ComputedOptions, M extends MethodOptions = MethodOptions> = {
__isFragment?: never;
__isTeleport?: never;
__isSuspense?: never;
new(...args: any[]): T;
};
而 ComponentPublicInstanceConstructor
的类型声明里可以看到有构造签名 new(...args: any[]): T;
所以我们可以把 typeof LoginForm
作为泛型的具体类型传给 InstanceType<Type>
,从而拿到 LoginForm
的具体类型。
转载自:https://juejin.cn/post/7196926405250252861