Tìm kiếm trang web

mẫu thiết kế - Typescript: Sử dụng kiểu suy luận của đối tượng để sử dụng trong Generic


Ngữ cảnh

Tôi đang cố gắng sử dụng Alpine.js cùng với TypeScript. Để làm điều đó, tôi đang sử dụng gói kiểu do cộng đồng duy trì @types/alpinejs (GitHub) cùng với mẫu thiết kế các thành phần có thể tái sử dụng được mô tả ở đây. Dưới đây là một ví dụ đơn giản sử dụng kiểu 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`.
    },
  }
}

Như bạn có thể thấy trong ví dụ trên, ngay bây giờ tôi đầu tiên định nghĩa kiểu của mình Component=AlpineComponent{...} với tất cả các thuộc tính mà tôi sẽ sử dụng. Và sau đó tôi phải định kiểu chúng lại khi thực sự xây dựng component của mình.

Những mặt hạn chế của phương pháp hiện tại của tôi

  1. Gõ tất cả mọi thứ hai lần như tôi đang làm bây giờ khá phiền phức và cảm thấy rất rối rắm.
  2. Khi alt-click vào bất kỳ thuộc tính nào, IDE của tôi (VS Code) bây giờ luôn nhảy đến định nghĩa trong kiểu tùy chỉnh Component của tôi thay vì đến cài đặt bên trong mã thực tế của tôi. Nhưng thực ra tôi luôn quan tâm nhiều hơn đến cài đặt thay vì định nghĩa.

Câu hỏi thực sự

Có cách nào tốt hơn để sắp xếp điều này, với ít việc gõ lại các thuộc tính hơn không? Có lẽ điều gì đó giống như suy ra kiểu của đối tượng được trả về từ component của tôi cho phần động của AlpineComponent<T>?

Câu trả lời #1

Ai đó trên GitHub đã nghĩ ra chính xác những gì tôi đang tìm kiếm:

https://github.com/alpinejs/alpine/issues/2199#issuecomment-1809892127

Sử dụng một hàm trợ giúp để tăng cường hàm đã cung cấp với các thành phần nội bộ của Alpine.js hoạt động rất hiệu quả:

utils.ts:

import type { AlpineComponent } from 'alpinejs'

export const defineComponent = <P, T>(fn: (params: P) => AlpineComponent<T>) => fn

component.ts:

import { defineComponent } from '~/utils/define-component'

export default defineComponent(() => ({
  isOpen: false,
  init() {
    this.$watch('isOpen', (value) => {
      this.onIsOpenChange(value)
    })
  },
  onIsOpenChange(value: boolean) {
    console.log('isOpen changed to ', value)
  }
}))

Sử dụng điều đó, giờ tôi có thể làm cả hai

  1. tận dụng các phần nội bộ của Alpine.js mà không bị lỗi TypeScript
  2. để phần thực thi (chức năng tùy chỉnh) được suy luận từ bên trong chính thành phần