使泛型类型成为可选的惯用方法是什么?

问题描述 投票:0回答:1

背景

我有一个库,它允许用户定义架构,并根据他们的定义返回自定义响应类型。这是一个最小的例子:

interface Item {
  id: string;
}

interface CardinalityDef {
  [objectType: string]: 'one' | 'many'
}

class Schema<Cardinality extends CardinalityDef> {
  constructor(public cardinality: Cardinality) {}
}

type ResponseOf<
  S extends Schema<any>, 
  K extends keyof S['cardinality']
> = S['cardinality'][K] extends 'one'
  ? Item
  : Item[];

class DB<S extends Schema<any>>{ 
  query<ObjectType extends keyof S['cardinality']>(objectType: ObjectType): ResponseOf<S, ObjectType> { 
    return 1 as any;
  }
}

function init<S extends Schema<any>>(schema: S): DB<S> {
  return 1 as any;
}

现在我可以定义一个模式,并按照我的预期获取响应类型:

const schema = new Schema({posts: 'many', author: 'one'})
const db = init(schema);
// This is correctly typed at Item[]
const postsResponse = db.query('posts');
// This is correctly typed as Item
const authorResponse = db.query('author');

游乐场链接

目标:使
schema
可选

我想让用户不提供模式。

const db = init(); // no schema provided

如果未提供架构,所有查询响应应返回

Item[]
:

const postsResponse: Item[] = db.query('posts');
const authorResponse: Item[] = db.query('author');

选项 1:IsAny

我可以将

schema
参数设置为可选:

function init<S extends Schema<any>>(schema?: S): DB<S> {
  return 1 as any;
}

然后更新

ResponseOf
类型,如下所示:

type IsAny<T> = 0 extends (1 & T) ? true : never;
type ResponseOf<
  S extends Schema<any>, 
  K extends keyof S['cardinality']
> = IsAny<S['cardinality']> extends true
  ? Item[] 
  : S['cardinality'][K] extends 'one' 
    ? Item 
    : Item[];

现在它可以工作了:

const dbWithoutSchema = init();
const postsResponseWithoutSchema = dbWithoutSchema.query('posts'); // types as Item[]
const authorResponseWithoutSchema = dbWithoutSchema.query('author'); // types as Item[]

游乐场链接

但是,我不确定这是否惯用。主要:

  1. 令人惊讶的是
    db
    被输入为
    Schema<any>
    。 它确实根本没有没有模式。
  2. 感觉很奇怪,我必须在 ResponseOf 中显式检查
    any
    。这就像我在与打字稿战斗。

选项 2:架构 |未定义

我也可以明确说明未定义的情况,如下所示:

function init<S extends Schema<any> | undefined>(schema?: S): DB<S> {
  return 1 as any;
}

游乐场链接

但是,这感觉也不对劲。我看到的缺点:

  1. 我必须在整个系统中贯穿
    | undefined
  2. 奇怪的是,在我提供模式的情况下,打字稿不会将
    S
    细化为严格
    undefined

问题

哪种方法在打字稿中更惯用?您会建议采用不同的方法来允许可选模式吗?

我倾向于将类型默认为 Schema,并明确执行 IsAny 检查。我的主要理由是,这将 a) 消除线程通过 | 的需要。到处都是未定义的,b) 在没有提供模式时使类型细化。

欢迎所有想法!

typescript
1个回答
0
投票

如果您希望类型参数在无法推断的情况下回退到某些内容,您可以给它一个默认类型参数

function init<
    S extends Schema<any> = Schema<{ [k: string]: 'many' }>
>(schema?: S): DB<S> {
    return 1 as any;
}

在这里,如果您调用

init()
并且 TypeScript 无法从中推断出
S
,因为您没有传入
schema
参数,TypeScript 将回退到默认值
Schema<{ [k: string]: 'many' }>
,其中
Schema
的类型参数有一个
string
索引签名,其类型为
"many"
。 这意味着如果您不带参数调用
init()
,TypeScript 会认为您调用了
init(schema)
,其中
schema
cardinality
属性将所有可能的属性视为
"many"
类型,从而映射每个可能的键至
Item[]

const dbWithoutSchema = init();
const postsResponseWithoutSchema = dbWithoutSchema.query('posts');
//    ^? const postsResponseWithoutSchema: Item[]
const authorResponseWithoutSchema = dbWithoutSchema.query('author');
//    ^? const authorResponseWithoutSchema: Item[]
const randomResponseWithoutSchema = dbWithoutSchema.query('randomCrud');
// ^? const randomResponseWithoutSchema: Item[]

它允许您使用您想要的任何属性密钥调用

query()
。当您传入参数时,它的行为与以前一样,其中类型参数被推断为该参数的类型,并根据键返回
Item
Item[]
,并且它只允许您传入已知键:

const schema = new Schema({ posts: 'many', author: 'one' })
const db = init(schema);
const postsResponse = db.query('posts');
//    ^? const postsResponse: Item[]
const authorResponse = db.query('author');
//    ^? const authorResponse: Item
db.query('randomCrud') // error,
//      ~~~~~~~~~~~~~
// '"randomCrud"' is not assignable to '"posts" | "author"'

使用默认的泛型类型参数是处理此类事情的惯用方法。你的其他方法可以工作或者可以工作,尽管你真的不想尝试检测

any
,虽然
undefined
可能是添加到类型参数域中的合理选择,但 TypeScript 实际上不会如果省略参数,则为 infer
undefined
,因为
init(undefined)
init()
的等价性不会反映在类型推断中。所以你仍然需要将其设置为默认值,产生类似的结果

function init<
    S extends Schema<any> | undefined = undefined
>(schema?: S): S extends undefined ? DB<Schema<{ [k: string]: 'many' }>> : DB<S> {
    return 1 as any;
}

但这更复杂,我只建议如果您希望人们明确地调用

init(undefined)
(实际上几乎没有人这样做)。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.