如何使打字稿类型中的一个属性成为非可选属性?

Lou*_*let 67 typescript

我有这种类型:

type User = { 
  id: string;
  name?: string;
  email?: string;
}
Run Code Online (Sandbox Code Playgroud)

我想构造一个类似的name非可选类型:

type UserWithName = {
  id: string;
  name: string;
  email?: string;
}
Run Code Online (Sandbox Code Playgroud)

我如何使用通用实用程序类型进行构造,而UserWithName不是像上面那样重复类型?必需的几乎可以完成这项工作,但它将所有属性设置为非可选,而我只想设置一个属性。User

Voj*_*nad 130

如果你检查类型的来源Required,它是这样的:

type Required<T> = {
  [P in keyof T]-?: T[P]
}
Run Code Online (Sandbox Code Playgroud)

可以使用相同的语法来构造泛型类型,它将为您提供所需的内容:

type User = {
  id: string
  name?: string
  email?: string
}

type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }

type UserWithName = WithRequired<User, 'name'>

// error: missing name
const user: UserWithName = {
  id: '12345',
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

  • 如果您想知道“-?”是什么意思,请参阅此[链接](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers) (24认同)
  • 这相当于“type WithRequired&lt;T, K extends keyof T&gt; = T &amp;Required&lt;Pick&lt;T, K&gt;&gt;”,不是吗?我发现 `Required&lt;Pick&lt;T, K&gt;&gt;` 比 `[P in K]-?: T[P]` 更具可读性。 (17认同)

Set*_*ske 24

您可以使用接口代替:

interface User  { 
  id: string;
  name?: string;
  email?: string;
}


interface UserWithName extends User {
  name: string;
}
Run Code Online (Sandbox Code Playgroud)

现在您已经在该User类型的基础上进行了构建,但将name属性覆盖为强制属性。查看这个打字稿游乐场-UserWithName没有 name 属性的打字稿将会出错。

  • 谢谢您的回答!这是一个有趣的替代方案,但我正在寻找一种使用通用实用程序类型的方法,因为我真的不想声明不同的函数类型(如果这有意义的话)。 (2认同)

Amr*_*lon 20

如果不想做辅助类型,可以将RequiredPick结合起来

type User = { 
  id: string;
  name?: string;
  email?: string;
}

const user: Required<Pick<User, 'name'>> & User = {
  id: '1',
  name: 'Jim'
}
Run Code Online (Sandbox Code Playgroud)

  • @Erythros 你是什么意思?电子邮件仍然有效 https://www.typescriptlang.org/play?#code/C4TwDgpgBAqgzhATlAvFA3lAUFKBLAEwC4o5hE8A7AcwG4cpKBDAWwgH4SyKb7cIWTPABtOpclTpYAvliwBjAPaUyUAK4JEJAEoQAjmryIIBADwAFPPIDWp+EgA0UAOTM2zgHweo AMlibUDAZCEmcARMcHBjcIUIApPBZIhgEhyVCYNQazLIABTJyaoiUkmSA (3认同)

cpr*_*ack 19

我认为这更简单并且效果很好。给定User您想要使name属性成为非可选的类型:

type User = { 
  id: string;
  name?: string;
  email?: string;
}
Run Code Online (Sandbox Code Playgroud)

创建命名接口:

interface UserWithName extends User {
  name: NonNullable<User['name']>
}
let personWithName1: UserWithName;
Run Code Online (Sandbox Code Playgroud)

要创建命名类型:

type UserWithName = User & {
  name: NonNullable<User['name']>
}
let personWithName2: UserWithName;
Run Code Online (Sandbox Code Playgroud)

创建匿名类型:

let personWithName3: User & { name: NonNullable<User['name']> }
Run Code Online (Sandbox Code Playgroud)

我喜欢这种方法,因为您不需要定义一个新的泛型,但您仍然可以从能够自由更改name中的属性类型中受益User,并且如果您更改 的名称本身,您也会看到错误该name财产。

如果您不完全理解发生了什么,我正在使用:

  • 非常好:使用 NonNullable 是最明确的!还要感谢您提供了 3 种声明修改类型的方法,并排查看这些方法很有用。 (3认同)

小智 7

我对上面提出的建议进行了改进。

type Required<T, K extends keyof T> = T & { [P in K]-?: T[P] }

type WithRequired<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> & Required<T, K>
Run Code Online (Sandbox Code Playgroud)

现在您可以一次需要多个属性

interface foo {x?: number, y?: String}

WithRequired<foo, 'x' | 'y'>
Run Code Online (Sandbox Code Playgroud)

现在需要每个属性

  • 感谢您的补充,但是语法 ```WithRequired&lt;foo, 'x' | 'y'&gt;``` 也适用于接受的答案,而且更简单:它不需要使用 ```Exclude``` (4认同)

byo*_*oda 6

对于那些寻找使用实用程序类型的答案的人来说,这应该可行

type WithRequiredProp<Type, Key extends keyof Type> = Type & Required<Pick<Type, Key>>;
Run Code Online (Sandbox Code Playgroud)