如何为接受参数的函数编写类型保护,该参数是 Partial<T> 和对象的交集?
How do I write a typeguard for a function that takes an argument that is an intersection of a Partial<T> and object?
我正在使用 Directus API 和 Typescript。使用 Typescript,它的 API 调用函数 return 部分实体(例如 PartialItem<Book>
),所以我在继续传递数据之前努力检查是否存在所需的属性。
但是,我仍然遇到 Typescript 类型错误,这些错误与 Directus 具体无关,但可能与我处理部分类型的方式有关。
这是一个使用纯 Typescript (and on TS Playground) 的简化示例:
interface Book {
id: string;
title?: string;
author?: string;
pages?: number;
}
class Library {
static books: Book[] = [];
static addBook(data: Partial<Book> & {id: string}) {
this.books.push(data);
}
}
function fetchBooks(): Promise<Partial<Book>[]> {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve([
{id: "1234",title:"Nineteen Eighty-Four"},
{id: "2345", title: "The Great Gatsby", author: "F. Scott Fitzgerald"}
])
},1000)
})
}
function bookLoadingFunction() {
fetchBooks().then(books=>{
books.map(b=>{
if(!b.id){
// Handle the fact that the API is missing ID
}else{
Library.addBook(b); // * COMPILE ERROR HERE *
}
})
})
}
即使在检查之后,编译器似乎也无法推断出 b.id
已定义。我收到以下错误:
Argument of type 'Partial<Book>' is not assignable to parameter of type 'Partial<Book> & { id: string; }'.
Type 'Partial<Book>' is not assignable to type '{ id: string; }'.
Types of property 'id' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
这是编译器的限制,还是存在 b.id
确实仍然可以是 undefined
的边缘情况?有没有办法让编译器满意而不会失去类型安全性?
我知道以下内容会使错误消失,但它远非理想:
Library.addBook(b as Partial<Book> & {id: string});
谢谢。
您期望通过检查 Partial<Book>
对象的 id
属性 以获取 truthiness, you would get the compiler narrow the object from Partial<Book>
to Partial<Book> & {id: string}
. Unfortunately this is not how narrowing works in TypeScript. See microsoft/TypeScript#16976 的(长期存在的)开放功能请求来支持此类事情.
目前,如果您检查 属性 的值,如 b.id
,它只会缩小 属性 b.id
本身的类型,而不是 b.id
的类型包含对象 b
... 好吧,除非 b
完全属于 discriminated union type and id
is its discriminant property. But Partial<Book>
is not a union,更不用说被歧视的对象了。好吧。
以下是我能想到的解决方法。一种是通过 object spreading:
之类的方式从缩小的 属性 和对象的其余部分重新组装对象
if (!b.id) { } else {
const obj = { ...b, id: b.id };
/* const obj: {
id: string;
title?: string | undefined;
author?: string | undefined;
pages?: number | undefined;
} */
Library.addBook({ ...b, id: b.id }); // okay
}
可以看到obj
被看作是等价于Partial<Book> & {id: string}
的类型(a.k.a.Book
)。因此,您可以毫无错误地调用 Library.addBook(obj)
。因此,您已经放弃缩小 b
,而是构建一个已经缩小类型的新版本 b
。
如果你不想创建一个本质上等同于旧对象的新对象,你可以放弃检查 if (!b.id) {}
而是编写一个 user-defined type function 需要 b
作为输入和 returns 一个 类型的谓词 说 b
可以缩小取决于结果是 true
还是 false
.例如:
function hasId<T extends { id?: any }>(
x: T
): x is T & Required<Pick<T, "id">> {
return x.id !== undefined
}
hasId()
函数接受一个参数 x
,已知该参数具有 id
属性(或至少一个可选参数),并且 returns 类型谓词 x is T & Required<Pick<T, "id">>
。你可以看到它的实际效果:
if (!hasId(b)) { } else {
b // b: Partial<Book> & Required<Pick<Partial<Book>, "id">>
Library.addBook(b); // okay
}
在 else
子句中,hasId(b)
返回了 true
,这意味着 b
已缩小为 Partial<Book> & Required<Pick<Partial<Book>, "id">>
。这也等同于 Book
(Required<Pick<Partial<Book>, "id">>
类型很难看,是根据 Required<T>
, Pick<T, K>
and Partial<T>
实用程序类型编写的,但如果你仔细研究它,你会发现它等同于 {id: string}
).
所以在这里你决定告诉编译器如何缩小 b
,因为它自己不知道如何这样做。
我正在使用 Directus API 和 Typescript。使用 Typescript,它的 API 调用函数 return 部分实体(例如 PartialItem<Book>
),所以我在继续传递数据之前努力检查是否存在所需的属性。
但是,我仍然遇到 Typescript 类型错误,这些错误与 Directus 具体无关,但可能与我处理部分类型的方式有关。
这是一个使用纯 Typescript (and on TS Playground) 的简化示例:
interface Book {
id: string;
title?: string;
author?: string;
pages?: number;
}
class Library {
static books: Book[] = [];
static addBook(data: Partial<Book> & {id: string}) {
this.books.push(data);
}
}
function fetchBooks(): Promise<Partial<Book>[]> {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve([
{id: "1234",title:"Nineteen Eighty-Four"},
{id: "2345", title: "The Great Gatsby", author: "F. Scott Fitzgerald"}
])
},1000)
})
}
function bookLoadingFunction() {
fetchBooks().then(books=>{
books.map(b=>{
if(!b.id){
// Handle the fact that the API is missing ID
}else{
Library.addBook(b); // * COMPILE ERROR HERE *
}
})
})
}
即使在检查之后,编译器似乎也无法推断出 b.id
已定义。我收到以下错误:
Argument of type 'Partial<Book>' is not assignable to parameter of type 'Partial<Book> & { id: string; }'.
Type 'Partial<Book>' is not assignable to type '{ id: string; }'.
Types of property 'id' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
这是编译器的限制,还是存在 b.id
确实仍然可以是 undefined
的边缘情况?有没有办法让编译器满意而不会失去类型安全性?
我知道以下内容会使错误消失,但它远非理想:
Library.addBook(b as Partial<Book> & {id: string});
谢谢。
您期望通过检查 Partial<Book>
对象的 id
属性 以获取 truthiness, you would get the compiler narrow the object from Partial<Book>
to Partial<Book> & {id: string}
. Unfortunately this is not how narrowing works in TypeScript. See microsoft/TypeScript#16976 的(长期存在的)开放功能请求来支持此类事情.
目前,如果您检查 属性 的值,如 b.id
,它只会缩小 属性 b.id
本身的类型,而不是 b.id
的类型包含对象 b
... 好吧,除非 b
完全属于 discriminated union type and id
is its discriminant property. But Partial<Book>
is not a union,更不用说被歧视的对象了。好吧。
以下是我能想到的解决方法。一种是通过 object spreading:
之类的方式从缩小的 属性 和对象的其余部分重新组装对象if (!b.id) { } else {
const obj = { ...b, id: b.id };
/* const obj: {
id: string;
title?: string | undefined;
author?: string | undefined;
pages?: number | undefined;
} */
Library.addBook({ ...b, id: b.id }); // okay
}
可以看到obj
被看作是等价于Partial<Book> & {id: string}
的类型(a.k.a.Book
)。因此,您可以毫无错误地调用 Library.addBook(obj)
。因此,您已经放弃缩小 b
,而是构建一个已经缩小类型的新版本 b
。
如果你不想创建一个本质上等同于旧对象的新对象,你可以放弃检查 if (!b.id) {}
而是编写一个 user-defined type function 需要 b
作为输入和 returns 一个 类型的谓词 说 b
可以缩小取决于结果是 true
还是 false
.例如:
function hasId<T extends { id?: any }>(
x: T
): x is T & Required<Pick<T, "id">> {
return x.id !== undefined
}
hasId()
函数接受一个参数 x
,已知该参数具有 id
属性(或至少一个可选参数),并且 returns 类型谓词 x is T & Required<Pick<T, "id">>
。你可以看到它的实际效果:
if (!hasId(b)) { } else {
b // b: Partial<Book> & Required<Pick<Partial<Book>, "id">>
Library.addBook(b); // okay
}
在 else
子句中,hasId(b)
返回了 true
,这意味着 b
已缩小为 Partial<Book> & Required<Pick<Partial<Book>, "id">>
。这也等同于 Book
(Required<Pick<Partial<Book>, "id">>
类型很难看,是根据 Required<T>
, Pick<T, K>
and Partial<T>
实用程序类型编写的,但如果你仔细研究它,你会发现它等同于 {id: string}
).
所以在这里你决定告诉编译器如何缩小 b
,因为它自己不知道如何这样做。