Typescript 将窄类键入一个有区别的并集

Typescript 将窄类键入一个有区别的并集,typescript,discriminated-union,Typescript,Discriminated Union,我很难将一个类的实例缩小到其受歧视的联合体 我有以下受歧视的工会: interface ILoadableLoading<T> { state: "Loading"; id: number; } interface ILoadableLoaded<T> { state: "Loaded"; id: number; item: T; } interface ILoadableErrored<T> { state: "Error";

我很难将一个类的实例缩小到其受歧视的联合体

我有以下受歧视的工会:

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"];
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");
}
接口ILoadableLoading{
国家:“装载”;
id:编号;
}
接口ILoadableLoaded{
声明:“已加载”;
id:编号;
项目:T;
}
接口ILoadableErrored{
声明:“错误”;
id:编号;
错误:字符串;
}
导出类型ILoadableDiscriminated=
|可适应负荷
|我可接受
|我犯了错误;
类型ILoadableState=ILoadableDiscriminated[“状态”];
以及以下类别:

class Loadable<T> {
  state: ILoadableState<T> = "Loading";
  id: number = 0;
  item?: T | undefined;
  error?: string | undefined;
}
类可加载{
状态:ILoadableState=“正在加载”;
id:number=0;
项目?:T |未定义;
错误?:字符串|未定义;
}
现在,如何将该类的实例缩小到其各自的
ILoadableDiscriminated
联合,以保持某种类型安全性(不使用任何类型)

例如,我有以下创建方法,并希望返回受歧视的联合:

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"];
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");
}
unction createLoadable(someState:boolean):ILoadableDiscriminated{
var loadable=new loadable();
如果(某个州){
loadable.state=“Error”;
loadable.error=“某些错误”;
//希望删除此强制转换,因为它应该将其从上面的state+定义的错误中缩小
返回可加载的ILoadableErrored;
}
如果(loadable.state==“正在加载”){
//希望移除此石膏,因为它将缩小其与州的距离;
返回可加载为可加载的加载;
}
if(loadable.state==“Loaded”&&loadable.item){
//希望移除此石膏,因为它将缩小其与州的距离;
返回可加载为可加载的ILoadableLoaded;
}
抛出新错误(“某些错误”);
}
样本可在以下网址找到:
文件:
src/DiscriminatedUnion.ts
问题在于
可加载的
和定义的接口之间没有关系,这将保证函数
createLoadable()
在返回项之前将每个属性设置为正确的状态。例如,
Loadable
可以具有以下值:

var loadable=new loadable();
loadable.state=“Error”;
lodable.item=“结果文本。”;
返回可装载;
上面的代码不适合任何接口,但它是有效的
可加载的
实例

我的做法如下:

简化接口,只有一个是通用的:

接口ILoadableLoading{
国家:“装载”;
id:编号;
}
接口ILoadableLoaded{
声明:“已加载”;
id:编号;
项目:T;
}
接口ILoadableErrored{
声明:“错误”;
id:编号;
错误:字符串;
}
导出类型ILoadableDiscriminated=
|可适应负荷
|我可接受
|我犯了错误;
类型ILoadableState=ILoadableDiscriminated[“状态”];
为每个接口创建单独的类,以确保创建的对象符合接口定义:

class LoadableLoading实现了ILoadableLoading{
状态:“加载”=“加载”;
id:number=0;
}
类LoadableLoaded实现了ILoadableLoaded{
构造函数(公共项:T){}
状态:“已加载”=“已加载”;
id:number=0;
}
类LoadableErrored实现了ILoadableErrored{
构造函数(公共错误:字符串){}
状态:“错误”=“错误”;
id:number=0;
}
然后我们可以使用带有重载的函数来声明意图:

函数createLoadable(someState:true,state:ILoadableState,item?:T):ILoadableErrored;
函数createLoadable(someState:false,state:Loading,item?:T):ILoadableLoading;
函数createLoadable(someState:false,state:Loaded,item?:T):ILoadableLoaded;
函数createLoadable(someState:boolean,state?:ILoadableState,item?:T):ILoadableDiscriminated{
如果(某个州){
返回新的LoadableErrored(“某些错误”);
}
如果(状态==“正在加载”){
//想移除这个石膏,因为它会从状态中找到它;
返回新的LoadableLoading();
}
如果(状态==“已加载”&&item){
//想移除这个石膏,因为它会从状态中找到它;
返回新的LoadableLoaded(项目);
}
抛出新错误(“某些错误”);
}
最后,根据您对
createLoadable()
函数的输入参数,返回类型将自动区分:

const lodableError=createLoadable(true,“加载”);
日志(lodableError.error);
const lodableLoading=createLoadable(假,“加载”);
控制台日志(“加载”);
const loadableLoaded=createLoadable(false,“Loaded”,“MyResponse”);
console.log(loadableLoaded.item)

请注意,参数重载Typescript编译器的状态意图,但您需要确保函数体中的代码执行您声明的操作。

Hmm,看起来在赋值时会发生联合收缩,但这不会传播到包含收缩属性的对象,这些属性可能与。这似乎应该缩小父对象的范围(因为
state
是一个判别式),但我不确定赋值时的缩小是否遵循与类型保护相同的路径。我有点不知所措。。。也许其他人有更多的想法?注意:上面的评论是关于当
someState
true
时会发生什么的。问题是,我依赖于实例是相同的,因为
可加载的属性是
@可观察的
(mobx),但它们的父对象不是。试图确保某个实体在同一时间只有一个可加载的文件,并且这些文件会经历不同的状态。typescript函数重载很有趣!我不知道!