Performance Optimization
Best practices for optimizing useTemporal applications for maximum performance.
Progressive Division Strategy
The divide() function is powerful but can create many objects if used carelessly. Follow these patterns for optimal performance:
❌ Inefficient: Creating Too Many Objects
typescript
// Don't create all subdivisions at once
const year = usePeriod(temporal, 'year')
const allSeconds = year.value
|> (y) => divide(temporal.adapter, y, 'month')
|> (months) => months.flatMap(m => divide(temporal.adapter, m, 'day'))
|> (days) => days.flatMap(d => divide(temporal.adapter, d, 'hour'))
|> (hours) => hours.flatMap(h => divide(temporal.adapter, h, 'minute'))
|> (minutes) => minutes.flatMap(m => divide(temporal.adapter, m, 'second'))
// This creates 31,536,000+ objects!✅ Efficient: Divide Only What You Need
typescript
// Better: Divide progressively based on view requirements
const month = usePeriod(temporal, 'month')
const days = divide(temporal.adapter, month.value, 'day')
// Only divide specific days when needed
if (needHourlyView) {
const todayHours = divide(temporal.adapter, days[14], 'hour')
}Memoization Strategies
Using Computed Values
Results are automatically memoized when using reactive computed values:
typescript
import { computed } from '@vue/reactivity'
// Results are cached until dependencies change
const month = usePeriod(temporal, 'month')
const days = computed(() => divide(temporal.adapter, month.value, 'day'))
// days.value is only recalculated when month changesManual Memoization for Non-Reactive Code
typescript
// Cache expensive calculations
const cache = new Map()
function getCachedDivision(temporal, period, unit) {
const key = `${period.start.toISOString()}-${unit}`
if (!cache.has(key)) {
cache.set(key, divide(temporal.adapter, period, unit))
}
return cache.get(key)
}Lazy Loading Patterns
On-Demand Division
Load time divisions only when the user needs them:
typescript
// Calendar component with lazy month loading
const visibleMonths = ref(new Set())
function loadMonth(monthPeriod) {
const monthKey = monthPeriod.start.toISOString()
if (!visibleMonths.value.has(monthKey)) {
const days = divide(temporal.adapter, monthPeriod, 'day')
visibleMonths.value.add(monthKey)
// Store days for rendering
}
}Viewport-Based Loading
For large time ranges, only load what's visible:
typescript
// Year view with viewport detection
function YearCalendar() {
const [visibleMonths, setVisibleMonths] = useState(new Set())
const handleIntersection = (entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const monthIndex = entry.target.dataset.month
loadMonthData(monthIndex)
}
})
}
useEffect(() => {
const observer = new IntersectionObserver(handleIntersection)
// Observe month containers
return () => observer.disconnect()
}, [])
}Bundle Size Optimization
Tree Shaking
Import only what you need:
typescript
// ❌ Imports everything
import * as useTemporal from '@allystudio/usetemporal'
// ✅ Imports only needed functions
import { createTemporal, usePeriod, divide } from '@allystudio/usetemporal'Adapter Selection
Choose the right adapter for your needs:
typescript
// Smallest bundle: Native adapter (0 dependencies)
import { createTemporal } from '@allystudio/usetemporal'
// Larger bundle: External library adapters
import { createTemporal } from '@allystudio/usetemporal'
import { createLuxonAdapter } from '@allystudio/usetemporal/luxon'Memory Management
Cleanup Unused Periods
typescript
// Vue example with proper cleanup
onUnmounted(() => {
// Clear any cached data
periodCache.clear()
// Stop any reactive effects
stopWatching()
})Limit Stored History
typescript
// Keep only recent calculations
const MAX_CACHE_SIZE = 100
const recentCalculations = new Map()
function addToCache(key, value) {
if (recentCalculations.size >= MAX_CACHE_SIZE) {
// Remove oldest entry
const firstKey = recentCalculations.keys().next().value
recentCalculations.delete(firstKey)
}
recentCalculations.set(key, value)
}Reactive Performance
Batch Updates
Group multiple temporal updates:
typescript
// ❌ Multiple reactive updates
temporal.browsing.value = nextMonth
selectedDay.value = firstDayOfMonth
highlightedWeek.value = firstWeek
// ✅ Batch updates
import { batch } from '@vue/reactivity'
batch(() => {
temporal.browsing.value = nextMonth
selectedDay.value = firstDayOfMonth
highlightedWeek.value = firstWeek
})Debounce Expensive Operations
typescript
import { debounce } from 'lodash-es'
const debouncedCalculation = debounce((temporal, period) => {
// Expensive calculation
const analysis = analyzeTimeRange(temporal, period)
updateUI(analysis)
}, 300)
// In reactive effect
watchEffect(() => {
debouncedCalculation(temporal, month.value)
})Rendering Optimization
Virtual Scrolling for Large Ranges
typescript
// Render only visible time periods
function VirtualCalendar({ years }) {
const rowHeight = 300 // Height of month row
const visibleRows = 4 // Number of visible months
return (
<VirtualList
height={rowHeight * visibleRows}
itemCount={years * 12}
itemSize={rowHeight}
renderItem={({ index, style }) => (
<div style={style}>
<MonthCalendar monthIndex={index} />
</div>
)}
/>
)
}Minimize Re-renders
typescript
// React: Use memo for expensive components
const DayCell = React.memo(({ day, isSelected, onSelect }) => {
return (
<div
className={isSelected ? 'selected' : ''}
onClick={() => onSelect(day)}
>
{day.date.getDate()}
</div>
)
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.day.date.getTime() === nextProps.day.date.getTime() &&
prevProps.isSelected === nextProps.isSelected
})Benchmarking
Measure Performance
typescript
function measureDividePerformance() {
const temporal = createTemporal({ date: new Date() })
const year = usePeriod(temporal, 'year')
console.time('divide-year-to-days')
const months = divide(temporal.adapter, year.value, 'month')
const allDays = months.flatMap(m => divide(temporal.adapter, m, 'day'))
console.timeEnd('divide-year-to-days')
console.log(`Created ${allDays.length} day objects`)
}Profile Memory Usage
typescript
if (typeof performance !== 'undefined' && performance.memory) {
const before = performance.memory.usedJSHeapSize
// Perform operations
const periods = generateManyPeriods()
const after = performance.memory.usedJSHeapSize
console.log(`Memory used: ${(after - before) / 1024 / 1024} MB`)
}Best Practices Summary
- Divide Progressively: Only create the time divisions you need
- Use Memoization: Cache expensive calculations
- Lazy Load: Load data on-demand, especially for large date ranges
- Optimize Bundle: Import only what you need
- Manage Memory: Clean up unused data and limit cache sizes
- Batch Updates: Group reactive updates together
- Virtual Rendering: Use virtual scrolling for large lists
- Profile Performance: Measure and optimize bottlenecks