samedi 25 mars 2023

Typescript: Use inferred type of object for use in Generic

The Context

I'm trying to use Alpine.js together with TypeScript. For that, I'm using the community-maintained typings package @types/alpinejs (GitHub) together with the re-useable components design pattern described here. Here is a simple example using the type AlpineComponent<T>:

// This is the type for alpine components from definitely typed
import { AlpineComponent } from "alpinejs";

/**
 * I have to declare the T-part of the AlpineComponent<T> beforehand,
 * to be able to use it later. Otherwise, all my custom properties
 * will have the type "any"
 */
type Component = AlpineComponent<{
  foo: string;
  greet: (to: string) => string;
  // The list goes on and on and on in real-world code
}>

export default (): Component => {
  return {
    foo: "foo", // type "string", as defined in the type `Component`
    bar: "bar", // inconveniently has the type "any", since I didn't declare it inside my type `Component`...will have to do it manually... đŸ˜©
    greet(to) {
      return `Hello ${to}!`;
    },
    /**
     * init() is being invoked automatically by Alpine.js when the component is mounted
     */
    async init() {
      console.log(this.greet("World")); // greet correctly has the return type "string"
      await this.$nextTick(); // this is a "magic" method from Alpine.js. It's defined in the type `AlpineComponent`.
    },
  }
}

As you can see in the above example, right now I first define my type Component = AlpineComponent{...} with all the properties I will be using. And then I have to type them again when actually building my component.

Downsides of my current approach

  1. Typing everything twice as I'm doing now is quite cumbersome and feels very conflated.
  2. On alt-click on any property, my IDE (VS Code) now always jumps to the definition in my custom type Component rather then to the implementation inside my actual code. But I'm actually always more interested in the implementation instead of the definition.

The actual Question

Is there a better way to organize this, with less repeated typing of properties? Maybe something like inferring the type of the object returned from my component for the dynamic part of AlpineComponent<T>?

Aucun commentaire:

Enregistrer un commentaire