项目结构 #

概述 #

良好的项目结构是构建可维护 Alpine.js 应用的基础。本章将介绍推荐的项目组织方式。

基础项目结构 #

CDN 项目 #

text
project/
├── index.html
├── css/
│   └── style.css
├── js/
│   ├── alpine-init.js
│   ├── components/
│   │   ├── dropdown.js
│   │   ├── modal.js
│   │   └── tabs.js
│   └── stores/
│       ├── user.js
│       └── cart.js
└── assets/
    └── images/

NPM 项目 #

text
project/
├── src/
│   ├── index.js
│   ├── components/
│   │   ├── index.js
│   │   ├── dropdown.js
│   │   ├── modal.js
│   │   └── tabs.js
│   ├── stores/
│   │   ├── index.js
│   │   ├── user.js
│   │   └── cart.js
│   ├── plugins/
│   │   └── my-plugin.js
│   └── utils/
│       └── helpers.js
├── public/
│   └── index.html
├── package.json
└── vite.config.js

组件组织 #

单文件组件 #

javascript
export function dropdown() {
    return {
        open: false,
        
        toggle() {
            this.open = !this.open
        },
        
        close() {
            this.open = false
        }
    }
}

export function dropdownTemplate() {
    return `
        <div x-data="dropdown()" class="dropdown">
            <button @click="toggle()">菜单</button>
            <div x-show="open" @click.away="close()">
                <slot></slot>
            </div>
        </div>
    `
}

组件注册 #

javascript
import { dropdown } from './components/dropdown'
import { modal } from './components/modal'
import { tabs } from './components/tabs'

document.addEventListener('alpine:init', () => {
    Alpine.data('dropdown', dropdown)
    Alpine.data('modal', modal)
    Alpine.data('tabs', tabs)
})

组件索引 #

javascript
export { dropdown } from './dropdown'
export { modal } from './modal'
export { tabs } from './tabs'

export function registerComponents(Alpine) {
    Alpine.data('dropdown', dropdown)
    Alpine.data('modal', modal)
    Alpine.data('tabs', tabs)
}

Store 组织 #

单个 Store 文件 #

javascript
export const userStore = {
    data: null,
    token: null,
    
    get isAuthenticated() {
        return !!this.token && !!this.data
    },
    
    async login(credentials) {
        const res = await fetch('/api/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(credentials)
        })
        const data = await res.json()
        this.token = data.token
        this.data = data.user
        localStorage.setItem('token', data.token)
    },
    
    logout() {
        this.data = null
        this.token = null
        localStorage.removeItem('token')
    }
}

export function registerUserStore(Alpine) {
    Alpine.store('user', userStore)
}

Store 索引 #

javascript
import { registerUserStore } from './user'
import { registerCartStore } from './cart'
import { registerNotificationStore } from './notification'

export function registerStores(Alpine) {
    registerUserStore(Alpine)
    registerCartStore(Alpine)
    registerNotificationStore(Alpine)
}

入口文件 #

完整入口示例 #

javascript
import Alpine from 'alpinejs'
import { registerComponents } from './components'
import { registerStores } from './stores'
import { registerPlugins } from './plugins'

window.Alpine = Alpine

document.addEventListener('alpine:init', () => {
    registerComponents(Alpine)
    registerStores(Alpine)
    registerPlugins(Alpine)
})

Alpine.start()

Laravel 集成 #

Blade 组件 #

html
@props(['open' => false])

<div 
    x-data="{ open: {{ $open ? 'true' : 'false' }} }"
    class="dropdown"
>
    <button @click="open = !open">
        {{ $slot->title }}
    </button>
    
    <div x-show="open" @click.away="open = false">
        {{ $slot->content }}
    </div>
</div>

使用组件 #

html
<x-dropdown>
    <x-slot:title>
        菜单
    </x-slot:title>
    
    <x-slot:content>
        <a href="#">选项 1</a>
        <a href="#">选项 2</a>
    </x-slot:content>
</x-dropdown>

Rails 集成 #

ERB 组件 #

erb
<div 
    x-data="{ open: <%= open ? 'true' : 'false' %> }"
    class="dropdown"
>
    <button @click="open = !open">
        <%= title %>
    </button>
    
    <div x-show="open" @click.away="open = false">
        <%= content %>
    </div>
</div>

命名约定 #

组件命名 #

javascript
Alpine.data('userDropdown', () => ({ }))
Alpine.data('productCard', () => ({ }))
Alpine.data('searchInput', () => ({ }))

Store 命名 #

javascript
Alpine.store('user', { })
Alpine.store('cart', { })
Alpine.store('notification', { })

事件命名 #

javascript
$dispatch('user:login')
$dispatch('user:logout')
$dispatch('cart:add')
$dispatch('cart:remove')

目录结构示例 #

中型项目 #

text
project/
├── src/
│   ├── index.js
│   ├── components/
│   │   ├── ui/
│   │   │   ├── button.js
│   │   │   ├── input.js
│   │   │   └── modal.js
│   │   ├── forms/
│   │   │   ├── contact-form.js
│   │   │   └── search-form.js
│   │   └── layout/
│   │       ├── header.js
│   │       ├── sidebar.js
│   │       └── footer.js
│   ├── stores/
│   │   ├── user.js
│   │   ├── cart.js
│   │   └── ui.js
│   ├── plugins/
│   │   ├── persist.js
│   │   └── toast.js
│   ├── utils/
│   │   ├── api.js
│   │   ├── format.js
│   │   └── validation.js
│   └── styles/
│       └── main.css
└── public/
    └── index.html

大型项目 #

text
project/
├── src/
│   ├── index.js
│   ├── modules/
│   │   ├── auth/
│   │   │   ├── components/
│   │   │   │   ├── login-form.js
│   │   │   │   └── register-form.js
│   │   │   ├── stores/
│   │   │   │   └── auth.js
│   │   │   └── index.js
│   │   ├── products/
│   │   │   ├── components/
│   │   │   │   ├── product-card.js
│   │   │   │   └── product-list.js
│   │   │   ├── stores/
│   │   │   │   └── products.js
│   │   │   └── index.js
│   │   └── cart/
│   │       ├── components/
│   │       │   ├── cart-item.js
│   │       │   └── cart-summary.js
│   │       ├── stores/
│   │       │   └── cart.js
│   │       └── index.js
│   ├── shared/
│   │   ├── components/
│   │   ├── stores/
│   │   └── utils/
│   └── plugins/
└── public/

最佳实践 #

1. 按功能组织 #

text
features/
├── auth/
│   ├── components/
│   ├── stores/
│   └── utils/
├── products/
└── cart/

2. 保持组件独立 #

javascript
export function counter(initial = 0) {
    return {
        count: initial,
        increment() { this.count++ },
        decrement() { this.count-- }
    }
}

3. 使用索引文件 #

javascript
export * from './components'
export * from './stores'
export * from './utils'

4. 配置外部化 #

javascript
const config = {
    apiUrl: import.meta.env.VITE_API_URL,
    timeout: 5000
}

export function useApi() {
    return {
        async fetch(endpoint) {
            return fetch(config.apiUrl + endpoint)
        }
    }
}

小结 #

项目结构要点:

  • 按功能模块组织代码
  • 使用索引文件简化导入
  • 保持组件独立性
  • 统一命名约定
  • 适配后端框架

下一章,我们将学习 Alpine.js 与 TypeScript 集成。

最后更新:2026-03-28