[英]How to narrow TypeScript types more elegantly, without using two returns?
假設我有一個對象是ApiErrorType | ApiSuccessType
ApiErrorType | ApiSuccessType
基於下面定義的類型。 這兩種類型有一些相似之處,但timestamp
僅與ApiErrorType
相關,如果isError
為false
則永遠不會出現。
我想通過返回MyErrorType | MySuccessType
的格式化函數將此對象與聯合類型放在一起 MyErrorType | MySuccessType
。 重要的是,下游代碼可以根據isError
的值知道它正在處理這兩種類型中的一種。
我能想到如何實現這一點的唯一方法是讓我的格式化函數有兩個單獨的返回語句,並創建局部變量以避免適用於兩個返回的冗余代碼。
有沒有更優雅的方法來實現這一目標? 更類似於betterFormatter
函數,它實際上不起作用?
interface Base {
readonly isError: boolean;
readonly timestamp?: number;
readonly msg: string;
}
interface ApiErrorType extends Base {
readonly isError: true
readonly timestamp: number;
}
interface ApiSuccessType extends Base {
readonly isError: false;
readonly timestamp: undefined;
}
const errorObj = {
isError: true,
timestamp: 787149920,
msg: "errored"
} as const;
const successObj = {
isError: false,
timestamp: undefined,
msg: "succeeded"
} as const;
interface MyErrorType {
readonly isError: true;
readonly date: Date;
readonly msg: string;
}
interface MySuccessType {
readonly isError: false;
readonly date: undefined;
readonly msg: string;
}
const formatter = (obj: ApiErrorType | ApiSuccessType): MyErrorType | MySuccessType => {
// This version works, but it requires two separate returns, and requires `msg` to be
// assigned to a local variable to be reused in both places. Overall, it's repetative.
const msg = obj.msg.toUpperCase();
if (obj.isError) {
return {
isError: obj.isError,
date: new Date(obj.timestamp),
msg
}
}
return {
isError: obj.isError,
date: undefined,
msg
};
};
// TODO: How could I accomplish this?
const betterFormatter (obj: ApiErrorType | ApiSuccessType): MyErrorType | MySuccessType => {
// This version should have one return, like the below, but I should somehow be able to
// tell TypeScript that the return type is in face `MyErrorType | MySuccessType`.
// As written, `betterFormatter` does not work.
return {
isError: obj.isError,
date: obj.timestamp ? new Date(obj.timestamp) : undefined,
msg: obj.msg.toUpperCase()
};
}
const error = formatter(errorObj);
const success = formatter(successObj);
if (error.isError) {
// We know this is a date
error.date.getDate();
}
if (!success.isError) {
// We know this is undefined
console.log(success.date === undefined);
}
type MyReturnType<T extends ApiErrorType | ApiSuccessType> =
T extends ApiErrorType ? MyErrorType : MySuccessType;
const betterFormatter = <T extends ApiErrorType | ApiSuccessType>(
obj: T
): MyReturnType<T> => {
return {
isError: obj.isError,
date: obj.isError ? new Date(obj.timestamp) : undefined,
msg: obj.msg,
} as MyReturnType<T>;
};
這是通過一次返回來執行此操作的一種方法,但我認為它的可讀性較差。
您可以使用區分聯合類型來更好地定義您的 API 響應:
type APIResponse = | { isError: true; timestamp: Date; msg: string } | { isError: false; msg: string } type MyErrorType = Extract<APIResponse, {isError: true}> type MySuccessType = Extract<APIResponse, {isError: false}> function formatter(obj: APIResponse): MyErrorType | MySuccessType { if (obj.isError) { return {...obj, timestamp: new Date(obj.timestamp), } } return obj as MySuccessType // here there is no need to cast it because TS already know that obj is of type MySuccessType and it won't have the timestamp property }
如果你想玩它:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.