Typescript 将 class 缩小为受歧视的联合
Typescript narrow class into a discriminated union
我很难将 class 的实例缩小到它的可区分联合。
我有以下歧视联盟:
interface ILoadableLoading<T> {
state: "Loading";
id: number;
}
interface ILoadableLoaded<T> {
state: "Loaded";
id: number;
item: T;
}
interface ILoadableErrored<T> {
state: "Error";
id: number;
error: string;
}
export type ILoadableDiscriminated<T> =
| ILoadableLoading<T>
| ILoadableLoaded<T>
| ILoadableErrored<T>;
type ILoadableState<T> = ILoadableDiscriminated<T>["state"];
以及以下class:
class Loadable<T> {
state: ILoadableState<T> = "Loading";
id: number = 0;
item?: T | undefined;
error?: string | undefined;
}
现在我如何将那个 class 的实例缩小到其各自的 ILoadableDiscriminated<T>
并保持某种类型安全(不使用任何类型)?
例如我有以下创建方法,并希望 return 受歧视的联合:
unction createLoadable<T>(someState: boolean): ILoadableDiscriminated<T> {
var loadable = new Loadable<T>();
if (someState) {
loadable.state = "Error";
loadable.error = "Some Error";
// Would like to remove this cast, as it should narrow it out from state + defined error above
return loadable as ILoadableErrored<T>;
}
if (loadable.state === "Loading") {
// Would like to remove this cast, as it should narrow it from state;
return loadable as ILoadableLoading<T>;
}
if (loadable.state === "Loaded" && loadable.item) {
// Would like to remove this cast, as it should narrow it from state;
return loadable as ILoadableLoaded<T>;
}
throw new Error("Some Error");
}
示例可在以下位置找到:https://codesandbox.io/embed/weathered-frog-bjuh0
文件:src/DiscriminatedUnion.ts
问题是 Loadable<T>
和定义的接口之间没有关系,这将保证函数 createLoadable()
在你 return 项目之前将每个 属性 设置为正确的状态.例如,Loadable<string>
可能具有以下值:
var loadable = new Loadable<string>();
loadable.state = "Error";
lodable.item = "Result text.";
return loadable;
以上不适合任何接口,但它是有效的 Loadable
实例。
我的方法如下:
简化界面,只有一个必须是通用的:
interface ILoadableLoading {
state: "Loading";
id: number;
}
interface ILoadableLoaded<T> {
state: "Loaded";
id: number;
item: T;
}
interface ILoadableErrored {
state: "Error";
id: number;
error: string;
}
export type ILoadableDiscriminated<T> =
| ILoadableLoading
| ILoadableLoaded<T>
| ILoadableErrored;
type ILoadableState<T> = ILoadableDiscriminated<T>["state"];
为每个接口创建单独的 class,以确保创建的对象符合接口定义:
class LoadableLoading implements ILoadableLoading {
state: "Loading" = "Loading";
id: number = 0;
}
class LoadableLoaded<T> implements ILoadableLoaded<T> {
constructor(public item: T){}
state: "Loaded" = "Loaded";
id: number = 0;
}
class LoadableErrored implements ILoadableErrored {
constructor(public error: string){}
state: "Error" = "Error";
id: number = 0;
}
然后我们可以使用带重载的函数,说明意图:
function createLoadable<T>(someState: true, state: ILoadableState<T>, item?: T): ILoadableErrored;
function createLoadable<T>(someState: false, state: "Loading", item?: T): ILoadableLoading;
function createLoadable<T>(someState: false, state: "Loaded", item?: T): ILoadableLoaded<T>;
function createLoadable<T>(someState: boolean, state?: ILoadableState<T>, item?: T): ILoadableDiscriminated<T> {
if (someState) {
return new LoadableErrored("Some error");
}
if (state === "Loading") {
// Would like to remove this cast, as it hsould figure it out from state;
return new LoadableLoading();
}
if (state === "Loaded" && item) {
// Would like to remove this cast, as it hsould figure it out from state;
return new LoadableLoaded(item);
}
throw new Error("Some Error");
}
最后,根据你给createLoadable()
函数输入的参数,类型会是return类型会自动区分:
const lodableError = createLoadable<string>(true, "Loading");
console.log(lodableError.error);
const lodableLoading = createLoadable<string>(false, "Loading");
console.log("Loading");
const loadableLoaded = createLoadable<string>(false, "Loaded", "MyResponse");
console.log(loadableLoaded.item)
请注意,Typescript 编译器的参数重载状态意图,但您需要确保函数体中的代码执行您声明的内容。
我很难将 class 的实例缩小到它的可区分联合。
我有以下歧视联盟:
interface ILoadableLoading<T> {
state: "Loading";
id: number;
}
interface ILoadableLoaded<T> {
state: "Loaded";
id: number;
item: T;
}
interface ILoadableErrored<T> {
state: "Error";
id: number;
error: string;
}
export type ILoadableDiscriminated<T> =
| ILoadableLoading<T>
| ILoadableLoaded<T>
| ILoadableErrored<T>;
type ILoadableState<T> = ILoadableDiscriminated<T>["state"];
以及以下class:
class Loadable<T> {
state: ILoadableState<T> = "Loading";
id: number = 0;
item?: T | undefined;
error?: string | undefined;
}
现在我如何将那个 class 的实例缩小到其各自的 ILoadableDiscriminated<T>
并保持某种类型安全(不使用任何类型)?
例如我有以下创建方法,并希望 return 受歧视的联合:
unction createLoadable<T>(someState: boolean): ILoadableDiscriminated<T> {
var loadable = new Loadable<T>();
if (someState) {
loadable.state = "Error";
loadable.error = "Some Error";
// Would like to remove this cast, as it should narrow it out from state + defined error above
return loadable as ILoadableErrored<T>;
}
if (loadable.state === "Loading") {
// Would like to remove this cast, as it should narrow it from state;
return loadable as ILoadableLoading<T>;
}
if (loadable.state === "Loaded" && loadable.item) {
// Would like to remove this cast, as it should narrow it from state;
return loadable as ILoadableLoaded<T>;
}
throw new Error("Some Error");
}
示例可在以下位置找到:https://codesandbox.io/embed/weathered-frog-bjuh0
文件:src/DiscriminatedUnion.ts
问题是 Loadable<T>
和定义的接口之间没有关系,这将保证函数 createLoadable()
在你 return 项目之前将每个 属性 设置为正确的状态.例如,Loadable<string>
可能具有以下值:
var loadable = new Loadable<string>();
loadable.state = "Error";
lodable.item = "Result text.";
return loadable;
以上不适合任何接口,但它是有效的 Loadable
实例。
我的方法如下:
简化界面,只有一个必须是通用的:
interface ILoadableLoading {
state: "Loading";
id: number;
}
interface ILoadableLoaded<T> {
state: "Loaded";
id: number;
item: T;
}
interface ILoadableErrored {
state: "Error";
id: number;
error: string;
}
export type ILoadableDiscriminated<T> =
| ILoadableLoading
| ILoadableLoaded<T>
| ILoadableErrored;
type ILoadableState<T> = ILoadableDiscriminated<T>["state"];
为每个接口创建单独的 class,以确保创建的对象符合接口定义:
class LoadableLoading implements ILoadableLoading {
state: "Loading" = "Loading";
id: number = 0;
}
class LoadableLoaded<T> implements ILoadableLoaded<T> {
constructor(public item: T){}
state: "Loaded" = "Loaded";
id: number = 0;
}
class LoadableErrored implements ILoadableErrored {
constructor(public error: string){}
state: "Error" = "Error";
id: number = 0;
}
然后我们可以使用带重载的函数,说明意图:
function createLoadable<T>(someState: true, state: ILoadableState<T>, item?: T): ILoadableErrored;
function createLoadable<T>(someState: false, state: "Loading", item?: T): ILoadableLoading;
function createLoadable<T>(someState: false, state: "Loaded", item?: T): ILoadableLoaded<T>;
function createLoadable<T>(someState: boolean, state?: ILoadableState<T>, item?: T): ILoadableDiscriminated<T> {
if (someState) {
return new LoadableErrored("Some error");
}
if (state === "Loading") {
// Would like to remove this cast, as it hsould figure it out from state;
return new LoadableLoading();
}
if (state === "Loaded" && item) {
// Would like to remove this cast, as it hsould figure it out from state;
return new LoadableLoaded(item);
}
throw new Error("Some Error");
}
最后,根据你给createLoadable()
函数输入的参数,类型会是return类型会自动区分:
const lodableError = createLoadable<string>(true, "Loading");
console.log(lodableError.error);
const lodableLoading = createLoadable<string>(false, "Loading");
console.log("Loading");
const loadableLoaded = createLoadable<string>(false, "Loaded", "MyResponse");
console.log(loadableLoaded.item)
请注意,Typescript 编译器的参数重载状态意图,但您需要确保函数体中的代码执行您声明的内容。