简体   繁体   English

打字稿:如何使用有区别的联合对true / false进行布尔工作?

[英]Typescript: how to make boolean work with discriminated union pair of true/false?

I have an interface 我有一个界面

interface IData {
  importantData: string
}

and I want to add isLoading flag there. 我想在那里添加isLoading标志。 If isLoading = false , then the importantData is loaded and for sure has to be there. 如果isLoading = false ,则加载importantData并且肯定必须在那里。 However, if isLoading = true , then importantData may be, or may be not there. 但是,如果isLoading = true ,那么importantData可能是,或者可能不存在。

interface ILoadedData extends IData {
  isLoading: false
}

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

So, my final type is a union of these: 所以,我的最终类型是这些的联合:

type IDataWithLoading = ILoadedData | ILoadingData

If I try it works great with boolean literals 如果我尝试它可以很好地使用布尔文字

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

however, at compile time I don't know whether things are loading or not, so: 但是,在编译时我不知道是否正在加载,所以:

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

It complaints that types are incompatible. 它抱怨类型不兼容。 It kind of makes sense, since I declared cases for true and false , not for boolean . 这有点意义,因为我声明了truefalse ,而不是boolean However, I covered every possible case for boolean , so I sense that TypeScript can understand that and I sense I'm doing something wrong. 但是,我覆盖了boolean每个可能的情况,所以我觉得TypeScript可以理解这一点,我觉得我做错了。

If I declare boolean explicitly, however, 但是,如果我明确声明boolean

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

it'll match isLoading = false with Partial<IData> , which is not what I want. 它将与Partial<IData>匹配isLoading = false ,这不是我想要的。 What should I do? 我该怎么办?

The problem is that you are using Typescript for a wrong task. 问题是您使用的是Typescript来执行错误的任务。 TS is about types, not values, so it cannot know TS是关于类型,而不是值,所以它无法知道

whether things are loading or not 是否装货

So your interface should look just like this 所以你的界面看起来应该是这样的

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

Update 更新

I'm trying to make TS complain if it sees isLoading=false and no importantData and do not complain if it sees isLoading=true regardless of importantData presence. 我试图让TS抱怨如果它看到isLoading = false并且没有importantData并且如果它看到isLoading = true则不抱怨,无论importantData存在。 I am 100% sure it's possible. 我100%确定它是可能的。

Replacing this part of your code: 替换这部分代码:

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

To this: 对此:

const loaded = Math.random() > 0.5

let e:IDataWithLoading

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

Should be all you need, if I understand you correctly. 如果我理解正确,应该是你所需要的一切。

UPDATE: 2019-05-30 with the release of TypeScript 3.5 this should be addressed by smarter union type checking . 更新:2019-05-30随着TypeScript 3.5的发布,这应该通过更智能的联合类型检查来解决。 The following applies to 3.4 and below: 以下适用于3.4及以下:


This is a known limitation of TypeScript . 这是TypeScript的已知限制 The compiler makes no attempt to propagate unions down into properties. 编译器不会尝试将联合传播到属性中。 Usually such a propagation can't happen (eg, {a: string, b: string} | {a: number, b: number} cannot be reduced to {a: string | number, b: string | number} or anything else more useful). 通常这种传播不会发生(例如, {a: string, b: string} | {a: number, b: number}不能简化为{a: string | number, b: string | number}或其他任何东西更有用)。 Even in the cases like yours where it is possible to do something, it's not cost-effective for the compiler to make the effort. 即使在像你这样可以做某些事情的情况下,编译器也不会付出成本效益。 Usually cases like this come down to manually guiding the compiler through possible states. 通常这样的情况归结为手动引导编译器通过可能的状态。

For example, you could try this: 例如,你可以试试这个:

interface ILoadedDataButNotSureYet extends IData {
  isLoading: boolean;
}

interface ILoadedData extends ILoadedDataButNotSureYet {
  isLoading: false
}

So an ILoadedDataButNotSureYet does have data loaded but isLoading might be true or false . 因此, ILoadedDataButNotSureYet确实已加载数据,但isLoading可能为truefalse Then, you can express IDataWithLoading as: 然后,您可以将IDataWithLoading表示为:

type IDataWithLoading = ILoadedDataButNotSureYet | ILoadingData;

This is equivalent to your original definition, but good luck trying to get the compiler to notice that. 这相当于你原来的定义,但是好运,试图让编译器注意到这一点。 Anyway, all the specific instances you mentioned still work: 无论如何,你提到的所有具体实例仍然有效:

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

but the last one also works: 但最后一个也有效:

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

You might or might not want to actually change IDataWithLoading . 您可能想要或不想要实际更改IDataWithLoading Another way to go is to keep the original definition and just slap the compiler hard enough until it understands that your code is safe. 另一种方法是保持原始定义,并且只需要对编译器进行足够的打击,直到它理解您的代码是安全的。 It's not pretty though: 虽然它不漂亮:

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

Yuck. 呸。 Here we are using object spread to copy properties, and a separate isLoading property to take advantage of the type guarding that happens when you inspect eLit.isLoading . 这里我们使用对象扩展来复制属性,并使用单独的isLoading属性来利用检查eLit.isLoading时发生的类型保护 The ternary operator is redundant (both the "then" and "else" clauses are identical) but the compiler gets the message that in each case the value matches IDataWithLoading . 三元运算符是冗余的(“then”和“else”子句都相同)但编译器会得到消息,在每种情况下值都与IDataWithLoading匹配。 As I said, yuck. 正如我所说,哎呀。


Finally, you could just decide that you're smarter than the compiler and use a type assertion to make it be quiet. 最后,您可以确定您比编译器更聪明,并使用类型断言使其安静。 This has the advantage of not making you jump through hoops, with the disadvantage that the compiler isn't helping you maintain type safety here: 这样做的好处是不会让你跳过箍,但缺点是编译器在这里没有帮助你维护类型安全:

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

It's up to you how to proceed. 这取决于你如何进行。 Hope that helps; 希望有所帮助; good luck! 祝好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM