我正在try 定义对象的类型化array.共享相同泛型类型 struct 的对象和泛型类型接受类型参数以提供更具体的(约束对象的其他(数组)属性的允许值).

Example

类型化数组应强制其元素遵守如下规则:

{ tableName: 'roles', ...}应将returnColumns限制为只有'id' and/or 'name',并且 { tableName: 'permissions', ...}应该将returnColumns限制为只有'id' and/or 'permission'

例如:

[ 
   {
      tableName: 'roles',
      returnColumns: ['id', 'name']  // should be OK
    },
    {
      tableName: 'roles',
      returnColumns: ['name']  // should be OK
    },
    {
      tableName: 'permissions',
      returnColumns: ['id', 'permission']  //  should be OK
    },
    {
      tableName: 'permissions',
      returnColumns: ['id']  //  should be OK
    },
    {
      tableName: 'roles',
      returnColumns: ['id', 'permission']  //  should not be OK, permission belongs to permissions
    },
]

Breakdown

第1部分:工作类型化对象

在下面的示例中,单个对象的类型正确:

type JoinTable<T> = {
  tableName: T;
  returnColumns: TableColumns<TypeOfTableName<T>>[];
};

const rolesExample1: JoinTable<'roles'> = {
  tableName: 'roles',
  returnColumns: ['id', 'name'] // OK, because roles is only allowed id and/or name
};

const rolesExample2: JoinTable<'roles'> = {
  tableName: 'roles',
  returnColumns: ['id', 'permission'] // Should fail, because roles is only allowed id and name, and permission is not available to roles
};

const permsExample3: JoinTable<'permissions'> = {
  tableName: 'permissions',
  returnColumns: ['id', 'permission'] // OK, because permissions is only allowed id and/or permission
};

const permsExample4: JoinTable<'permissions'> = {
  tableName: 'permissions',
  returnColumns: ['id', 'name'] // Should fail, because permissions is only allowed id and/or permission, and name is not available to permissions
};

第2部分:失败的类型化对象数组

在下面的示例中,我们看到了失败:

type JoinTable<T> = {
  tableName: T;
  returnColumns: TableColumns<TypeOfTableName<T>>[];
};

type JoinColumns<T> = T extends unknown
  ? TableColumns<TypeOfTableName<T>>
  : never;

// This code allows for returnColumns elements to be any value of any table
export type Joins<T extends TableName> = {
  joins: (Omit<JoinTable<RelatedTableName<T>>, 'returnColumns'> & {
      returnColumns: JoinColumns<RelatedTableName<T>>[]
  })[]
};


const combinedJoins: Joins<'role_permissions'> = {
  joins: [
    {
      tableName: 'roles',
      returnColumns: ['id', 'name', 'permission'] // Should (but doesn't) fail, because permission is not available to roles
    },
    {
      tableName: 'permissions',
      returnColumns: ['id', 'name', 'permission'] // Should (but doesn't) fail, because name is not available to permissions
    },
    {
      tableName: 'roles',
      returnColumns: ['id', 'name', 'uh-oh'] // Should fail (and does) because nothing supports uh-oh
    },
    {
      tableName: 'roles',
      returnColumns: ['id', 'name']  // should be OK
    },
    {
      tableName: 'roles',
      returnColumns: ['name']  // should be OK
    },
    {
      tableName: 'permissions',
      returnColumns: ['id', 'permission']  //  should be OK
    },
    
  ]
};

第3部分:从哪里开始

我最初使用的是以下Joins类型的变体:

export type Joins<T extends TableName> = {
  joins: JoinTable<RelatedTableName<T>>[]
};

当数组中只有一种对象类型时,这是有效的,但对于两种对象类型,突然之间,redouColumns的值被限制为仅限于数组中所有对象共享的那些值.

例如: if there was both an roles and a permissions object in the array, the values of returnColumns could only be id which both types have in common.

数据模式和支持类型:数据库、表名、表列、表关系等


export interface Database {
  golf_scores: {
    Tables: {
      permissions: {
        Row: {
          id: number;
          permission: string;
        };
        Relationships: [];
      };
      role_permissions: {
        Row: {
          id_permissions: number;
          id_roles: number;
        };

        Relationships: [
          {
            referencedRelation: 'permissions';
            referencedColumns: ['id'];
          },
          {
            referencedRelation: 'roles';
            referencedColumns: ['id'];
          }
        ];
      };
      roles: {
        Row: {
          id: number;
          name: string;
        };
        Relationships: [];
      };
    };
  }
}

export type TableName = keyof Database['golf_scores']['Tables'];


export type TableColumns<T extends TableName> =
  keyof Database['golf_scores']['Tables'][T]['Row'];

export type TypeOfTableName<T> = T extends TableName ? T : never;

type TablesRelationships<T extends TableName> =
  Database['golf_scores']['Tables'][T]['Relationships'];

export type RelatedTableName<T extends TableName> =
  TablesRelationships<T>[number]['referencedRelation'];


