我有一个接口
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
它抱怨类型不兼容。这是有道理的,因为我声明了
true
和false
的案例,而不是针对boolean
。然而,我涵盖了boolean
的所有可能情况,所以我感觉typescript可以理解这一点,我感觉我做错了什么。如果我明确声明
boolean
,interface ILoadingData extends Partial<IData> {
isLoading: boolean
}
它会匹配
isLoading = false
和Partial<IData>
,这不是我想要的。我该怎么办? 最佳答案
更新:2019-05-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
可能是true
或false
。然后,可以将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。三元运算符是冗余的(“然后”和“否则”子句是相同的),但是编译器得到的消息,在每种情况下,值匹配IDataWithLoading
。就像我说的,恶心。最后,你可以决定你比编译器更聪明,并且使用type assertion使它安静。这样做的好处是不会让您跳过程序,而编译器不能帮助您维护类型安全:
const e = {
isLoading: random,
importantData: 'secret'
} as IDataWithLoading; // Take that, compiler!
该怎么办取决于你。希望有帮助,祝你好运!