简体   繁体   English

如何在 TypeScript 中使用备用键正确键入 object?

[英]How to correctly type an object with alternate keys in TypeScript?

I want to create a TypeScript type that involves a set of fixed keys, plus a set of alternate groups of keys, such as:我想创建一个 TypeScript 类型,它涉及一组固定键,以及一组备用键组,例如:

type Props = {
   id: string
} & (
{
  height: number, 
  width: number
} 
  | {color: string}
)

Now, in this case, it correctly enforces that each of the groups have all the properties of that group, or none of them, ie: I can't use height without also using width .现在,在这种情况下,它正确地强制每个组都具有该组的所有属性,或者没有,即:我不能在不使用width的情况下使用height

Now, if I want to also allow an alternative without any of the optional keys, the only option that I know actually gives the desired result is by using {} as an alternative.现在,如果我还想允许没有任何可选键的替代方案,我知道实际提供所需结果的唯一选项是使用{}作为替代方案。

But lint rule @typescript-eslint/ban-types disallows that, saying that depending on the use case I should either Record<string, unknown> , unknown or Record<string, never> .但是 lint 规则@typescript-eslint/ban-types不允许这样做,说根据用例我应该Record<string, unknown>unknownRecord<string, never>

But none of these really work for this case.但这些都不适用于这种情况。 If I use unknown or Record<string, never> or object TS doesn't allow any of the keys in the alternatives.如果我使用unknownRecord<string, never>object TS 不允许替代项中的任何键。 If I use Record<string, unknown> it doesn't allow any key unless I fill one of the alternative key groups.如果我使用Record<string, unknown>它不允许任何密钥,除非我填写其中一个备用密钥组。

Is there another way of doing this that I'm missing, or should I ignore the lint rule to achieve this?有没有我遗漏的另一种方法,或者我应该忽略 lint 规则来实现这个?

I find in React that overloads a better case for this.我发现在 React 中重载是一个更好的例子。

You can create an overload for each pattern that you want to support.您可以为每个要支持的模式创建一个重载。

import React from 'react'

type RequiredProps = { id: string }

type SizeProps = { height: number, width: number } 
type ColorProps = { color: string }
type AllOptionProps = SizeProps | ColorProps


function MyComponent(props: RequiredProps): JSX.Element
function MyComponent(props: RequiredProps & SizeProps): JSX.Element
function MyComponent(props: RequiredProps & ColorProps): JSX.Element

function MyComponent(props: RequiredProps & Partial<AllOptionProps>) {
    console.log(props.id)
    if ('width' in props) console.log(props.width)
    if ('color' in props) console.log(props.color)
    return <></>
}

const a = <MyComponent id='abc' />
const b = <MyComponent id='abc' width={50} height={100} />
const c = <MyComponent id='abc' color='red' />

const d = <MyComponent id='abc' width={50} /> // error

See Playground 看游乐场


A second approach is to create a union where all props are in all members, but are forced to undefined if you say you can have them.第二种方法是创建一个联合,其中所有 props 都在所有成员中,但如果你说你可以拥有它们,则强制为undefined

import React from 'react'

type RequiredProps = { id: string }

type NoOptionsProps = {
    height?: undefined,
    width?: undefined,
    color?: undefined
}
type SizeProps = {
    height: number,
    width: number,
    color?: undefined
} 
type ColorProps = {
    height?: undefined,
    width?: undefined,
    color: string
}
type Props = RequiredProps & (NoOptionsProps | SizeProps | ColorProps)

function MyComponent(props: Props) {
    console.log(props.id)
    console.log(props.width)
    console.log(props.color)
    return <></>
}

const a = <MyComponent id='abc' />
const b = <MyComponent id='abc' width={50} height={100} />
const c = <MyComponent id='abc' color='red' />

const d = <MyComponent id='abc' width={50} /> // error

See Playground 看游乐场

You can try intersection of FC :您可以尝试FC的交集:

import React, { FC } from 'react'

interface Base {
  id: string
}

interface WithRect extends Base {
  height: number,
  width: number
}

interface WithColor extends Base {
  color: string
}


type Result = FC<WithColor> & FC<WithRect> & FC<Base>

const App: Result = (props) => <p></p>

const jsx = <App id="hello" color="green" /> // ok
const jsx____ = <App id="hello" /> // ok
const jsx___ = <App id="hello" width={1} height={1} /> // ok


const jsx_ = <App id="hello" color="green" height={1} /> // expected error
const jsx__ = <App id="hello" height={1} /> // expected error

Playground操场

This behavior is similar to function overloading, I would even say 95% similar but not equal.这种行为类似于 function 重载,我什至可以说 95% 相似但不相等。

If you are interested in typing React component props, see my articles my blog and dev.to如果你对输入 React component props 感兴趣,请参阅我的文章my blogdev.to

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

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