Typescript:如何使布尔值与可区分的真/假联合对一起使用?

shl*_*jin 6 typescript

我有一个界面

interface IData {
  importantData: string
}
Run Code Online (Sandbox Code Playgroud)

我想isLoading在那里添加标志。如果isLoading = false,则importantData已加载并且肯定必须存在。然而,如果isLoading = true,则importantData可能存在,也可能不存在。

interface ILoadedData extends IData {
  isLoading: false
}

interface ILoadingData extends Partial<IData> {
  isLoading: true
}
Run Code Online (Sandbox Code Playgroud)

所以,我的最终类型是这些的联合:

type IDataWithLoading = ILoadedData | ILoadingData
Run Code Online (Sandbox Code Playgroud)

如果我尝试它对于布尔文字效果很好

const a:IDataWithLoading = ({ isLoading: false, importantData: 'secret' })
const b:IDataWithLoading = ({ isLoading: false }) // the only error, nice
const c:IDataWithLoading = ({ isLoading: true })
const d:IDataWithLoading = ({ isLoading: true, importantData: 'secret' })
Run Code Online (Sandbox Code Playgroud)

但是,在编译时我不知道东西是否正在加载,所以:

const random = Math.random() > 0.5
const e:IDataWithLoading = ({ isLoading: random, importantData: 'secret' }) // doesn't work
Run Code Online (Sandbox Code Playgroud)

它抱怨类型不兼容。这是有道理的,因为我声明了true和 的情况false,而不是 的情况boolean。然而,我涵盖了 的所有可能情况boolean,所以我感觉 TypeScript 可以理解这一点,并且我感觉我做错了什么。

然而,如果我boolean明确声明,

interface ILoadingData extends Partial<IData> {
  isLoading: boolean
}
Run Code Online (Sandbox Code Playgroud)

它会isLoading = false与匹配Partial<IData>,这不是我想要的。我应该怎么办?

jca*_*alz 4

更新:2019-05-30 随着 TypeScript 3.5 的发布,这个问题应该通过更智能的联合类型检查来解决。以下内容适用于 3.4 及以下版本:


这是TypeScript 的一个已知限制。编译器不会尝试将联合传播到属性中。通常这样的传播不会发生(例如,{a: string, b: string} | {a: number, b: number}不能简化为{a: string | number, b: string | number}或更有用的东西)。即使在像您这样的情况下可以做某事,编译器这样做也不符合成本效益。通常这样的情况可以归结为手动引导编译器完成可能的状态。

例如,您可以尝试以下操作:

interface ILoadedDataButNotSureYet extends IData {
  isLoading: boolean;
}

interface ILoadedData extends ILoadedDataButNotSureYet {
  isLoading: false
}
Run Code Online (Sandbox Code Playgroud)

因此 anILoadedDataButNotSureYet确实已加载数据,但isLoading可能是trueor false。那么,你可以表示IDataWithLoading为:

type IDataWithLoading = ILoadedDataButNotSureYet | ILoadingData;
Run Code Online (Sandbox Code Playgroud)

这相当于你原来的定义,但祝你好运,让编译器注意到这一点。无论如何,您提到的所有具体实例仍然有效:

const a: IDataWithLoading = ({ isLoading: false, importantData: 'secret' })
const b: IDataWithLoading = ({ isLoading: false }) // the only error, nice
const c: IDataWithLoading = ({ isLoading: true })
const d: IDataWithLoading = ({ isLoading: true, importantData: 'secret' })
Run Code Online (Sandbox Code Playgroud)

但最后一个也有效:

const random = Math.random() > 0.5
const e: IDataWithLoading = ({ isLoading: random, importantData: 'secret' }) // works
Run Code Online (Sandbox Code Playgroud)

您可能想也可能不想真正改变IDataWithLoading。另一种方法是保留原始定义,然后对编译器施加足够的压力,直到它了解您的代码是安全的。但它并不漂亮:

const eLit = { isLoading: random, importantData: 'secret' };
const e: IDataWithLoading = eLit.isLoading ? 
  {...eLit, isLoading: eLit.isLoading} : {...eLit, isLoading: eLit.isLoading};
// works, but is ugly
Run Code Online (Sandbox Code Playgroud)

恶心。在这里,我们使用对象扩展来复制属性,并使用单独的isLoading属性来利用检查时发生的类型保护eLit.isLoading。三元运算符是多余的(“then”和“else”子句是相同的),但编译器得到的消息是在每种情况下值都匹配IDataWithLoading。正如我所说,恶心。


最后,您可以认为自己比编译器更聪明,并使用类型断言使其安静。这样做的优点是不会让您跳过麻烦,缺点是编译器无法帮助您维护类型安全:

const e = { 
 isLoading: random, 
 importantData: 'secret' 
} as IDataWithLoading; // Take that, compiler!
Run Code Online (Sandbox Code Playgroud)

如何继续取决于您。希望有帮助;祝你好运!