打字稿:如何使布尔值与真/假的已区分联合对一起工作?

时间:2018-10-17 15:16:49

标签: typescript

我有一个界面

interface IData {
  importantData: string
}

,我想在那里添加isLoading标志。如果为isLoading = false,则将加载importantData,并且肯定要在那里。但是,如果isLoading = true,则importantData可能存在,也可能不存在。

interface ILoadedData extends IData {
  isLoading: false
}

interface ILoadingData extends Partial<IData> {
  isLoading: true
}

所以,我的最终类型是这些的并集:

type IDataWithLoading = ILoadedData | ILoadingData

如果我尝试使用布尔文字,那么效果很好

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' })

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

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

它抱怨类型不兼容。因为我声明了truefalse的情况,而不是boolean的情况,所以这很有道理。但是,我介绍了boolean的所有可能情况,因此我感觉到TypeScript可以理解这一点,并且感觉到我做错了事。

但是,如果我明确声明boolean

interface ILoadingData extends Partial<IData> {
  isLoading: boolean
}

它将isLoading = falsePartial<IData>匹配,这不是我想要的。我该怎么办?

2 个答案:

答案 0 :(得分:2)

问题是您将Typescript用于错误的任务。 TS是关于类型而不是值的,因此它不知道

  

是否正在加载

所以您的界面应该看起来像这样

interface IData {
  isLoading: boolean
  importantData: string | null
}

更新

  

如果它看到isLoading = false并且没有,我试图让TS抱怨   重要数据,无论看到isLoading = true都不要抱怨   重要数据的存在。我100%确信有可能。

替换这段代码:

const random = Math.random() > 0.5
const e: IDataWithLoading = ({ isLoading: random, importantData: 'secret' }) 

对此:

const loaded = Math.random() > 0.5

let e:IDataWithLoading

if(loaded) {  
  e = { isLoading: false, importantData: 'secret' }
} else {
  e = { isLoading: true, importantData: undefined }
}

如果我对您的理解正确,那应该是您所需要的全部。

答案 1 :(得分:2)

更新:2019年5月30日,TypeScript 3.5发行版应通过smarter union type checking解决。以下内容适用于3.4及以下版本:


这是已知的limitation of 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
}

因此,ILoadedDataButNotSureYet确实加载了数据,但是isLoading可能是truefalse。然后,您可以将IDataWithLoading表示为:

type IDataWithLoading = ILoadedDataButNotSureYet | ILoadingData;

这等效于您的原始定义,但是祝您好运,以使编译器注意到这一点。无论如何,您提到的所有特定实例仍然有效:

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' })

但最后一个也可以:

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

您可能想要或实际上不想更改IDataWithLoading。另一种可行的方法是保留原始定义,并在对编译器的理解是安全的之前,对其进行足够的拍击。虽然不是很漂亮:

const eLit = { isLoading: random, importantData: 'secret' };
const e: IDataWithLoading = eLit.isLoading ? 
  {...eLit, isLoading: eLit.isLoading} : {...eLit, isLoading: eLit.isLoading};
// works, but is ugly

好在这里,我们使用object spread复制属性,并使用单独的isLoading属性来利用检查eLit.isLoading时发生的type guarding。三元运算符是多余的(“ then”和“ else”子句都是相同的),但是编译器会收到一条消息,指出每种情况下值均与IDataWithLoading相匹配。就像我说的,祝你好运。


最后,您可以确定自己比编译器更聪明,并使用type assertion使其变得安静。这样做的好处是不会使您无所适从,而缺点是编译器无法在此处帮助您维护类型安全:

const e = { 
 isLoading: random, 
 importantData: 'secret' 
} as IDataWithLoading; // Take that, compiler!

由您决定如何进行。希望能有所帮助;祝你好运!