如何为具有泛型类型的引用赋值?

Tin*_*ger 6 typescript vue.js

我正在使用 TypeScript 编写一个 Vue 可组合项。

它接受一个泛型类型T和一个 paramter path,并返回一个documentref 。

我几乎已经让它工作了,但是每当我尝试为documentref 分配一个值时,它都会抛出如下错误:

Type '{ id: string; }' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to '{ id: string; }'.ts(2322)
Run Code Online (Sandbox Code Playgroud)

这是可组合项的精简版本:

import { ref, Ref } from "vue";
import { projectFirestore } from "@/firebase/config";
import { doc, onSnapshot } from "firebase/firestore";

const getDocument = <T>(
  path: string
): {
  document: Ref<T | null>;
} => {
  const document = ref(null) as Ref<T | null>;
  const docRef = doc(projectFirestore, path);
  onSnapshot(docRef, (doc) => {
    document.value = { //<-- Error here on "document.value"
      ...doc.data(),
      id: doc.id,
    };
  });
  return { document };
};

export default getDocument;
Run Code Online (Sandbox Code Playgroud)

无论我分配给什么document.value(字符串、空对象等),它总是给出类似的错误,说它不能分配给 type T

我知道错误告诉我类型T可以是任何东西,因此分配这些东西是不安全的,因为这些东西的类型可能与 type 不兼容T

但我该如何解决这个问题呢?我可以以某种方式告诉 TypeScript 该类型T与其他类型兼容吗?

Jef*_*ica 3

T仅在您的返回类型中使用。您的泛型声明生成的 Ref 是调用者的T无边界选择。

\n

即使您可以指定T extends { id: string }或您的返回类型是T & { id: string }\xe2\x80\x94 并且您可以,使用该语法 \xe2\x80\x94 您必须在某些时候击败强类型,因为在运行时getDocument永远不会对 T 有足够的了解,可以断言它将doc.data()与调用推断的特定 T 兼容getDocument

\n

我会选择以下三个选项之一:

\n
    \n
  • 删除 generic 和 return Ref<{id: string} | null>,它是类型安全的,但会强制您使用ref["key"]符号来检查任意未知的键。
  • \n
  • 保持通用性并相信您的调用会getDocument指定正确的类型,因为您的代码引入错误的可能性比 Firestore 为您提供意外数据类型的可能性要大得多。您需要使用as any来向 TypeScript 表明您在分配 时故意放弃类型安全性document.value
  • \n
  • 使用与路径一起传递的类型谓词。getDocument谓词是一个函数,它接受单个 arg 并返回其 arg is T,您可以检查它onSnapshot以确保您document可以安全地转换为 T 或因设计的缓解/错误而失败。这也限制了 T,因此您的泛型绝对不能是任何类型。请参阅下面的示例。
  • \n
\n
\n
const getDocument = <T>(\n  path: string,\n  docPredicate: (doc: any) => doc is T,  // accept predicate\n): {\n  document: Ref<T & {id: string} | null>;\n} => {\n  const document = ref(null) as Ref<T & {id: string} | null>;\n  const docRef = doc(projectFirestore, path);\n  onSnapshot(docRef, (doc) => {\n    const data = doc.data(); // extract to local variable\n    if(docPredicate(data)) { // convince TS data is type T\n      document.value = {\n        ...data,             // integrate as expected\n        id: doc.id,\n      };\n    }\n  });\n  return { document };\n};\n\ninterface Foo { a: string, b: number }\nfunction isFoo(doc: any): doc is Foo {\n  // TODO: check that doc has {a: string, b: number}\n  return true;\n}\n\nconst fooDoc = getDocument("bar", isFoo);\nif (fooDoc.document.value) {\n  console.log(fooDoc.document.value.a);\n  console.log(fooDoc.document.value.id);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

操场前=>操场后

\n