typescript 打字稿接口 - 可能需要“一个或另一个”属性?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37688318/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Typescript Interface - Possible to make "one or the other" properties required?
提问by dsifford
Possibly an odd question, but i'm curious if it's possible to make an interface where one property or the other is required.
可能是一个奇怪的问题,但我很好奇是否可以制作一个需要一个属性或另一个属性的接口。
So, for example...
所以,例如...
interface Message {
text: string;
attachment: Attachment;
timestamp?: number;
// ...etc
}
interface Attachment {...}
In the above case, I'd like to make sure that either text
or attachment
exists.
在上述情况下,我想确保要么text
或attachment
存在。
Hopefully that makes sense.
希望这是有道理的。
Thanks in advance!
提前致谢!
Edit:This is how I'm doing it right now. Thought it was a bit verbose (typing botkit for slack).
编辑:这就是我现在的做法。认为它有点冗长(为 slack 键入 botkit)。
interface Message {
type?: string;
channel?: string;
user?: string;
text?: string;
attachments?: Slack.Attachment[];
ts?: string;
team?: string;
event?: string;
match?: [string, {index: number}, {input: string}];
}
interface AttachmentMessageNoContext extends Message {
channel: string;
attachments: Slack.Attachment[];
}
interface TextMessageNoContext extends Message {
channel: string;
text: string;
}
回答by Ryan Cavanaugh
You can use a union type to do this:
您可以使用联合类型来执行此操作:
interface MessageBasics {
timestamp?: number;
/* more general properties here */
}
interface MessageWithText extends MessageBasics {
text: string;
}
interface MessageWithAttachment extends MessageBasics {
attachment: Attachment;
}
type Message = MessageWithText | MessageWithAttachment;
If you want to allow both text and attachment, you would write
如果你想同时允许文本和附件,你会写
type Message = MessageWithText | MessageWithAttachment | (MessageWithText & MessageWithAttachment);
回答by publicJorn
Thanks @ryan-cavanaugh that put me in the right direction.
感谢 @ryan-cavanaugh 使我朝着正确的方向前进。
I have a similar case, but then with array types. Struggled a bit with the syntax, so I put it here for later reference:
我有一个类似的情况,但后来是数组类型。在语法上有点挣扎,所以我把它放在这里供以后参考:
interface BaseRule {
optionalProp?: number
}
interface RuleA extends BaseRule {
requiredPropA: string
}
interface RuleB extends BaseRule {
requiredPropB: string
}
type SpecialRules = Array<RuleA | RuleB>
// or
type SpecialRules = (RuleA | RuleB)[]
// or (in the strict linted project I'm in):
type SpecialRule = RuleA | RuleB
type SpecialRules = SpecialRule[]
Update:
更新:
Note that later on, you might still get warnings as you use the declared variable in your code. You can then use the (variable as type)
syntax.
Example:
请注意,稍后,当您在代码中使用声明的变量时,您可能仍会收到警告。然后您可以使用该(variable as type)
语法。例子:
const myRules: SpecialRules = [
{
optionalProp: 123,
requiredPropA: 'This object is of type RuleA'
},
{
requiredPropB: 'This object is of type RuleB'
}
]
myRules.map((rule) => {
if ((rule as RuleA).requiredPropA) {
// do stuff
} else {
// do other stuff
}
})
回答by robstarbuck
If you're truly after "one property or the other" and not both you can use never
in the extending type:
如果您真正追求“一个属性或另一个”而不是两者,您可以never
在扩展类型中使用:
interface MessageBasics {
timestamp?: number;
/* more general properties here */
}
interface MessageWithText extends MessageBasics {
text: string;
attachment?: never;
}
interface MessageWithAttachment extends MessageBasics {
text?: never;
attachment: string;
}
type Message = MessageWithText | MessageWithAttachment;
// OK
let foo: Message = {attachment: 'a'}
// OK
let bar: Message = {text: 'b'}
// ? ERROR: Type '{ attachment: string; text: string; }' is not assignable to type 'Message'.
let baz: Message = {attachment: 'a', text: 'b'}
回答by cuddlemeister
You can create few interfaces for the required conditions and join them in a type like here:
您可以为所需条件创建几个接口,并将它们加入如下类型:
interface SolidPart {
name: string;
surname: string;
action: 'add' | 'edit' | 'delete';
id?: number;
}
interface WithId {
action: 'edit' | 'delete';
id: number;
}
interface WithoutId {
action: 'add';
id?: number;
}
export type Entity = SolidPart & (WithId | WithoutId);
const item: Entity = { // valid
name: 'John',
surname: 'Doe',
action: 'add'
}
const item: Entity = { // not valid, id required for action === 'edit'
name: 'John',
surname: 'Doe',
action: 'edit'
}
回答by Stefan van de Vooren
There're some cool Typescript option that you could use https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk
您可以使用一些很酷的 Typescript 选项https://www.typescriptlang.org/docs/handbook/utility-types.html#omitk
Your question is: make an interface where either 'text' or attachment exist. You could do something like:
您的问题是:制作一个包含“文本”或附件的界面。你可以这样做:
interface AllMessageProperties {
text: string,
attachement: string,
}
type Message = Omit<AllMessageProperties, 'text'> | Omit<AllMessageProperties, 'attachement'>;
const messageWithText : Message = {
text: 'some text'
}
const messageWithAttachement : Message = {
attachement: 'path-to/attachment'
}
const messageWithTextAndAttachement : Message = {
text: 'some text',
attachement: 'path-to/attachment'
}
// results in Typescript error
const messageWithOutTextOrAttachement : Message = {
}
回答by Ryan Fujiwara
Ok, so after while of trial and error and googling I found that the answer didn't work as expected for my use case. So in case someone else is having this same problem I thought I'd share how I got it working. My interface was such:
好的,经过一段时间的反复试验和谷歌搜索,我发现答案对我的用例没有按预期工作。所以如果其他人遇到同样的问题,我想我会分享我是如何让它工作的。我的界面是这样的:
export interface MainProps {
prop1?: string;
prop2?: string;
prop3: string;
}
What I was looking for was a type definition that would say that we could have neither prop1 nor prop2 defined. We could have prop1 defined but not prop2. And finally have prop2 defined but not prop1. Here is what I found to be the solution.
我正在寻找的是一个类型定义,它会说我们既不能定义 prop1 也不能定义 prop2。我们可以定义 prop1 但不能定义 prop2。最后定义了 prop2 但没有定义 prop1。这是我发现的解决方案。
interface MainBase {
prop3: string;
}
interface MainWithProp1 {
prop1: string;
}
interface MainWithProp2 {
prop2: string;
}
export type MainProps = MainBase | (MainBase & MainWithProp1) | (MainBase & MainWithProp2);
This worked perfect, except one caveat was that when I tried to reference either prop1 or prop2 in another file I kept getting a property does not exist TS error. Here is how I was able to get around that:
这很完美,除了一个警告是,当我尝试在另一个文件中引用 prop1 或 prop2 时,我不断收到属性不存在的 TS 错误。这是我如何解决这个问题:
import {MainProps} from 'location/MainProps';
const namedFunction = (props: MainProps) => {
if('prop1' in props){
doSomethingWith(props.prop1);
} else if ('prop2' in props){
doSomethingWith(props.prop2);
} else {
// neither prop1 nor prop2 are defined
}
}
Just thought I'd share that, cause if I was running into that little bit of weirdness then someone else probably was too.
只是想我会分享这一点,因为如果我遇到了那一点点奇怪的事情,那么其他人可能也是如此。