(注:此处的工作示例:(typescriptlang.org)

Final comments

到目前为止,似乎只可能拥有所有表中的所有值,或者只拥有所有表中通用的所有表中的值.我一直在探索一些类似的解决方案:Typescript, merge object types?个,但我开始怀疑我正在try 的是否可能?

推荐答案

问题是,在

type Joins<T extends TableName> = {
  joins: (Omit<JoinTable<RelatedTableName<T>>, 'returnColumns'> & {
      returnColumns: JoinColumns<RelatedTableName<T>>[]
  })[]
};

joins属性的类型不是RelatedTableName<T>中的distribute超过unions.为了便于讨论,让我们将该定义分成几个部分.您的代码相当于:

type Joins<T extends TableName> = {
  joins: J<RelatedTableName<T>>
}

type J<R> = (Omit<JoinTable<R>, 'returnColumns'> & {
  returnColumns: JoinColumns<R>[]
})[]

R是像R1 | R2 | R3这样的联合类型时,您真的希望J<R>也是像J<R1> | J<R2> | J<R3>这样的联合类型.这就是"通过unions 分配"的意思.但这不是J<R>的工作方式.The Omit utility type个不会在unions 之间分配,即使它分配了,也不会与& { returnColumns: JoinColumns<R>[] }一起作为一个单独的块分配.


幸运的是,将非分布式类型转换为分布式类型非常容易.你可以写distributive conditional type,就像

type DistribJ<R> = R extends unknown ?
  (Omit<JoinTable<R>, 'returnColumns'> & {
    returnColumns: JoinColumns<R>[]
  })[] : never

或者,因为R应该是类似键的,所以您可以编写distributive object type(在ms/TS#47109中创造),其中您map over R,然后立即index into映射的类型:

type DistribJ<R extends PropertyKey> = {
  [K in R]: (Omit<JoinTable<R>, 'returnColumns'> & {
    returnColumns: JoinColumns<R>[]
  })[] }[R]

如果你用上面的DistribJ替换J,你的代码就会开始工作.


为了进行演示,我将使用分布式对象版本,不使用中间的J类型,如下所示:

type Joins<T extends TableName> = {
  joins: ({ [K in RelatedTableName<T>]: (Omit<JoinTable<K>, 'returnColumns'> & {
    returnColumns: JoinColumns<K>[]
  }) }[RelatedTableName<T>])[]
};

现在一切都如你所愿.如果我们判断Joins<'role_permissions'>(如How can I see the full expanded contract of a Typescript type?中所述扩展类型),我们得到:

type JoinsRolePermissionsExplicit = 
  ExpandRecursively<Joins<'role_permissions'>>;
/* type JoinsRolePermissionsExplicit = {
    joins: ({
        tableName: "permissions";
        returnColumns: ("id" | "permission")[];
    } | {
        tableName: "roles";
        returnColumns: ("id" | "name")[];
    })[];
} */

这正是你想要的:

const combinedJoins: Joins<'role_permissions'> = {
  joins: [
    {
      tableName: 'roles',
      returnColumns: ['id', 'name', 'permission'] // error!
      //~~~~~~~~~~~
      // Type '("id" | "name" | "permission")[]' is not assignable to 
      // type '("id" | "name")[] | ("id" | "permission")[]'.
    },
    {
      tableName: 'permissions',
      returnColumns: ['id', 'name', 'permission'] // error!
      //~~~~~~~~~~~
      // Type '("id" | "name" | "permission")[]' is not assignable to 
      // type '("id" | "name")[] | ("id" | "permission")[]'.(2322)
    },
    {
      tableName: 'roles',
      returnColumns: ['id', 'name', 'uh-oh'] // error!
      //                            ~~~~~~~
      // Type '"uh-oh"' is not assignable to type '"id" | "name" | "permission"'.
    },
    {
      tableName: 'roles',
      returnColumns: ['id', 'name']  // okay
    },
    {
      tableName: 'roles',
      returnColumns: ['name']  // okay
    }
  ]
};

Playground link to code

Typescript相关问答推荐

类型脚本如何将嵌套的通用类型展开为父通用类型

带有联合参数的静态大小数组

Angular中的其他服务仅获取默认值

在泛型类型中对子代执行递归时出错

Next.js错误:不变:页面未生成

我想创建一个只需要一个未定义属性的打字脚本类型

如何表示多层深度的泛型类型数组?

是否可以基于对象的S属性值[]约束对象的其他属性值用于类型化的对象数组?

有没有可能产生输出T,它在视觉上省略了T中的一些键?

Angular文件上传到Spring Boot失败,多部分边界拒绝

将带点的对象键重新映射为具有保留值类型的嵌套对象

使用泛型识别对象值类型

TaskListProps[]类型不可分配给TaskListProps类型

为什么数字是`{[key:string]:UNKNOWN;}`的有效密钥?

Typescript循环函数引用

在对象数组中设置值

Typescript 从泛型推断类型

TS 排除带点符号的键

Typescript 子类继承的方法允许参数中未定义的键

类型为 `(callback: (...args: T) => void, params: T)` 的函数的泛型