我有一个接口

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>,这不是我想要的。我该怎么办?

最佳答案

更新: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可能是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。三元运算符是冗余的(“然后”和“否则”子句是相同的),但是编译器得到的消息,在每种情况下,值匹配IDataWithLoading。就像我说的,恶心。
最后,你可以决定你比编译器更聪明,并且使用type assertion使它安静。这样做的好处是不会让您跳过程序,而编译器不能帮助您维护类型安全:
const e = {
 isLoading: random,
 importantData: 'secret'
} as IDataWithLoading; // Take that, compiler!

该怎么办取决于你。希望有帮助,祝你好运!

09-19 20:11