【发布时间】:2022-01-14 19:15:12
【问题描述】:
我在使用 TypeScript 从可组合的 Vue 3 中解构引用时遇到问题。
在下面的示例中,我试图从 getDocument 可组合中解构 document 和 error 引用。
第一次尝试:
在第一次尝试中,我尝试使用{ [key: string]: string | undefined } 的索引签名为document ref 提供一个非常通用的类型。我认为这将允许我返回任何形状的文档而不会出错。 Spolier:我错了,它在 Vue 模板中产生了很多错误。
这是 Vue 3 SFC:
<template>
<div v-if="document">
<img :src="document.coverUrl" />
<div v-if="!document.songs.length">No songs</div> //Error #1 detailed below
<div v-for="song in document.songs" :key="song.id">{{ song.title }}</div> //Errors #2, #3, and #4 detailed below
<div v-if="error">{{ error }}</div>
</div>
</template>
<script setup lang="ts">
import getDocument from "@/composables/getDocument";
const { error, document } = getDocument("9Tn765FhtBa2");
</script>
这是 getDocument 可组合(带有 Pseduo 代码的精简版):
import { ref, Ref } from "vue";
const getDocument = (
docId: string
): {
document: Ref<{ [key: string]: string | undefined } | null>;
error: Ref<string | null>;
} => {
const document = ref<{ [key: string]: string | undefined } | null>(null);
const error = ref<string | null>(null);
const docFromDatabase = getdocFromDatabase(docId);
document.value = {
...docFromDatabase.data(),
id: docFromDatabase.id,
};
error.value = "Some error text here";
return { document, error };
};
export default getDocument;
在此尝试期间,它在 Vue SFC 的模板中产生了许多错误:
-
v-if="!document.songs.length"的错误为Object is possibly 'undefined'. -
v-for="song in document.songs"的错误为The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'string'.ts(2407) -
:key="song.id"的错误为Property 'id' does not exist on type 'string'.ts(2339) -
{{ song.title }}的错误为Property 'title' does not exist on type 'string'.ts(2339)
第二次尝试:
在第二次尝试中,我尝试创建 Playlist 接口并将其作为泛型类型传递给可组合函数。这解决了所有 SFC 模板错误,但在可组合项中创建了一个不同的错误:
<template>
<div v-if="document">
<img :src="document.coverUrl" />
<div v-if="!document.songs.length">No songs</div>
<div v-for="song in document.songs" :key="song.id">{{ song.title }}</div>
<div v-if="error">{{ error }}</div>
</div>
</template>
<script setup lang="ts">
import getDocument from "@/composables/getDocument";
interface Playlist {
coverUrl: string;
createdAt: string;
description: string;
filePath: string;
songs: { [key: string]: string | number }[];
title: string;
userId: string;
userName: string;
id: string;
}
const { error, document } = getDocument<Playlist>("9Tn765FhtBa2");
</script>
这是新的getDocument 可组合:
import { ref, Ref } from "vue";
const getDocument = <T>(
docId: string
): {
document: Ref<T | null>;
error: Ref<string | null>;
} => {
const document = ref<T | null>(null);
const error = ref<string | null>(null);
const docFromDatabase = getdocFromDatabase(docId);
document.value = {
...docFromDatabase.data(),
id: docFromDatabase.id,
};
error.value = "Some error text here";
return { document, error }; // Error on "document" is detailed below
};
export default getDocument;
这解决了 SFC 中的所有错误。但是现在可组合在返回的document ref 的底部有一个非常复杂的错误:
Type 'Ref<UnwrapRef<T> | null>' is not assignable to type 'Ref<T | null>'.
Type 'UnwrapRef<T> | null' is not assignable to type 'T | null'.
Type 'UnwrapRef<T>' is not assignable to type 'T | null'.
Type 'unknown' is not assignable to type 'T | null'.
Type 'unknown' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.ts(2322)
getDocumentT.ts(6, 3): The expected type comes from property 'document' which is declared here on type '{ document: Ref<T | null>; error: Ref<string | null>; }'
【问题讨论】:
-
每首歌曲或文档本身是否应该有
id和title属性? -
歌曲数组中的每首歌曲都有一个
id、title和artist。所以我曾经使用{ [key: string]: string | number }的索引签名来涵盖所有这些内容,以及以后可能添加的其他内容。文档本身也有一个id和一个title。 -
我建议然后添加这些属性。
{id: string, title: string, artist: string, [key: string]: string | number}[]。这样,TS 就可以准确地知道每首歌曲变量的内容。 -
@2pichar Ok 已经尝试添加,但不幸的是它不会改变任何错误。从可组合对象中解构引用仍然存在同样的问题。
-
正如我在之前的回答中提到的,不要指定返回类型,除非你真的需要,让它们从你返回的内容中推断出来。问题是任何 T 都不会是
Ref<T | null>,因为如果 T 已经是一个 ref,它的行为会有所不同(这就是 UnwrapRef 出现的原因)。如果 T 更具体,请这样做,您可能会避免这些问题。第一个问题是文档不是真的为空,它立即被分配了document.value =。第二个问题是 T 实际上是具有id属性的特定对象,而不仅仅是任何类型。尽可能使用T extends。
标签: typescript vue.js