如何解构一个可组合的?

Posted

技术标签:

【中文标题】如何解构一个可组合的?【英文标题】:How to destructure a composable? 【发布时间】:2022-01-14 19:15:12 【问题描述】:

我在使用 TypeScript 从可组合的 Vue 3 中解构引用时遇到问题。

在下面的示例中,我试图从 getDocument 可组合中解构 documenterror 引用。

第一次尝试:

在第一次尝试中,我尝试使用 [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>; '

【问题讨论】:

每首歌曲或文档本身是否应该有 idtitle 属性? 歌曲数组中的每首歌曲都有一个idtitleartist。所以我曾经使用 [key: string]: string | number 的索引签名来涵盖所有这些内容,以及以后可能添加的其他内容。文档本身也有一个id 和一个title 我建议然后添加这些属性。 id: string, title: string, artist: string, [key: string]: string | number[]。这样,TS 就可以准确地知道每首歌曲变量的内容。 @2pichar Ok 已经尝试添加,但不幸的是它不会改变任何错误。从可组合对象中解构引用仍然存在同样的问题。 正如我在之前的回答中提到的,不要指定返回类型,除非你真的需要,让它们从你返回的内容中推断出来。问题是任何 T 都不会是 Ref&lt;T | null&gt;,因为如果 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&lt;T | null&gt;;document 转换为Ref&lt;T | null&gt;

这也消除了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]()

[JavaScript]解构赋值详解

Common Lisp:在 first、rest、last 中解构列表(如 Python 可迭代解包)

python内置函数封装解构

解构可为空的对象