我正在使用 TypeScript 编写一个 Vue 可组合项。
它接受一个泛型类型T
和一个 paramter path
,并返回一个document
ref 。
我几乎已经让它工作了,但是每当我尝试为document
ref 分配一个值时,它都会抛出如下错误:
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
与其他类型兼容吗?
T
仅在您的返回类型中使用。您的泛型声明生成的 Ref 是调用者的T
无边界选择。
即使您可以指定T extends { id: string }
或您的返回类型是T & { id: string }
\xe2\x80\x94 并且您可以,使用该语法 \xe2\x80\x94 您必须在某些时候击败强类型,因为在运行时getDocument
永远不会对 T 有足够的了解,可以断言它将doc.data()
与调用推断的特定 T 兼容getDocument
。
我会选择以下三个选项之一:
\nRef<{id: string} | null>
,它是类型安全的,但会强制您使用ref["key"]
符号来检查任意未知的键。getDocument
指定正确的类型,因为您的代码引入错误的可能性比 Firestore 为您提供意外数据类型的可能性要大得多。您需要使用as any
来向 TypeScript 表明您在分配 时故意放弃类型安全性document.value
。getDocument
谓词是一个函数,它接受单个 arg 并返回其 arg is T
,您可以检查它onSnapshot
以确保您document
可以安全地转换为 T 或因设计的缓解/错误而失败。这也限制了 T,因此您的泛型绝对不能是任何类型。请参阅下面的示例。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