【问题标题】:Make a single property optional in TypeScript在 TypeScript 中将单个属性设为可选
【发布时间】:2017-08-26 20:37:12
【问题描述】:

在 TypeScript 中,2.2...

假设我有一个 Person 类型:

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}

我想创建一个返回 Person 的函数,但不需要昵称:

function makePerson(input: ???): Person {
  return {...input, nickname: input.nickname || input.name};
}

input 的类型应该是什么?我正在寻找一种动态方式来指定与Person 相同的类型,除了nickname 是可选的(nickname?: string | undefined)。到目前为止,我想出的最接近的是:

type MakePersonInput = Partial<Person> & {
  name: string;
  hometown: string;
}

但这并不是我想要的,因为我必须指定所有 必需的类型,而不是可选的类型。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    好吧,你真正描述的是两种不同的“类型”人(即人类型).. 一个普通人和一个昵称的人。

    interface Person {
        name: string;
        hometown: string;
    }
    
    interface NicknamedPerson extends Person {
        nickname: string;
    }
    

    然后,如果您真的不想要一个昵称的人而只是一个人,您只需实现 Person 接口。

    如果您只想使用一个 Person 接口,另一种方法是为非昵称的人使用不同的实现:

    interface Person {
      name: string;
      hometown: string;
      nickname: string;
    }
    class NicknamedPerson implements Person {
        constructor(public name: string, public hometown: string, public nickname: string) {}
    }
    
    class RegularPerson implements Person {
        nickname: string;
        constructor(public name: string, public hometown: string) {
            this.nickname = name;
        }
    }
    
    makePerson(input): Person {
         if(input.nickname != null) {
           return new NicknamedPerson(input.name, input.hometown, input.nickname);
         } else {
           return new RegularPerson(input.name, input.hometown);
         }
    }
    

    这使您仍然可以分配昵称(在没有昵称的情况下只是人名)并且仍然维护 Person 接口的合同。它实际上与您打算如何使用界面有关。代码是否关心有昵称的人?如果不是,那么第一个建议可能会更好。

    【讨论】:

    • 嗨......所以......这是对我提出的问题的一个很好的回应,但对于我实际试图解决的问题并没有那么多:/抱歉这个问题过于简单化了。就我所问的而言...基本上,每个人 必须 有一个昵称,但我不希望这个函数需要它。如果没有提供,它只会使用name 输入。
    • 我编辑了我的答案,以更好地说明我提倡的第二个选项。如果我们仍然不在同一页面上,请告诉我?请注意,RegularPerson 构造函数使“昵称”值等于输入的“名称”值。
    • input 这里仍然缺少一个类型,这是这个问题的棘手部分......我认为我正在尝试做的事情在 TypeScript 中是不可能的。我想我会用我发现的东西自我回答。
    【解决方案2】:

    经过大量挖掘,我认为我正在尝试做的事情在 TypeScript 中是不可能的...... 。不过,当spread/rest types 落地时,我认为它的语法类似于{ ...Person, nickname?: string }

    现在,我采用了一种更详细的方法,声明了 所需的属性:

    type MakePersonInput = Partial<Person> & {
      name: string;
      hometown: string;
    };
    function makePerson(input: MakePersonInput): Person {
      return {...input, nickname: input.nickname || input.name};
    }
    

    不幸的是,每当我向Person 添加更多必需的属性时,这需要我更新MakePersonInput,但不可能忘记这样做,因为它会导致makePerson 中的类型错误。

    【讨论】:

      【解决方案3】:

      更新:

      从 TypeScript 2.8 开始,条件类型更简洁地支持了这一点!到目前为止,这似乎也比以前的实现更可靠。

      type Overwrite<T1, T2> = {
          [P in Exclude<keyof T1, keyof T2>]: T1[P]
      } & T2;
      
      interface Person {
        name: string;
        hometown: string;
        nickname: string;
      }
      
      type MakePersonInput = Overwrite<Person, {
        nickname?: string;
      }>
      
      function makePerson(input: MakePersonInput): Person {
        return {...input, nickname: input.nickname || input.name};
      }
      

      和以前一样,MakePersonInput 等价于:

      type MakePersonInput = {
          name: string;
          hometown: string;
      } & {
          nickname?: string;
      }
      

      过时:

      从 TypeScript 2.4.1 开始,似乎还有另一个选项可用,正如 GitHub 用户 ahejlsberg 在类型减法线程中所建议的那样:https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458

      type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
      type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;
      
      interface Person {
        name: string;
        hometown: string;
        nickname: string;
      }
      type MakePersonInput = Overwrite<Person, {
        nickname?: string
      }>
      function makePerson(input: MakePersonInput): Person {
        return {...input, nickname: input.nickname || input.name};
      }
      

      根据 Intellisense,MakePersonInput 相当于:

      type MakePersonInput = {
          name: string;
          hometown: string;
      } & {
          nickname?: string;
      }
      

      这看起来有点滑稽,但绝对可以完成工作。

      不利的一面是,我需要盯着 Diff 类型一段时间,然后才能开始了解它是如何工作的。

      【讨论】:

      • 请注意,这使得原始界面中的任何可选字段都是必需的
      • 使用它来保留可选字段 - 类型 Overwrite = Pick> & T2;
      【解决方案4】:

      您也可以这样做,仅部分键。

      type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
      type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
      
      interface Person {
        name: string;
        hometown: string;
        nickname: string;
      }
      
      type MakePersonInput = PartialBy<Person, 'nickname'>
      

      【讨论】:

      • 这太棒了!请注意,您可以传递多个属性:键入 OnlyNameIsMandatory = PartialBy
      • 快速说明,Omit 帮助器类型现在默认在TypeScript 3.5+ 中可用。因此,如果您使用的是 TS 3.5 或更高版本,则不再需要自己定义Omit
      • 如果我还想向 MakePersonInput 添加一些额外的属性怎么办?
      • @ThanosM 你可以使用interface PersonExtended extends MakePersonInput { some_prop: string; } 或交集类型type MakePersonInput = PartialBy&lt;Person, 'nickname'&gt; &amp; {some_prop: string}
      • Pick&lt;T,K&gt; 实际上似乎没有必要:Omit&lt;T, K&gt; &amp; Partial&lt;T&gt; 也能完成工作。
      【解决方案5】:

      对于即插即用解决方案,请考虑使用出色的utility-types 包:

      npm i utility-types --save
      

      然后简单地使用Optional&lt;T, K&gt;:

      import { Optional } from 'utility-types';
      
      type Person = {
        name: string;
        hometown: string;
        nickname: string;
      }
      
      type PersonWithOptionalNickname = Optional<Person, 'nickname'>;
      
      // Expect:
      //
      // type PersonWithOptionalNickname {
      //   name: string;
      //   hometown: string;
      //   nickname?: string;
      // }
      

      【讨论】:

        【解决方案6】:

        这是我的 Typescript 3.5+ 可选实用程序类型

        type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
        
        // and your use case
        type MakePersonInput = Optional<Person, 'nickname'>
        
        // and if you wanted to make the hometown optional as well
        type MakePersonInput = Optional<Person, 'hometown' | 'nickname'>
        

        【讨论】:

          猜你喜欢
          • 2017-02-04
          • 2020-02-07
          • 2023-04-08
          • 2020-12-20
          • 1970-01-01
          • 2018-10-06
          • 2018-07-21
          • 1970-01-01
          • 2017-10-10
          相关资源
          最近更新 更多