动态声明TypeScript的Stimulus目标类型

3
我正在使用TypeScript编写刺激控制器,但我觉得肯定有更好的方法来声明所有的has*Target/*Target/*Targets属性,而不是一个个地声明。
有人知道我在寻找的东西是否可能吗?提前感谢。
这是一些我一直在尝试让它工作的代码。
import { Controller } from '@hotwired/stimulus';

namespace Transform {
  export type HasTarget<T> = {
    [K in keyof T as `has${Capitalize<string & K>}Target`]: boolean;
  };
  export type Target<T> = {
    [K in keyof T as `${string & K}Target`]: T[K];
  };
  export type Targets<T> = {
    [K in keyof T as `${string & K}Targets`]: Array<T[K]>;
  };
}

const targets = [
  'button',
  'container',
] as const;

type TargetList = typeof targets;

interface Targets extends Record<TargetList[number], HTMLElement> {
  button: HTMLButtonElement;
  container: HTMLDivElement;
}

type ControllerWithTarget<T> =
  & Transform.HasTarget<T>
  & Transform.Target<T>
  & Transform.Targets<T>;


// Trying to achieve something like this
export default class extends Controller implements ControllerWithTarget<Targets> {
  static targets = [...targets];

  // declare readonly hasButtonTarget: boolean;
  // declare readonly buttonTarget: HTMLButtonElement;
  // declare readonly buttonTargets: Array<HTMLButtonElement>;

  // declare readonly hasContainerTarget: boolean;
  // declare readonly containerTarget: HTMLDivElement;
  // declare readonly containerTargets: Array<HTMLDivElement>;

  disableButton(): void {
    if ( this.hasButtonTarget ) {
      this.buttonTarget.disabled = true;
    }
  }
}

编辑编译器报错:类 'default' 不能正确实现接口 'ControllerWithTarget<Targets>'。


你有没有考虑过使用工厂模式? - 0xts
我以前从来没有真正使用过工厂。你能提供一个例子吗? - M. Wyatt
2个回答

0
这是一种在TypeScript中通用声明Stimulus控制器的has/target/targets属性的方法:
import { Controller } from '@hotwired/stimulus'

type TargetName = 'button' | 'container';

const targets = [
  'button',
  'container'
] as const;

interface Targets {
  [key in TargetName]: HTMLElement;
}

type ControllerProps<T extends TargetName> = {
  [P in `has${Capitalize<string & T>}Target`]: boolean;
} & {
  [P in `${T}Target`]: Targets[T];
} & {
  [P in `${T}Targets`]: Targets[T][];
};

export default class extends Controller {
  static targets = targets;

  constructor() {
    super();

    for (let target of targets) {
      this[`${target}Target`] = null; 
      this[`${target}Targets`] = null;
      this[`has${capitalize(target)}Target`] = false;
    }
  }

  private getProps<T extends TargetName>(): ControllerProps<T> {
    return {
      // extra props here
    }
  }
}

关键要点:
定义目标和属性的类型 在初始化时循环遍历目标并设置属性 使用泛型为每个目标获取强类型属性 这样就不需要显式声明每个属性。

有几个问题:Targets 接口以及 for 循环都无法编译通过。这导致了不同的 *Target/*Targets 无法成为不同的类型(例如 HTMLButtonElementHTMLElement)。在 Stimulus 中,你不能以这种方式设置 *Target 等属性,因为它们是 getter 而不仅仅是属性。 - M. Wyatt

-1
这是一个我将如何实施的示例:
import { Controller } from '@hotwired/stimulus';

namespace Transform {
  export type HasTarget<T> = {
    [K in keyof T as `has${Capitalize<string & K>}Target`]: boolean;
  };
  export type Target<T> = {
    [K in keyof T as `${string & K}Target`]: T[K];
  };
  export type Targets<T> = {
    [K in keyof T as `${string & K}Targets`]: Array<T[K]>;
  };
}

const targets = [
  'button',
  'container',
] as const;

interface Targets extends Record<typeof targets[number], HTMLElement> {
  button: HTMLButtonElement;
  container: HTMLDivElement;
}

type ControllerWithTarget<T> =
  & Transform.HasTarget<T>
  & Transform.Target<T>
  & Transform.Targets<T>;

export default class extends Controller implements ControllerWithTarget<Targets> {
  static targets = [...targets];

  disableButton(): void {
    if (this.hasButtonTarget) {
      this.buttonTarget.disabled = true;
    }
  }
}

这个代码跟我的代码99%一样。唯一的区别是删除了注释并将TargetList类型内联了进去。TS说Class 'default' incorrectly implements interface 'ControllerWithTarget<Targets>'. - M. Wyatt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接