我正在尝试将 Alpine.js 与 TypeScript 一起使用。为此,我使用社区维护的类型包 @types/alpinejs (GitHub) 以及此处描述的可重用组件设计模式。这是一个使用类型 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`.
},
}
}
正如您在上面的示例中所看到的,现在我首先使用我将使用的所有属性定义我的类型 Component = AlpineComponent{...}
。然后在实际构建我的组件时我必须再次输入它们。 我当前方法的缺点
Component
implementation。但实际上我总是对实现而不是定义更感兴趣。
AlpineComponent<T>
的动态部分推断从我的组件返回的对象的类型?
使用辅助函数来增强 Alpine.js 内部提供的功能就像一个魅力:
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)
}
}))
使用它我现在可以同时使用
使用 Alpine.js 内部结构而不会出现 TypeScript 错误