TypeScript仅允许参数中父对象的子项

时间:2018-02-08 19:38:35

标签: typescript

假设我有以下interfaces。我想从那些objects(例如interfaces / Actor)声明Film并将它们传递给一个类(From)。类From有一个方法Select,我只希望它们接受传递对象(Actor)的子项。我错了吗?有没有更好的办法?这不起作用,我不太了解它。

interface ColumnString {
    maxLength: number
}

type ColumnType = ColumnString | typeof Number | typeof Date

interface Column {
    toString: () => String 
    column: ColumnType
}

interface Table {
    toString: () => string
    columns: { [columnName: string]: Column }
}

const Actor: Table = {
    toString: () => "Actor",
    columns: {
        FirstName: {
            toString: () => "FirstName", column: { maxLength: 50 }
        },
        LastName: {
            toString: () => "LastName", column: { maxLength: 50 }
        },
        BirthDate: {
            toString: () => "BirthDate", column: Date
        }
    }
}

const Film: Table = {
    toString: () => "Film",
    columns: {
        FilmTitle: {
            toString: () => "FilmTitle", column: { maxLength: 100 }
        },
        Rating: {
            toString: () => "Rating", column: { maxLength: 5 }
        }
    }
}

// This doesn't work
type TableColumn<T extends Table> = { [P in keyof T["columns"]]: Column } 

class From1<T extends Table> {
    private table: T
    private columns: TableColumn<T>

    constructor(table: T) {
        this.table = table
    }

    public Print() {
        console.log("Table:", this.table.toString())
        Object.keys(this.columns).forEach((column, idx) => {
            console.log("Column", idx+":", column.toString())
        })
    }

    // This doesn't work.
    public Select(...columns: TableColumn<T>[]) {
        this.columns = columns
        return this
    }

}

function From<T extends Table>(table: T) {
    return new From1(table)
}

const A = Actor.columns
const F = Film.columns

From(Actor)
    // Should fail when I pass in F.FilmTitle but be OK with
    // any Actor Column
    .Select(A.FirstName, F.FilmTitle)
    .Print()

我尝试了很多东西,但我不确定该怎么做。

提前致谢!

1 个答案:

答案 0 :(得分:1)

您需要将列/表的名称作为字符串文字类型传递。然后,您可以使用不同字符串文字类型不兼容的事实,以确保您无法传递到其他表的Select列。

我必须稍微更改类的结构,以获得name属性,还有一些额外的初始化必须在表和列上完成,所以我不得不添加一些额外的功能,但结果实际上非常有用。

<强>用法

const Actor = table("Actor", {
    FirstName: { maxLength: 50 },
    LastName: { maxLength: 50 },
    BirthDate: Date
});

const Film = table("Actor", {
    FilmTitle: { maxLength: 50 },
    Rating:{ maxLength: 50 },
});

const A = Actor.columns
const F = Film.columns

From(Actor)
    // A.FirstName is ok, F.FilmTitle fails
    .Select(A.FirstName, F.FilmTitle)
    .Print()

<强>实施

type ColumnType = { maxLength: number } | (new () => Date); //For testing 

interface Column<TName = string, TOwnerName = string> {
    name: TName;
    tableName : TOwnerName;
    column: ColumnType;
    toString(): string;

}

interface Table<TName, TColumns extends { [name: string]: Column }> {
    name: TName;
    toString(): string;
    columns: TColumns
}

function table<TTableName extends string, TColumns extends { [name: string]: ColumnType }>(tableName: TTableName, columnTypes: TColumns) :  Table<TTableName, { [P in keyof TColumns] : Column<P, TTableName>}> {
    let columns :{ [P in keyof TColumns] : Column<P, TTableName>} = {} as any;
    for(let key of Object.getOwnPropertyNames(columnTypes)) {
        columns[key] = {
            name: key, 
            toString : () => key,
            column: columnTypes[key],
            tableName: tableName
        }
    }

    return {
        name: tableName,
        toString: () => tableName,
        columns
    };
}

class From1<TTableName extends string, TColumns  extends { [name: string] : Column }> {
    private table: Table<TTableName, TColumns>
    private columns: Column<keyof TColumns, TTableName>[]

    constructor(table: Table<TTableName, TColumns>) {
        this.table = table
    }

    public Print() {
        console.log("Table:", this.table.toString())
        this.columns.forEach((column, idx) => {
            console.log("Column", idx + ":", column.toString())
        })
    }
    public Select<TColumnName extends keyof TColumns>(...columns: Column<TColumnName, TTableName>[]) {
        this.columns = columns
        return this
    }

}

function From<TTableName extends string, TColumns  extends { [name: string] : Column<string> }>(table: Table<TTableName, TColumns>) {
    return new From1(table)
}