繁体   English   中英

TypeScript 缩短通用 function 呼叫

[英]TypeScript shorten generic function call

我在节点14.17.6上使用Sequelize 6.6.5TypeScript 4.4.3

db/models/User.ts (仅相关部分)

import {
  Sequelize,
  Model,
  DataTypes,
  Optional,
  ModelAttributeColumnOptions,
} from 'sequelize';

import { FlagPositions, Flags } from '../types';

export interface IUserFlags {
  isDev: boolean;
  isAdmin: boolean;
  isMod: boolean;
}

export const flagPositions: FlagPositions<IUserFlags> = {
  isDev: 0,
  isAdmin: 1,
  isMod: 2,
};

export interface IUserAttributes extends Flags, IUserFlags {
  id: number;

  name: string;

  email: string;

  // ...
}

export interface IUserCreationAttributes
  extends Optional<
    IUserAttributes,
    | 'id'
    | 'flags'
    | 'isDev'
    | 'isAdmin'
    | 'isMod'
  > {}

export class User
  extends Model<IUserAttributes, IUserCreationAttributes>
  implements IUserAttributes
{
  public id!: number;

  public name!: string;

  public email!: string;

  public flags!: number;

  public isDev!: boolean;
  public isAdmin!: boolean;
  public isMod!: boolean;

  // ...
}

export function init(sequelize: Sequelize): void {
  User.init(
    {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true,
        unique: true,
      },
      name: {
        type: DataTypes.STRING,
        allowNull: false,
        unique: true,
      },
      email: {
        type: DataTypes.STRING,
        allowNull: false,
        unique: true,
      },
      // ...
      flags: {
        type: DataTypes.INTEGER,
        defaultValue: 0,
      },
      isDev: {
        type: DataTypes.VIRTUAL,
        get(): boolean {
          return !!((this.flags >> flagPositions.isDev) & 1);
        },
        set(value: boolean): void {
          const flags = this.getDataValue('flags');

          const mask = 1 << flagPositions.isDev;

          this.setDataValue('flags', value ? flags | mask : flags & ~mask);
        },
      },
      isAdmin: {
        type: DataTypes.VIRTUAL,
        get(): boolean {
          return !!((this.flags >> flagPositions.isAdmin) & 1);
        },
        set(value: boolean): void {
          const flags = this.getDataValue('flags');

          const mask = 1 << flagPositions.isAdmin;

          this.setDataValue('flags', value ? flags | mask : flags & ~mask);
        },
      },
      isMod: {
        type: DataTypes.VIRTUAL,
        get(): boolean {
          return !!((this.flags >> flagPositions.isMod) & 1);
        },
        set(value: boolean): void {
          const flags = this.getDataValue('flags');

          const mask = 1 << flagPositions.isMod;

          this.setDataValue('flags', value ? flags | mask : flags & ~mask);
        },
      },
    },
    { sequelize, tableName: 'users' },
  );
}

../types

interface Flags {
  flags: number;
}

type FlagPositions<F extends Record<keyof F, boolean>> = {
  [K in keyof F]: number;
};

如您所见,标记 virtuals 的代码基本上是重复的 (WET)。

我想要这样的东西

isDev: createFlagVirtual<IUserFlags>(flagPositions, 'isDev');

在列出一长串错误之后,我想出了

function createFlagVirtual<
  TModelAttributes extends Flags,
  TModelCreationAttributes extends Partial<TModelAttributes>,
  TModel extends Model<TModelAttributes, TModelCreationAttributes> &
    TModelAttributes,
  TFlags extends Record<keyof TFlags, boolean>,
  TFlag extends keyof TFlags = keyof TFlags,
>(
  positions: FlagPositions<TFlags>,
  flag: TFlag,
): ModelAttributeColumnOptions<TModel> {
  return {
    type: DataTypes.VIRTUAL,
    get(): boolean {
      return !!((this.flags >> positions[flag]) & 1);
    },
    set(value: boolean): void {
      const flags = this.getDataValue('flags');

      const mask = 1 << positions[flag];

      this.setDataValue('flags', value ? flags | mask : flags & ~mask);
    },
  };
}

想知道我怎么称呼它吗?:

createFlagVirtual<IUserAttributes, IUserCreationAttributes, User, IUserFlags>(
  flagPositions,
  'isMod',
);

是的; 不那么干。

最重要的是,typescript 仍然不知道可以将'flags'传递给this.getDataValue ,至少根据 vscode 智能感知。 但是,它知道直接访问this.flags是一个number

我想知道如何缩短对createFlagVirtual的调用,以便以下工作createFlagVirtual<IUserFlags>(flagPositions, 'isDev'); . TypeScript 应该知道可以传递给第二个参数的内容,也就是keyofIUserFlags 此外,在实现中, this.getDataValue应该知道'flags'是一个值。

我自己弄明白了

function createFlagVirtual<
  TFlags extends Record<keyof TFlags, boolean>,
  TModel extends Model<Flags, {}> & Flags = Model<Flags, {}> & Flags,
>(
  positions: FlagPositions<TFlags>,
  name: keyof TFlags,
): ModelAttributeColumnOptions<TModel> {
  return {
    type: DataTypes.VIRTUAL,
    get(): boolean {
      return !!((this.getDataValue('flags') >> positions[name]) & 1);
    },
    set(value: boolean): void {
      const flags = this.getDataValue('flags');

      const mask = 1 << positions[name];

      this.setDataValue('flags', value ? flags | mask : flags & ~mask);
    },
  };
}

用作

createFlagVirtual<IUserFlags>(flagPositions, 'isDev')
// or
createFlagVirtual<IUserFlags, User>(flagPositions, 'isDev'),

关键部分是TModel extends Model<Flags, {}> & Flags = Model<Flags, {}> & Flags

Model<Flags, {}>允许this.getDataValue知道'flags'是允许的值,而& Flags让 typescript 知道直接访问this.flags是一个number

编辑:这表明 TypeScript 可能是一团糟,以及它如何通过让程序员专注于最终无关紧要的事情来实际减慢开发速度。 我再次将我的代码库移至 vanilla javascript。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM