alpinejs
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAlpineJS Best Practices
AlpineJS最佳实践
Golden Rule: Keep Attributes Short
黄金准则:保持属性简短
Never put complex logic in HTML attributes. If your , , or any directive exceeds ~50 characters, extract it.
x-datax-init绝对不要在HTML属性中写入复杂逻辑。如果你的、或任何指令的内容超过约50个字符,请将其提取出来。
x-datax-initDirective Cheatsheet
指令速查表
| Directive | Purpose | Example |
|---|---|---|
| Declare reactive component state | |
| Run code on component init | |
| Toggle visibility (CSS display) | |
| Conditional rendering (must wrap | |
| Loop (must wrap | |
| Bind attribute to expression | |
| Listen to events | |
| Two-way bind form inputs | |
| Set text content | |
| Set inner HTML | |
| Reference element via | |
| Hide until Alpine initializes | |
| Apply enter/leave transitions | |
| Run reactive side effects | |
| Skip Alpine initialization | |
| Move element to another location | |
| Expose property for external binding | |
| 指令 | 用途 | 示例 |
|---|---|---|
| 声明响应式组件状态 | |
| 在组件初始化时运行代码 | |
| 切换可见性(CSS display属性) | |
| 条件渲染(必须包裹在 | |
| 循环渲染(必须包裹在 | |
| 将属性绑定到表达式 | |
| 监听事件 | |
| 表单输入双向绑定 | |
| 设置文本内容 | |
| 设置innerHTML | |
| 通过 | |
| 在Alpine初始化前隐藏元素 | |
| 应用进入/离开过渡效果 | |
| 运行响应式副作用 | |
| 跳过Alpine初始化 | |
| 将元素移动到另一个位置 | |
| 暴露属性供外部绑定 | |
Magic Properties
魔法属性
| Property | Description |
|---|---|
| Current DOM element |
| Access elements with |
| Access global Alpine stores |
| Watch a property for changes |
| Dispatch custom events |
| Run after DOM updates |
| Root element of component |
| Access component data object |
| Generate unique IDs |
| 属性 | 描述 |
|---|---|
| 当前DOM元素 |
| 访问带有 |
| 访问全局Alpine存储 |
| 监听属性变化 |
| 分发自定义事件 |
| 在DOM更新后运行代码 |
| 组件的根元素 |
| 访问组件数据对象 |
| 生成唯一ID |
Patterns
实践模式
❌ BAD: Long Inline JavaScript
❌ 错误示例:冗长的内联JavaScript
html
<!-- DON'T DO THIS -->
<div x-data="{ items: [], loading: true, error: null, async fetchItems() { this.loading = true; try { const res = await fetch('/api/items'); this.items = await res.json(); } catch (e) { this.error = e.message; } finally { this.loading = false; } } }" x-init="fetchItems()">html
<!-- DON'T DO THIS -->
<div x-data="{ items: [], loading: true, error: null, async fetchItems() { this.loading = true; try { const res = await fetch('/api/items'); this.items = await res.json(); } catch (e) { this.error = e.message; } finally { this.loading = false; } } }" x-init="fetchItems()">✅ GOOD: Extract to Function
✅ 正确示例:提取为函数
html
<script>
function itemList() {
return {
items: [],
loading: true,
error: null,
async fetchItems() {
this.loading = true;
try {
const res = await fetch('/api/items');
this.items = await res.json();
} catch (e) {
this.error = e.message;
} finally {
this.loading = false;
}
}
};
}
</script>
<div x-data="itemList()" x-init="fetchItems()">
<!-- template -->
</div>html
<script>
function itemList() {
return {
items: [],
loading: true,
error: null,
async fetchItems() {
this.loading = true;
try {
const res = await fetch('/api/items');
this.items = await res.json();
} catch (e) {
this.error = e.message;
} finally {
this.loading = false;
}
}
};
}
</script>
<div x-data="itemList()" x-init="fetchItems()">
<!-- template -->
</div>✅ GOOD: Simple Inline State
✅ 正确示例:简单内联状态
html
<!-- Simple state is fine inline -->
<div x-data="{ open: false, count: 0 }">
<button @click="open = !open">Toggle</button>
<div x-show="open" x-transition>Content</div>
</div>html
<!-- Simple state is fine inline -->
<div x-data="{ open: false, count: 0 }">
<button @click="open = !open">Toggle</button>
<div x-show="open" x-transition>Content</div>
</div>✅ GOOD: Global Store for Shared State
✅ 正确示例:使用全局存储管理共享状态
html
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('cart', {
items: [],
add(item) { this.items.push(item); },
get total() { return this.items.reduce((sum, i) => sum + i.price, 0); }
});
});
</script>
<div x-data>
<span x-text="$store.cart.total"></span>
</div>html
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('cart', {
items: [],
add(item) { this.items.push(item); },
get total() { return this.items.reduce((sum, i) => sum + i.price, 0); }
});
});
</script>
<div x-data>
<span x-text="$store.cart.total"></span>
</div>✅ GOOD: Reusable Component with Alpine.data()
✅ 正确示例:使用Alpine.data()创建可复用组件
html
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
open: false,
toggle() { this.open = !this.open; },
close() { this.open = false; }
}));
});
</script>
<div x-data="dropdown" @click.outside="close()">
<button @click="toggle()">Menu</button>
<ul x-show="open" x-transition>
<li>Item 1</li>
</ul>
</div>html
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
open: false,
toggle() { this.open = !this.open; },
close() { this.open = false; }
}));
});
</script>
<div x-data="dropdown" @click.outside="close()">
<button @click="toggle()">Menu</button>
<ul x-show="open" x-transition>
<li>Item 1</li>
</ul>
</div>✅ GOOD: Form with Validation
✅ 正确示例:带验证的表单
html
<script>
function contactForm() {
return {
email: '',
message: '',
errors: {},
validate() {
this.errors = {};
if (!this.email.includes('@')) this.errors.email = 'Invalid email';
if (this.message.length < 10) this.errors.message = 'Too short';
return Object.keys(this.errors).length === 0;
},
submit() {
if (this.validate()) {
// submit logic
}
}
};
}
</script>
<form x-data="contactForm()" @submit.prevent="submit()">
<input x-model="email" type="email">
<span x-show="errors.email" x-text="errors.email" class="error"></span>
<textarea x-model="message"></textarea>
<span x-show="errors.message" x-text="errors.message" class="error"></span>
<button type="submit">Send</button>
</form>html
<script>
function contactForm() {
return {
email: '',
message: '',
errors: {},
validate() {
this.errors = {};
if (!this.email.includes('@')) this.errors.email = 'Invalid email';
if (this.message.length < 10) this.errors.message = 'Too short';
return Object.keys(this.errors).length === 0;
},
submit() {
if (this.validate()) {
// submit logic
}
}
};
}
</script>
<form x-data="contactForm()" @submit.prevent="submit()">
<input x-model="email" type="email">
<span x-show="errors.email" x-text="errors.email" class="error"></span>
<textarea x-model="message"></textarea>
<span x-show="errors.message" x-text="errors.message" class="error"></span>
<button type="submit">Send</button>
</form>Event Modifiers
事件修饰符
html
@click.prevent <!-- preventDefault() -->
@click.stop <!-- stopPropagation() -->
@click.outside <!-- Click outside element -->
@click.window <!-- Listen on window -->
@click.document <!-- Listen on document -->
@click.once <!-- Fire once -->
@click.debounce <!-- Debounce (default 250ms) -->
@click.throttle <!-- Throttle -->
@keydown.enter <!-- Specific key -->
@keydown.escape <!-- Escape key -->html
@click.prevent <!-- preventDefault() -->
@click.stop <!-- stopPropagation() -->
@click.outside <!-- Click outside element -->
@click.window <!-- Listen on window -->
@click.document <!-- Listen on document -->
@click.once <!-- Fire once -->
@click.debounce <!-- Debounce (default 250ms) -->
@click.throttle <!-- Throttle -->
@keydown.enter <!-- Specific key -->
@keydown.escape <!-- Escape key -->Transition Modifiers
过渡修饰符
html
x-transition <!-- Default fade -->
x-transition.duration.300ms <!-- Custom duration -->
x-transition.opacity <!-- Opacity only -->
x-transition.scale.90 <!-- Scale from 90% -->
x-transition:enter.duration.500ms <!-- Enter duration -->
x-transition:leave.duration.200ms <!-- Leave duration -->html
x-transition <!-- Default fade -->
x-transition.duration.300ms <!-- Custom duration -->
x-transition.opacity <!-- Opacity only -->
x-transition.scale.90 <!-- Scale from 90% -->
x-transition:enter.duration.500ms <!-- Enter duration -->
x-transition:leave.duration.200ms <!-- Leave duration -->Quick Decision Guide
快速决策指南
- State is 1-3 simple properties? → Inline
x-data="{ open: false }" - Has methods or complex logic? → Extract to
function componentName() { return {...} } - Reused across pages? → Use
Alpine.data('name', () => ({...})) - Shared global state? → Use
Alpine.store('name', {...}) - Long attribute string? → You're doing it wrong. Extract it.
- 状态是1-3个简单属性? → 使用内联
x-data="{ open: false }" - 包含方法或复杂逻辑? → 提取为
function componentName() { return {...} } - 需要跨页面复用? → 使用
Alpine.data('name', () => ({...})) - 需要全局共享状态? → 使用
Alpine.store('name', {...}) - 属性字符串过长? → 你的做法有误,请提取出来。