如何创建类似于Partial的,需要设置单个属性

时间:2018-01-12 16:58:24

标签: typescript generics

我们的结构如下:

export type LinkRestSource = {
    model: string;
    rel?: string;
    title?: string;
} | {
    model?: string;
    rel: string;
    title?: string;
} | {
    model?: string;
    rel?: string;
    title: string;
};

这与说

几乎相同
type LinkRestSource = Partial<{model: string, rel: string, title: string}>

除了这将允许传入一个空对象,而初始类型需要传递一个属性

如何创建类似Partial的通用,但其行为与我上面的结构相似?

5 个答案:

答案 0 :(得分:15)

我想我有一个解决方案。您正在寻找带有T类型的内容,并生成一个相关类型,其中包含来自T至少一个属性。也就是说,它就像Partial<T>但排除了空对象。

如果是这样,这里是:

type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]

要剖析它:首先,AtLeastOne<T> Partial<T>某事相交。 U[keyof U]表示它是U的所有属性值的并集。我已将({1}}的默认值定义为mapped type,其中U的每个属性都映射到T,这是键的单属性类型Pick<T, K>。 (例如,K等同于Pick<{foo: string, bar: number},'foo'> ...它从原始类型“选择”{foo: string}属性。)意味着'foo'在这种情况下是联合来自U[keyof U]的所有可能的单一属性类型。

嗯,这可能令人困惑。让我们一步一步地看看它如何在以下具体类型上运行:

T

扩展到

type FullLinkRestSource = {
  model: string;
  rel: string;
  title: string;
}

type LinkRestSource = AtLeastOne<FullLinkRestSource>

type LinkRestSource = AtLeastOne<FullLinkRestSource, {
  [K in keyof FullLinkRestSource]: Pick<FullLinkRestSource, K>
}>

type LinkRestSource = AtLeastOne<FullLinkRestSource, {
  model: Pick<FullLinkRestSource, 'model'>,
  rel: Pick<FullLinkRestSource, 'rel'>,
  title: Pick<FullLinkRestSource, 'title'>
}>

type LinkRestSource = AtLeastOne<FullLinkRestSource, {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}>

type LinkRestSource = Partial<FullLinkRestSource> & {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}[keyof {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}]

type LinkRestSource = Partial<FullLinkRestSource> & {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}['model' | 'rel' | 'title']

type LinkRestSource = Partial<FullLinkRestSource> &
  ({model: string} | {rel: string} | {title: string})

type LinkRestSource = {model?: string, rel?: string, title?: string} & 
  ({model: string} | {rel: string} | {title: string})

我认为,这就是你想要的。

你可以测试一下:

type LinkRestSource = { model: string, rel?: string, title?: string } 
  | {model?: string, rel: string, title?: string} 
  | {model?: string, rel?: string, title: string}

那么,这对你有用吗?祝你好运!

答案 1 :(得分:7)

jcalz解决方案的更简单版本:

type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]

因此整个实现变成

type FullLinkRestSource = {
  model: string;
  rel: string;
  title: string;
}

type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
type LinkRestSource = AtLeastOne<FullLinkRestSource>

const okay0: LinkRestSource = { model: 'a', rel: 'b', title: 'c' }
const okay1: LinkRestSource = { model: 'a', rel: 'b' }
const okay2: LinkRestSource = { model: 'a' }
const okay3: LinkRestSource = { rel: 'b' }
const okay4: LinkRestSource = { title: 'c' }

const error0: LinkRestSource = {} // missing property
const error1: LinkRestSource = { model: 'a', titel: 'c' } // excess property on string literal

and here's the TS playground link to try it

答案 2 :(得分:3)

如果知道所需的属性,还有另一种解决方案。

AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>

这也可以让您锁定一种类型的多个键,例如AtLeast<T, 'model' | 'rel'>

答案 3 :(得分:2)

Partial<T>无法做到这一点。在引擎盖下它看起来像这样:

type Partial<T> = { [P in keyof T]?: T[P]; };

所有属性都是可选的。

我怀疑通过类型系统强制执行规则是可能的(或简单的)。

可以尝试以类似的方式创建使用keyof的类型,但在默认构造函数中有条件。

如果你能想出一种方法来声明类似Partial的类型,它构建类似于你的类型矩阵,为每个类型中的不同键发出?,并使用{{1}连接所有类型在第一个示例中,您可能能够强制执行规则类型系统。

This blog post on keyof可能会给你一些想法。

答案 4 :(得分:0)

也许是这样的:

type X<A, B, C> = (A & Partial<B> & Partial<C>) | (Partial<A> & B & Partial<C>) | (Partial<A> & Partial<B> & C);
type LinkRestSource = X<{ model: string }, { rel: string }, { title: string }>
var d: LinkRestSource = {rel: 'sdf'};  

但它有点乱::)

type Y<A, B, C> = Partial<A & B & C> & (A | B | C);