mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 12:31:22 -05:00
459 lines
10 KiB
Markdown
459 lines
10 KiB
Markdown
# Alpine.js Optimization Strategies and Best Practices
|
|
|
|
## Research Summary
|
|
Comprehensive research from Alpine.js documentation focusing on performance optimization, component patterns, and best practices for the ThrillWiki frontend redesign.
|
|
|
|
## Performance Optimization Strategies
|
|
|
|
### 1. Component Initialization and Lifecycle
|
|
|
|
#### Efficient Component Registration
|
|
```javascript
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('dropdown', () => ({
|
|
open: false,
|
|
toggle() {
|
|
this.open = !this.open
|
|
},
|
|
destroy() {
|
|
// Clean up resources to prevent memory leaks
|
|
clearInterval(this.timer);
|
|
}
|
|
}))
|
|
})
|
|
```
|
|
|
|
#### Performance Measurement
|
|
```javascript
|
|
window.start = performance.now();
|
|
document.addEventListener('alpine:initialized', () => {
|
|
setTimeout(() => {
|
|
console.log(performance.now() - window.start);
|
|
});
|
|
});
|
|
```
|
|
|
|
### 2. Event Handling Optimization
|
|
|
|
#### Use .passive Modifier for Scroll Performance
|
|
```html
|
|
<div @touchstart.passive="...">...</div>
|
|
<div @wheel.passive="...">...</div>
|
|
```
|
|
**Critical**: The `.passive` modifier prevents blocking the browser's scroll optimizations by indicating the listener won't call `preventDefault()`.
|
|
|
|
#### Debounced Event Handling
|
|
```html
|
|
<input @input.debounce.300ms="search()" placeholder="Search...">
|
|
```
|
|
|
|
#### Efficient Event Delegation
|
|
```html
|
|
<div @click.outside="open = false" x-data="{ open: false }">
|
|
<!-- Content -->
|
|
</div>
|
|
```
|
|
|
|
### 3. Data Management Optimization
|
|
|
|
#### Minimize Reactive Data
|
|
```javascript
|
|
Alpine.data('app', () => ({
|
|
// Only make data reactive if it needs to trigger UI updates
|
|
items: [], // Reactive - triggers UI updates
|
|
_cache: {}, // Non-reactive - use for internal state
|
|
|
|
get filteredItems() {
|
|
// Use getters for computed properties
|
|
return this.items.filter(item => item.active)
|
|
}
|
|
}))
|
|
```
|
|
|
|
#### Efficient Array Operations
|
|
```javascript
|
|
// Good: Use array methods that don't trigger full re-renders
|
|
this.items.splice(index, 1); // Remove specific item
|
|
this.items.push(newItem); // Add item
|
|
|
|
// Avoid: Full array replacement when possible
|
|
// this.items = this.items.filter(...) // Triggers full re-render
|
|
```
|
|
|
|
### 4. DOM Manipulation Optimization
|
|
|
|
#### Use x-show vs x-if Strategically
|
|
```html
|
|
<!-- Use x-show for frequently toggled content -->
|
|
<div x-show="isVisible" x-transition>
|
|
Frequently toggled content
|
|
</div>
|
|
|
|
<!-- Use x-if for conditionally rendered content -->
|
|
<template x-if="shouldRender">
|
|
<div>Rarely shown content</div>
|
|
</template>
|
|
```
|
|
|
|
#### Optimize x-for Loops
|
|
```html
|
|
<!-- Always use :key for efficient list updates -->
|
|
<template x-for="item in items" :key="item.id">
|
|
<div x-text="item.name"></div>
|
|
</template>
|
|
```
|
|
|
|
#### Minimize DOM Queries
|
|
```javascript
|
|
Alpine.data('component', () => ({
|
|
init() {
|
|
// Cache DOM references in init()
|
|
this.element = this.$el;
|
|
this.container = this.$refs.container;
|
|
}
|
|
}))
|
|
```
|
|
|
|
### 5. Memory Management
|
|
|
|
#### Proper Cleanup in destroy()
|
|
```javascript
|
|
Alpine.data('timer', () => ({
|
|
timer: null,
|
|
counter: 0,
|
|
|
|
init() {
|
|
this.timer = setInterval(() => {
|
|
console.log('Increased counter to', ++this.counter);
|
|
}, 1000);
|
|
},
|
|
|
|
destroy() {
|
|
// Critical: Clean up to prevent memory leaks
|
|
clearInterval(this.timer);
|
|
}
|
|
}))
|
|
```
|
|
|
|
#### Avoid Memory Leaks in Event Listeners
|
|
```javascript
|
|
Alpine.data('component', () => ({
|
|
init() {
|
|
// Use arrow functions to maintain context
|
|
this.handleResize = () => this.onResize();
|
|
window.addEventListener('resize', this.handleResize);
|
|
},
|
|
|
|
destroy() {
|
|
window.removeEventListener('resize', this.handleResize);
|
|
}
|
|
}))
|
|
```
|
|
|
|
## Component Architecture Patterns
|
|
|
|
### 1. Reusable Component Registration
|
|
|
|
#### Global Component Registration
|
|
```javascript
|
|
Alpine.data('dropdown', () => ({
|
|
open: false,
|
|
|
|
toggle() {
|
|
this.open = !this.open
|
|
},
|
|
|
|
// Encapsulate directive logic
|
|
trigger: {
|
|
['@click']() {
|
|
this.toggle()
|
|
}
|
|
},
|
|
|
|
dialogue: {
|
|
['x-show']() {
|
|
return this.open
|
|
}
|
|
}
|
|
}))
|
|
```
|
|
|
|
#### Usage in Templates
|
|
```html
|
|
<div x-data="dropdown">
|
|
<button x-bind="trigger">Toggle</button>
|
|
<div x-bind="dialogue">Content</div>
|
|
</div>
|
|
```
|
|
|
|
### 2. State Management Patterns
|
|
|
|
#### Hierarchical Data Access
|
|
```html
|
|
<div x-data="{ open: false }">
|
|
<div x-data="{ label: 'Content:' }">
|
|
<span x-text="label"></span>
|
|
<span x-show="open"></span> <!-- Accesses parent's open -->
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
#### Centralized State with $store
|
|
```javascript
|
|
Alpine.store('app', {
|
|
user: null,
|
|
parks: [],
|
|
|
|
setUser(user) {
|
|
this.user = user
|
|
},
|
|
|
|
addPark(park) {
|
|
this.parks.push(park)
|
|
}
|
|
})
|
|
```
|
|
|
|
### 3. Advanced Interaction Patterns
|
|
|
|
#### Custom Event Dispatching
|
|
```html
|
|
<div @park-created="handleParkCreated">
|
|
<button @click="$dispatch('park-created', { park: newPark })">
|
|
Create Park
|
|
</button>
|
|
</div>
|
|
```
|
|
|
|
#### Intersection Observer Integration
|
|
```html
|
|
<div x-data="{ shown: false }" x-intersect="shown = true">
|
|
<div x-show="shown" x-transition>
|
|
I'm in the viewport!
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
#### Watch for Reactive Updates
|
|
```html
|
|
<div x-data="{ search: '' }"
|
|
x-init="$watch('search', value => console.log('Search:', value))">
|
|
<input x-model="search" placeholder="Search...">
|
|
</div>
|
|
```
|
|
|
|
## Integration with HTMX
|
|
|
|
### 1. Complementary Usage Patterns
|
|
|
|
#### HTMX for Server Communication, Alpine for Client State
|
|
```html
|
|
<div x-data="{ loading: false, count: 0 }">
|
|
<button hx-post="/increment"
|
|
hx-target="#result"
|
|
@click="loading = true; count++"
|
|
@htmx:after-request="loading = false">
|
|
<span x-show="!loading">Increment</span>
|
|
<span x-show="loading">Loading...</span>
|
|
</button>
|
|
|
|
<div id="result" x-text="'Local count: ' + count"></div>
|
|
</div>
|
|
```
|
|
|
|
#### Coordinated State Updates
|
|
```html
|
|
<div x-data="{ items: [] }"
|
|
@item-added.window="items.push($event.detail)">
|
|
|
|
<form hx-post="/items"
|
|
hx-target="#items-list"
|
|
@htmx:after-request="$dispatch('item-added', { item: newItem })">
|
|
<!-- Form fields -->
|
|
</form>
|
|
|
|
<div id="items-list"></div>
|
|
</div>
|
|
```
|
|
|
|
### 2. Performance Coordination
|
|
|
|
#### Efficient DOM Updates
|
|
```html
|
|
<!-- Use Alpine for immediate feedback -->
|
|
<div x-data="{ optimisticUpdate: false }">
|
|
<button @click="optimisticUpdate = true"
|
|
hx-post="/action"
|
|
hx-target="#result"
|
|
@htmx:after-request="optimisticUpdate = false">
|
|
<span x-show="!optimisticUpdate">Click me</span>
|
|
<span x-show="optimisticUpdate">Processing...</span>
|
|
</button>
|
|
</div>
|
|
```
|
|
|
|
## ThrillWiki-Specific Optimizations
|
|
|
|
### 1. Search Component Optimization
|
|
```javascript
|
|
Alpine.data('parkSearch', () => ({
|
|
query: '',
|
|
results: [],
|
|
loading: false,
|
|
|
|
async search() {
|
|
if (!this.query.trim()) {
|
|
this.results = [];
|
|
return;
|
|
}
|
|
|
|
this.loading = true;
|
|
try {
|
|
// Use HTMX for actual search, Alpine for state
|
|
const response = await fetch(`/search/parks/?q=${encodeURIComponent(this.query)}`);
|
|
this.results = await response.json();
|
|
} catch (error) {
|
|
console.error('Search failed:', error);
|
|
this.results = [];
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
get filteredResults() {
|
|
return this.results.slice(0, 10); // Limit results for performance
|
|
}
|
|
}))
|
|
```
|
|
|
|
### 2. Photo Gallery Optimization
|
|
```javascript
|
|
Alpine.data('photoGallery', () => ({
|
|
photos: [],
|
|
currentIndex: 0,
|
|
loading: false,
|
|
|
|
init() {
|
|
// Lazy load images as they come into view
|
|
this.$nextTick(() => {
|
|
this.setupIntersectionObserver();
|
|
});
|
|
},
|
|
|
|
setupIntersectionObserver() {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
img.src = img.dataset.src;
|
|
observer.unobserve(img);
|
|
}
|
|
});
|
|
});
|
|
|
|
this.$el.querySelectorAll('img[data-src]').forEach(img => {
|
|
observer.observe(img);
|
|
});
|
|
},
|
|
|
|
destroy() {
|
|
// Clean up observer
|
|
if (this.observer) {
|
|
this.observer.disconnect();
|
|
}
|
|
}
|
|
}))
|
|
```
|
|
|
|
### 3. Form Validation Optimization
|
|
```javascript
|
|
Alpine.data('parkForm', () => ({
|
|
form: {
|
|
name: '',
|
|
location: '',
|
|
operator: ''
|
|
},
|
|
errors: {},
|
|
validating: false,
|
|
|
|
async validateField(field) {
|
|
if (this.validating) return;
|
|
|
|
this.validating = true;
|
|
try {
|
|
// Use HTMX for server-side validation
|
|
const response = await fetch('/validate-park-field/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': this.getCsrfToken()
|
|
},
|
|
body: JSON.stringify({
|
|
field: field,
|
|
value: this.form[field]
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
if (result.errors) {
|
|
this.errors[field] = result.errors;
|
|
} else {
|
|
delete this.errors[field];
|
|
}
|
|
} finally {
|
|
this.validating = false;
|
|
}
|
|
},
|
|
|
|
getCsrfToken() {
|
|
return document.querySelector('[name=csrfmiddlewaretoken]').value;
|
|
}
|
|
}))
|
|
```
|
|
|
|
## Performance Monitoring
|
|
|
|
### 1. Component Performance Tracking
|
|
```javascript
|
|
Alpine.data('performanceTracker', () => ({
|
|
init() {
|
|
const start = performance.now();
|
|
this.$nextTick(() => {
|
|
const end = performance.now();
|
|
console.log(`Component initialized in ${end - start}ms`);
|
|
});
|
|
}
|
|
}))
|
|
```
|
|
|
|
### 2. Memory Usage Monitoring
|
|
```javascript
|
|
// Monitor component count and memory usage
|
|
setInterval(() => {
|
|
if (performance.memory) {
|
|
console.log('Memory usage:', {
|
|
used: Math.round(performance.memory.usedJSHeapSize / 1048576) + 'MB',
|
|
total: Math.round(performance.memory.totalJSHeapSize / 1048576) + 'MB'
|
|
});
|
|
}
|
|
}, 10000);
|
|
```
|
|
|
|
## Implementation Priorities for ThrillWiki
|
|
|
|
### High Priority
|
|
1. **Component Registration**: Set up reusable components for common UI patterns
|
|
2. **Event Optimization**: Use .passive modifiers for scroll events
|
|
3. **Memory Management**: Implement proper cleanup in destroy() methods
|
|
4. **State Management**: Optimize reactive data usage
|
|
|
|
### Medium Priority
|
|
1. **Intersection Observer**: Lazy loading for images and content
|
|
2. **Performance Monitoring**: Track component initialization times
|
|
3. **Advanced Patterns**: Custom event dispatching and coordination
|
|
4. **Search Optimization**: Debounced search with result limiting
|
|
|
|
### Low Priority
|
|
1. **Advanced State Management**: Global stores for complex state
|
|
2. **Custom Directives**: Create project-specific Alpine directives
|
|
3. **Performance Profiling**: Detailed memory and performance analysis |