Alpine.js与TypeScript #
概述 #
虽然 Alpine.js 本身是用 JavaScript 编写的,但可以与 TypeScript 配合使用,获得类型安全和更好的 IDE 支持。
基础配置 #
安装依赖 #
bash
npm install alpinejs
npm install -D typescript @types/node
tsconfig.json #
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
类型定义 #
组件类型 #
typescript
interface CounterData {
count: number
increment(): void
decrement(): void
reset(): void
}
function counter(initial: number = 0): CounterData {
return {
count: initial,
increment() { this.count++ },
decrement() { this.count-- },
reset() { this.count = initial }
}
}
Store 类型 #
typescript
interface User {
id: number
name: string
email: string
}
interface UserStore {
data: User | null
token: string | null
isAuthenticated: boolean
login(credentials: { email: string; password: string }): Promise<boolean>
logout(): void
}
declare global {
interface Window {
Alpine: typeof Alpine
}
}
类型安全的组件 #
定义组件类型 #
typescript
interface Todo {
id: number
text: string
completed: boolean
}
interface TodoListData {
todos: Todo[]
newTodo: string
addTodo(): void
removeTodo(id: number): void
toggleTodo(id: number): void
get activeTodos(): Todo[]
get completedTodos(): Todo[]
}
export function todoList(): TodoListData {
return {
todos: [],
newTodo: '',
addTodo() {
if (this.newTodo.trim()) {
this.todos.push({
id: Date.now(),
text: this.newTodo.trim(),
completed: false
})
this.newTodo = ''
}
},
removeTodo(id: number) {
this.todos = this.todos.filter(t => t.id !== id)
},
toggleTodo(id: number) {
const todo = this.todos.find(t => t.id === id)
if (todo) todo.completed = !todo.completed
},
get activeTodos() {
return this.todos.filter(t => !t.completed)
},
get completedTodos() {
return this.todos.filter(t => t.completed)
}
}
}
注册组件 #
typescript
import Alpine from 'alpinejs'
declare global {
interface Window {
Alpine: typeof Alpine
}
}
window.Alpine = Alpine
document.addEventListener('alpine:init', () => {
Alpine.data('todoList', todoList)
})
Alpine.start()
类型安全的 Store #
定义 Store #
typescript
interface CartItem {
id: number
name: string
price: number
quantity: number
}
interface CartStore {
items: CartItem[]
add(product: Omit<CartItem, 'quantity'>): void
remove(productId: number): void
updateQuantity(productId: number, quantity: number): void
clear(): void
readonly total: number
readonly count: number
}
const cartStore: CartStore = {
items: [],
add(product) {
const existing = this.items.find(i => i.id === product.id)
if (existing) {
existing.quantity++
} else {
this.items.push({ ...product, quantity: 1 })
}
},
remove(productId) {
this.items = this.items.filter(i => i.id !== productId)
},
updateQuantity(productId, quantity) {
const item = this.items.find(i => i.id === productId)
if (item) {
item.quantity = Math.max(0, quantity)
if (item.quantity === 0) {
this.remove(productId)
}
}
},
clear() {
this.items = []
},
get total() {
return this.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
},
get count() {
return this.items.reduce((sum, i) => sum + i.quantity, 0)
}
}
document.addEventListener('alpine:init', () => {
Alpine.store('cart', cartStore)
})
泛型组件 #
创建泛型组件 #
typescript
interface SelectOption<T> {
value: T
label: string
}
interface SelectData<T> {
options: SelectOption<T>[]
selected: T | null
isOpen: boolean
select(value: T): void
toggle(): void
close(): void
}
function select<T>(options: SelectOption<T>[]): SelectData<T> {
return {
options,
selected: null,
isOpen: false,
select(value: T) {
this.selected = value
this.close()
},
toggle() {
this.isOpen = !this.isOpen
},
close() {
this.isOpen = false
}
}
}
使用泛型组件 #
typescript
interface User {
id: number
name: string
}
const userSelect = select<User>([
{ value: { id: 1, name: 'John' }, label: 'John' },
{ value: { id: 2, name: 'Jane' }, label: 'Jane' }
])
类型声明文件 #
alpine.d.ts #
typescript
import 'alpinejs'
declare module 'alpinejs' {
interface Alpine {
data(name: string, component: () => unknown): void
store(name: string, state: unknown): void
plugin(plugin: (Alpine: Alpine) => void): void
magic<T>(name: string, callback: (el: Element, options: { Alpine: Alpine }) => T): void
directive(name: string, callback: DirectiveCallback): void
}
interface DirectiveCallback {
(el: HTMLElement, directive: Directive, utils: Utilities): void
}
interface Directive {
value: string
expression: string
modifiers: string[]
}
interface Utilities {
effect(callback: () => void): void
evaluateLater(expression: string): () => unknown
cleanup(callback: () => void): void
}
}
declare global {
interface Window {
Alpine: typeof import('alpinejs').default
}
}
export {}
实用类型 #
组件类型工具 #
typescript
type ComponentData<T> = T & {
init?(): void
destroy?(): void
}
type StoreData<T> = T & {
init?(): void
}
function defineComponent<T extends object>(data: ComponentData<T>): ComponentData<T> {
return data
}
function defineStore<T extends object>(data: StoreData<T>): StoreData<T> {
return data
}
使用示例 #
typescript
const counter = defineComponent({
count: 0,
increment() {
this.count++
},
init() {
console.log('Counter initialized')
}
})
const user = defineStore({
data: null as User | null,
get isAuthenticated() {
return !!this.data
}
})
API 类型 #
类型安全的 API 调用 #
typescript
interface ApiResponse<T> {
data: T
status: number
message: string
}
interface User {
id: number
name: string
email: string
}
interface ApiClient {
get<T>(url: string): Promise<ApiResponse<T>>
post<T>(url: string, data: unknown): Promise<ApiResponse<T>>
put<T>(url: string, data: unknown): Promise<ApiResponse<T>>
delete<T>(url: string): Promise<ApiResponse<T>>
}
const api: ApiClient = {
async get<T>(url: string) {
const res = await fetch(url)
return res.json()
},
async post<T>(url: string, data: unknown) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
return res.json()
},
async put<T>(url: string, data: unknown) {
const res = await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
return res.json()
},
async delete<T>(url: string) {
const res = await fetch(url, { method: 'DELETE' })
return res.json()
}
}
async function fetchUsers(): Promise<User[]> {
const response = await api.get<User[]>('/api/users')
return response.data
}
最佳实践 #
1. 接口优先 #
typescript
interface FormData {
name: string
email: string
message: string
}
interface FormComponent {
data: FormData
errors: Partial<Record<keyof FormData, string>>
submit(): Promise<void>
}
2. 使用类型断言 #
typescript
const userStore = Alpine.store('user') as UserStore
3. 泛型复用 #
typescript
interface AsyncData<T> {
data: T | null
loading: boolean
error: string | null
fetch(): Promise<void>
}
function createAsyncData<T>(fetcher: () => Promise<T>): AsyncData<T> {
return {
data: null,
loading: false,
error: null,
async fetch() {
this.loading = true
try {
this.data = await fetcher()
} catch (e) {
this.error = (e as Error).message
} finally {
this.loading = false
}
}
}
}
4. 严格模式 #
json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
小结 #
TypeScript 集成要点:
- 定义组件和 Store 接口
- 使用泛型创建可复用组件
- 创建类型声明文件
- 使用类型安全的 API 调用
- 启用严格模式获得更好的类型检查
下一章,我们将学习测试策略。
最后更新:2026-03-28