如何解构一个可组合的?
Posted
技术标签:
【中文标题】如何解构一个可组合的?【英文标题】:How to destructure a composable? 【发布时间】: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
。
【参考方案1】:
我找到了两个解决方案。
第一个解决方案
正如 @EstusFlask 在 cmets 中提到的,我可以删除返回类型。
在这种情况下,可组合的 beomes:
import ref from "vue";
const getDocument = <T>(docId: string) =>
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 ;
;
export default getDocument;
第二个解决方案
我发现的另一种方法是保留返回类型,但使用const document = ref(null) as Ref<T | null>;
将document
转换为Ref<T | null>
。
这也消除了document
上的错误,并具有满足 ESLint 的额外好处,当复杂函数没有返回类型时会抱怨。
import ref, Ref from "vue";
const getDocument = <T>(
docId: string
):
document: Ref<T | null>;
error: Ref<string | null>;
=>
const document = ref(null) as Ref<T | 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;
【讨论】:
以上是关于如何解构一个可组合的?的主要内容,如果未能解决你的问题,请参考以下文章
TypeError:无效的解构不可迭代实例的尝试。为了可迭代,非数组对象必须有一个 [Symbol.iterator]()