TypeScript:将映射类型与泛型一起使用
TypeScript: Using Mapped Types with Generics
我一直在尝试使用 TypeScript,希望制作一个类型安全的数据库查询库(松散地基于 Scala 的 Slick)。在 Mapped Types 的帮助下,我取得了一些不错的进展,但我一直坚持保留 Column 的基础类型。有关综合示例,请参见下面的代码:
class Table {}
class TableExpression<TK extends Table> {
table: TK
alias: string
constructor(table: TK, alias?: string) {
this.table = table
this.alias = alias || table.constructor.name
}
}
class Column<T> {
name: string
defaultValue?: T
}
class StringC extends Column<string> {
constructor(name: string, defaultValue?: string) {
super()
this.name = name
this.defaultValue = defaultValue
}
}
class ColumnExpression<TK extends Table, CK extends Column<any>> {
table: TableExpression<TK>
column: CK
alias: string
constructor(table: TableExpression<TK>, column: CK, alias?: string) {
this.table = table
this.column = column
this.alias = alias || column.name
}
eq(val: any): string {
return `${this.table.alias}.${this.column.name} = "${val}"` // Obviously, not safe.
}
}
class E1 extends Table {
name = new StringC('name')
slug = new StringC('slug')
}
let e1 = new E1()
let ee1 = new TableExpression(e1, 'e1')
type TableQuery<TK extends Table> = {
[P in keyof TK]: ColumnExpression<TK, TK[P]>
}
function query<TK extends Table>(te: TableExpression<TK>): TableQuery<TK> {
let result = {} as TableQuery<TK>
for (const k in te.table) {
result[k] = new ColumnExpression(te, te.table[k])
}
return result
}
let tq1 = query(ee1)
console.log(tq1.name.eq('Pancakes')) // e1.name = "Pancakes"
此代码按预期编译和工作。我遇到的问题是如何使 eq()
方法利用 Column 使用的泛型类型。我可以轻松地扩展 ColumnExpression 以使用另一种类型参数,例如下面的 CT:
class ColumnExpression<TK extends Table, CT, CK extends Column<CT>> {
...
eq(val: CT) { ... }
}
这部分说得有道理。接下来的问题是将其带入 TableQuery
的映射类型定义中。似乎 TableQuery 需要以某种方式进行参数化,以便我可以将 CT 传递给 ColumnExpression,但我不知道该怎么做。有任何想法吗?
您可以使用 Lookup Types.
要在 eq
中获取泛型作为参数值,请像这样更改您的签名:
eq(val: CK['defaultValue']): string
对于 Column<string>
这将导致 string | undefined
,至少在启用 --strictNullChecks
的情况下。那是因为 defaultValue
是可选的。所以调用 eq(undefined)
仍然有效。
您可以通过 CK['defaultValue'] & Object
删除 undefined
,但我不确定这是否有任何我不知道的副作用。
我一直在尝试使用 TypeScript,希望制作一个类型安全的数据库查询库(松散地基于 Scala 的 Slick)。在 Mapped Types 的帮助下,我取得了一些不错的进展,但我一直坚持保留 Column 的基础类型。有关综合示例,请参见下面的代码:
class Table {}
class TableExpression<TK extends Table> {
table: TK
alias: string
constructor(table: TK, alias?: string) {
this.table = table
this.alias = alias || table.constructor.name
}
}
class Column<T> {
name: string
defaultValue?: T
}
class StringC extends Column<string> {
constructor(name: string, defaultValue?: string) {
super()
this.name = name
this.defaultValue = defaultValue
}
}
class ColumnExpression<TK extends Table, CK extends Column<any>> {
table: TableExpression<TK>
column: CK
alias: string
constructor(table: TableExpression<TK>, column: CK, alias?: string) {
this.table = table
this.column = column
this.alias = alias || column.name
}
eq(val: any): string {
return `${this.table.alias}.${this.column.name} = "${val}"` // Obviously, not safe.
}
}
class E1 extends Table {
name = new StringC('name')
slug = new StringC('slug')
}
let e1 = new E1()
let ee1 = new TableExpression(e1, 'e1')
type TableQuery<TK extends Table> = {
[P in keyof TK]: ColumnExpression<TK, TK[P]>
}
function query<TK extends Table>(te: TableExpression<TK>): TableQuery<TK> {
let result = {} as TableQuery<TK>
for (const k in te.table) {
result[k] = new ColumnExpression(te, te.table[k])
}
return result
}
let tq1 = query(ee1)
console.log(tq1.name.eq('Pancakes')) // e1.name = "Pancakes"
此代码按预期编译和工作。我遇到的问题是如何使 eq()
方法利用 Column 使用的泛型类型。我可以轻松地扩展 ColumnExpression 以使用另一种类型参数,例如下面的 CT:
class ColumnExpression<TK extends Table, CT, CK extends Column<CT>> {
...
eq(val: CT) { ... }
}
这部分说得有道理。接下来的问题是将其带入 TableQuery
的映射类型定义中。似乎 TableQuery 需要以某种方式进行参数化,以便我可以将 CT 传递给 ColumnExpression,但我不知道该怎么做。有任何想法吗?
您可以使用 Lookup Types.
要在 eq
中获取泛型作为参数值,请像这样更改您的签名:
eq(val: CK['defaultValue']): string
对于 Column<string>
这将导致 string | undefined
,至少在启用 --strictNullChecks
的情况下。那是因为 defaultValue
是可选的。所以调用 eq(undefined)
仍然有效。
您可以通过 CK['defaultValue'] & Object
删除 undefined
,但我不确定这是否有任何我不知道的副作用。