UNPKG

cs-element

Version:

Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support

1,160 lines (936 loc) 38.3 kB
# ⚡ Реактивность и Live Queries в CSElement Реактивность - это сердце CSElement. Система автоматически отслеживает изменения и обновляет зависимые данные, делая ваши приложения отзывчивыми и современными. ## 🎯 Основы реактивности ### Что такое реактивность? Реактивность означает, что когда данные изменяются, все зависимые от них компоненты обновляются автоматически. ```typescript import { CSElement } from 'cs-element'; // Создаем реактивную структуру const store = CSElement.create('Store') .withData('count', 0) .withData('multiplier', 2); // Создаем computed свойство const doubledCount = CSElement.computed(() => { return store.getData('count') * store.getData('multiplier'); }); console.log('Удвоенное значение:', doubledCount.value); // 0 // Изменяем данные - computed автоматически пересчитается await store.setData('count', 5); console.log('Удвоенное значение:', doubledCount.value); // 10 await store.setData('multiplier', 3); console.log('Удвоенное значение:', doubledCount.value); // 15 ``` ### Отслеживание изменений с watch ```typescript // Отслеживаем изменения конкретного поля CSElement.watch([store.id, 'count'], (newValue, oldValue) => { console.log(`Счетчик изменился: ${oldValue} → ${newValue}`); }); // Отслеживаем изменения computed свойства CSElement.watch([doubledCount.id], (newValue, oldValue) => { console.log(`Удвоенное значение: ${oldValue} → ${newValue}`); }); // Отслеживаем множественные изменения CSElement.watch([ [store.id, 'count'], [store.id, 'multiplier'] ], (values, oldValues) => { console.log('Изменились основные данные:', values); }); ``` ## 🔍 Live Queries - реактивные запросы Live Queries автоматически обновляют результаты поиска при изменении данных. ### Создание Live Query ```typescript // Создаем коллекцию пользователей const users = CSElement.create('Users'); // Добавляем пользователей await users.addElement('user1', { data: new Map([ ['name', 'Алексей'], ['role', 'admin'], ['active', true], ['lastLogin', Date.now()] ]) }); await users.addElement('user2', { data: new Map([ ['name', 'Мария'], ['role', 'user'], ['active', true], ['lastLogin', Date.now() - 86400000] // вчера ]) }); // Создаем Live Query для активных пользователей const activeUsersQuery = CSElement.createLiveQuery('[active=true]', { debounce: 100, cache: true }); // Подписываемся на результаты activeUsersQuery.onResults(activeUsers => { console.log(`Активных пользователей: ${activeUsers.length}`); activeUsers.forEach(user => { console.log(`- ${user.getData('name')} (${user.getData('role')})`); }); }); // Запускаем отслеживание CSElement.liveQueries.start(activeUsersQuery.id); // Теперь при изменении пользователей результаты обновятся автоматически! ``` ### Продвинутые Live Queries ```typescript // Live Query с фильтрами и трансформациями const adminStatsQuery = CSElement.createLiveQuery('[role=admin]') .filter(admin => admin.getData('active') === true) .transform(admin => ({ name: admin.getData('name'), lastLogin: new Date(admin.getData('lastLogin')).toLocaleDateString(), isOnline: Date.now() - admin.getData('lastLogin') < 300000 // 5 минут })) .sort((a, b) => a.name.localeCompare(b.name)) .onResults(adminStats => { console.log('Статистика администраторов:', adminStats); }); // Live Query с агрегацией данных const userStatsQuery = CSElement.createLiveQuery('*') .transform(user => user.getData('role')) .onResults(roles => { const stats = roles.reduce((acc, role) => { acc[role] = (acc[role] || 0) + 1; return acc; }, {}); console.log('Статистика по ролям:', stats); }); CSElement.liveQueries.start(adminStatsQuery.id); CSElement.liveQueries.start(userStatsQuery.id); ``` ## 🎨 Создание реактивных компонентов ### Реактивный счетчик ```typescript class ReactiveCounter extends CSElement { private countQuery: any; private doubledQuery: any; constructor(initialValue: number = 0) { super('ReactiveCounter'); this.withData('count', initialValue); this.setupReactivity(); } private setupReactivity() { // Отслеживаем изменения счетчика this.countQuery = CSElement.createLiveQuery(`[id=${this.id}]`) .transform(element => element.getData('count')) .onResults(([count]) => { this.emit('count:changed', count); this.updateDisplay(count); }); // Computed свойство для удвоенного значения this.doubledQuery = CSElement.computed(() => { return this.getData('count') * 2; }); CSElement.liveQueries.start(this.countQuery.id); } private updateDisplay(count: number) { console.log(`Счетчик: ${count}, Удвоенное: ${this.doubledQuery.value}`); } async increment() { const current = this.getData('count') || 0; await this.setData('count', current + 1); } async decrement() { const current = this.getData('count') || 0; await this.setData('count', current - 1); } async reset() { await this.setData('count', 0); } get count(): number { return this.getData('count') || 0; } get doubled(): number { return this.doubledQuery.value; } } // Использование const counter = new ReactiveCounter(5); counter.on('count:changed', (newCount) => { console.log(`Событие: счетчик изменился на ${newCount}`); }); await counter.increment(); // Счетчик: 6, Удвоенное: 12 await counter.increment(); // Счетчик: 7, Удвоенное: 14 await counter.reset(); // Счетчик: 0, Удвоенное: 0 ``` ### Реактивная корзина покупок ```typescript class ReactiveShoppingCart extends CSElement { private itemsQuery: any; private totalQuery: any; private itemCountQuery: any; constructor() { super('ShoppingCart'); this.setupQueries(); } private setupQueries() { // Отслеживаем все товары в корзине this.itemsQuery = CSElement.createLiveQuery('[type=cartItem]', { root: this, debounce: 50 }).onResults(items => { this.emit('items:changed', items); this.updateCartDisplay(items); }); // Computed для общей суммы this.totalQuery = CSElement.computed(() => { const items = this.query('[type=cartItem]'); return items.reduce((total, item) => { const price = item.getData('price') || 0; const quantity = item.getData('quantity') || 0; return total + (price * quantity); }, 0); }); // Computed для количества товаров this.itemCountQuery = CSElement.computed(() => { const items = this.query('[type=cartItem]'); return items.reduce((count, item) => { return count + (item.getData('quantity') || 0); }, 0); }); CSElement.liveQueries.start(this.itemsQuery.id); // Отслеживаем изменения общей суммы CSElement.watch([this.totalQuery.id], (newTotal) => { this.setData('total', newTotal); this.emit('total:changed', newTotal); }); // Отслеживаем изменения количества товаров CSElement.watch([this.itemCountQuery.id], (newCount) => { this.setData('itemCount', newCount); this.emit('itemCount:changed', newCount); }); } private updateCartDisplay(items: CSElement[]) { console.log('\n🛒 Корзина покупок:'); if (items.length === 0) { console.log('Корзина пуста'); return; } items.forEach(item => { const name = item.getData('name'); const price = item.getData('price'); const quantity = item.getData('quantity'); const subtotal = price * quantity; console.log(` ${name} - ${price}₽ × ${quantity} = ${subtotal}₽`); }); console.log(`\nВсего товаров: ${this.itemCountQuery.value}`); console.log(`Общая сумма: ${this.totalQuery.value}₽`); } async addItem(product: { id: string; name: string; price: number; quantity?: number; }) { const existingItem = this.getElement(product.id); if (existingItem) { // Увеличиваем количество существующего товара const currentQuantity = existingItem.getData('quantity') || 0; await existingItem.setData('quantity', currentQuantity + (product.quantity || 1)); } else { // Добавляем новый товар await this.addElement(product.id, { data: new Map([ ['type', 'cartItem'], ['name', product.name], ['price', product.price], ['quantity', product.quantity || 1] ]) }); } } async removeItem(productId: string) { return await this.removeElement(productId); } async updateQuantity(productId: string, quantity: number) { const item = this.getElement(productId); if (item) { if (quantity <= 0) { await this.removeItem(productId); } else { await item.setData('quantity', quantity); } } } async clear() { const items = this.query('[type=cartItem]'); for (const item of items) { await this.removeElement(item); } } get total(): number { return this.totalQuery.value; } get itemCount(): number { return this.itemCountQuery.value; } getItems(): CSElement[] { return this.query('[type=cartItem]'); } } // Использование корзины const cart = new ReactiveShoppingCart(); // Подписываемся на события cart.on('total:changed', (newTotal) => { console.log(`💰 Общая сумма изменилась: ${newTotal}₽`); }); cart.on('itemCount:changed', (newCount) => { console.log(`📦 Количество товаров: ${newCount}`); }); // Добавляем товары await cart.addItem({ id: 'laptop', name: 'Ноутбук', price: 50000, quantity: 1 }); await cart.addItem({ id: 'mouse', name: 'Мышь', price: 2000, quantity: 2 }); await cart.addItem({ id: 'laptop', // Увеличит количество существующего товара name: 'Ноутбук', price: 50000, quantity: 1 }); // Изменяем количество await cart.updateQuantity('mouse', 1); // Удаляем товар await cart.removeItem('mouse'); ``` ## 🔄 Реактивные вычисления ### Создание цепочек зависимостей ```typescript // Создаем базовые данные const gameState = CSElement.create('GameState') .withData('level', 1) .withData('experience', 0) .withData('baseHealth', 100); // Computed свойства с зависимостями const levelMultiplier = CSElement.computed(() => { const level = gameState.getData('level'); return 1 + (level - 1) * 0.1; // +10% за уровень }); const maxHealth = CSElement.computed(() => { const baseHealth = gameState.getData('baseHealth'); return Math.floor(baseHealth * levelMultiplier.value); }); const experienceToNextLevel = CSElement.computed(() => { const level = gameState.getData('level'); return level * 1000; // 1000 опыта за уровень * текущий уровень }); const experienceProgress = CSElement.computed(() => { const currentExp = gameState.getData('experience'); const neededExp = experienceToNextLevel.value; return Math.min(currentExp / neededExp, 1.0); }); // Отслеживаем изменения CSElement.watch([maxHealth.id], (newHealth) => { console.log(`💚 Максимальное здоровье: ${newHealth}`); }); CSElement.watch([experienceProgress.id], (progress) => { const percent = Math.round(progress * 100); console.log(`⭐ Прогресс до следующего уровня: ${percent}%`); }); // Функция повышения уровня async function gainExperience(amount: number) { const currentExp = gameState.getData('experience'); const newExp = currentExp + amount; const neededExp = experienceToNextLevel.value; await gameState.setData('experience', newExp); if (newExp >= neededExp) { const newLevel = gameState.getData('level') + 1; await gameState.setData('level', newLevel); await gameState.setData('experience', newExp - neededExp); console.log(`🎉 Поздравляем! Достигнут ${newLevel} уровень!`); } } // Симуляция игры await gainExperience(500); // ⭐ Прогресс до следующего уровня: 50% await gainExperience(600); // 🎉 Поздравляем! Достигнут 2 уровень! // 💚 Максимальное здоровье: 110 // ⭐ Прогресс до следующего уровня: 5% ``` ### Реактивная аналитика ```typescript class ReactiveAnalytics extends CSElement { private metricsQuery: any; private summaryComputed: any; private trendsComputed: any; constructor() { super('Analytics'); this.setupAnalytics(); } private setupAnalytics() { // Отслеживаем все метрики this.metricsQuery = CSElement.createLiveQuery('[type=metric]', { root: this, debounce: 100 }).onResults(metrics => { this.updateAnalytics(metrics); }); // Computed для сводной статистики this.summaryComputed = CSElement.computed(() => { const metrics = this.query('[type=metric]'); const summary = { totalEvents: metrics.length, avgValue: 0, minValue: Infinity, maxValue: -Infinity, categories: new Map() }; if (metrics.length === 0) return summary; let totalValue = 0; metrics.forEach(metric => { const value = metric.getData('value') || 0; const category = metric.getData('category') || 'unknown'; totalValue += value; summary.minValue = Math.min(summary.minValue, value); summary.maxValue = Math.max(summary.maxValue, value); if (!summary.categories.has(category)) { summary.categories.set(category, { count: 0, total: 0 }); } const catStats = summary.categories.get(category); catStats.count++; catStats.total += value; }); summary.avgValue = totalValue / metrics.length; return summary; }); // Computed для трендов (последние 10 метрик) this.trendsComputed = CSElement.computed(() => { const metrics = this.query('[type=metric]') .sort((a, b) => b.getData('timestamp') - a.getData('timestamp')) .slice(0, 10); if (metrics.length < 2) return { trend: 'insufficient_data' }; const values = metrics.map(m => m.getData('value')).reverse(); const trend = this.calculateTrend(values); return { trend, recentValues: values, change: values.length > 1 ? values[values.length - 1] - values[0] : 0 }; }); CSElement.liveQueries.start(this.metricsQuery.id); // Отслеживаем изменения сводки CSElement.watch([this.summaryComputed.id], (summary) => { this.emit('summary:updated', summary); }); // Отслеживаем изменения трендов CSElement.watch([this.trendsComputed.id], (trends) => { this.emit('trends:updated', trends); }); } private calculateTrend(values: number[]): 'up' | 'down' | 'stable' { if (values.length < 2) return 'stable'; const first = values[0]; const last = values[values.length - 1]; const change = (last - first) / first; if (change > 0.05) return 'up'; if (change < -0.05) return 'down'; return 'stable'; } private updateAnalytics(metrics: CSElement[]) { console.log('\n📊 Обновление аналитики:'); console.log(`Всего метрик: ${metrics.length}`); const summary = this.summaryComputed.value; if (summary.totalEvents > 0) { console.log(`Среднее значение: ${summary.avgValue.toFixed(2)}`); console.log(`Диапазон: ${summary.minValue} - ${summary.maxValue}`); } const trends = this.trendsComputed.value; if (trends.trend !== 'insufficient_data') { const trendIcon = trends.trend === 'up' ? '📈' : trends.trend === 'down' ? '📉' : '➡️'; console.log(`Тренд: ${trendIcon} ${trends.trend} (изменение: ${trends.change.toFixed(2)})`); } } async addMetric(category: string, value: number, metadata: any = {}) { const metricId = `metric_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; await this.addElement(metricId, { data: new Map([ ['type', 'metric'], ['category', category], ['value', value], ['timestamp', Date.now()], ['metadata', metadata] ]) }); } getSummary() { return this.summaryComputed.value; } getTrends() { return this.trendsComputed.value; } getMetricsByCategory(category: string): CSElement[] { return this.query(`[category=${category}]`); } } // Использование аналитики const analytics = new ReactiveAnalytics(); analytics.on('summary:updated', (summary) => { console.log('📈 Сводка обновлена:', summary); }); analytics.on('trends:updated', (trends) => { console.log('📊 Тренды обновлены:', trends); }); // Добавляем метрики await analytics.addMetric('sales', 1500, { product: 'laptop' }); await analytics.addMetric('sales', 2000, { product: 'phone' }); await analytics.addMetric('visits', 150, { page: 'home' }); await analytics.addMetric('sales', 1800, { product: 'tablet' }); await analytics.addMetric('visits', 200, { page: 'products' }); ``` ## 🎯 Оптимизация реактивности ### Debouncing и throttling ```typescript // Создаем элемент с частыми изменениями const fastChangingElement = CSElement.create('FastChanging') .withData('value', 0); // Live Query с debounce для оптимизации const debouncedQuery = CSElement.createLiveQuery(`[id=${fastChangingElement.id}]`, { debounce: 300 // Обновления не чаще чем раз в 300мс }).onResults(([element]) => { console.log('Debounced update:', element.getData('value')); }); // Live Query без debounce для сравнения const immediateQuery = CSElement.createLiveQuery(`[id=${fastChangingElement.id}]`, { debounce: 0 }).onResults(([element]) => { console.log('Immediate update:', element.getData('value')); }); CSElement.liveQueries.start(debouncedQuery.id); CSElement.liveQueries.start(immediateQuery.id); // Быстрые изменения for (let i = 1; i <= 10; i++) { await fastChangingElement.setData('value', i); await new Promise(resolve => setTimeout(resolve, 50)); } // Immediate update сработает 10 раз // Debounced update сработает только 1-2 раза ``` ### Селективное отслеживание ```typescript // Отслеживаем только конкретные поля const selectiveWatcher = CSElement.watch( [[fastChangingElement.id, 'importantField']], // Только это поле (newValue, oldValue) => { console.log('Important field changed:', newValue); } ); // Отслеживаем изменения с условием const conditionalWatcher = CSElement.watch( [[fastChangingElement.id, 'value']], (newValue, oldValue) => { if (newValue % 10 === 0) { // Только кратные 10 console.log('Milestone reached:', newValue); } } ); ``` ## 🎉 Полный пример - Реактивная панель мониторинга ```typescript import { CSElement } from 'cs-element'; class ReactiveDashboard extends CSElement { private dataSourcesQuery: any; private alertsQuery: any; private systemHealthComputed: any; private performanceMetricsComputed: any; constructor() { super('Dashboard'); this.initializeDataSources(); this.setupReactiveQueries(); this.setupComputedMetrics(); this.setupAlerts(); } private initializeDataSources() { // Создаем источники данных this.withChild('servers'); this.withChild('applications'); this.withChild('databases'); this.withChild('alerts'); } private setupReactiveQueries() { // Отслеживаем все источники данных this.dataSourcesQuery = CSElement.createLiveQuery('[type=dataSource]', { root: this, debounce: 1000 }).onResults(dataSources => { this.updateDashboard(dataSources); }); // Отслеживаем активные алерты this.alertsQuery = CSElement.createLiveQuery('[type=alert][status=active]', { root: this.getElement('alerts'), debounce: 100 }).onResults(activeAlerts => { this.handleAlerts(activeAlerts); }); CSElement.liveQueries.start(this.dataSourcesQuery.id); CSElement.liveQueries.start(this.alertsQuery.id); } private setupComputedMetrics() { // Computed для общего здоровья системы this.systemHealthComputed = CSElement.computed(() => { const servers = this.getElement('servers').query('[type=server]'); const apps = this.getElement('applications').query('[type=application]'); const dbs = this.getElement('databases').query('[type=database]'); const healthyServers = servers.filter(s => s.getData('status') === 'healthy').length; const healthyApps = apps.filter(a => a.getData('status') === 'running').length; const healthyDbs = dbs.filter(d => d.getData('status') === 'online').length; const totalComponents = servers.length + apps.length + dbs.length; const healthyComponents = healthyServers + healthyApps + healthyDbs; return { overallHealth: totalComponents > 0 ? (healthyComponents / totalComponents) * 100 : 100, servers: { healthy: healthyServers, total: servers.length }, applications: { healthy: healthyApps, total: apps.length }, databases: { healthy: healthyDbs, total: dbs.length } }; }); // Computed для метрик производительности this.performanceMetricsComputed = CSElement.computed(() => { const servers = this.getElement('servers').query('[type=server]'); if (servers.length === 0) { return { avgCpu: 0, avgMemory: 0, avgDisk: 0 }; } const totalCpu = servers.reduce((sum, s) => sum + (s.getData('cpu') || 0), 0); const totalMemory = servers.reduce((sum, s) => sum + (s.getData('memory') || 0), 0); const totalDisk = servers.reduce((sum, s) => sum + (s.getData('disk') || 0), 0); return { avgCpu: totalCpu / servers.length, avgMemory: totalMemory / servers.length, avgDisk: totalDisk / servers.length }; }); // Отслеживаем изменения метрик CSElement.watch([this.systemHealthComputed.id], (health) => { this.emit('health:updated', health); this.checkHealthThresholds(health); }); CSElement.watch([this.performanceMetricsComputed.id], (metrics) => { this.emit('performance:updated', metrics); this.checkPerformanceThresholds(metrics); }); } private setupAlerts() { // Автоматические алерты на основе метрик CSElement.watch([this.performanceMetricsComputed.id], (metrics) => { if (metrics.avgCpu > 80) { this.createAlert('high_cpu', 'Высокая загрузка CPU', 'critical'); } if (metrics.avgMemory > 90) { this.createAlert('high_memory', 'Высокое использование памяти', 'critical'); } if (metrics.avgDisk > 85) { this.createAlert('high_disk', 'Мало места на диске', 'warning'); } }); } private updateDashboard(dataSources: CSElement[]) { console.log('\n🖥️ Обновление панели мониторинга:'); console.log(`Источников данных: ${dataSources.length}`); const health = this.systemHealthComputed.value; const performance = this.performanceMetricsComputed.value; console.log(`Общее здоровье системы: ${health.overallHealth.toFixed(1)}%`); console.log(`Средняя загрузка CPU: ${performance.avgCpu.toFixed(1)}%`); console.log(`Средняя загрузка памяти: ${performance.avgMemory.toFixed(1)}%`); } private handleAlerts(activeAlerts: CSElement[]) { if (activeAlerts.length > 0) { console.log(`🚨 Активных алертов: ${activeAlerts.length}`); activeAlerts.forEach(alert => { const severity = alert.getData('severity'); const message = alert.getData('message'); const icon = severity === 'critical' ? '🔴' : severity === 'warning' ? '🟡' : '🔵'; console.log(`${icon} ${message}`); }); } } private checkHealthThresholds(health: any) { if (health.overallHealth < 50) { this.createAlert('system_health', 'Критическое состояние системы', 'critical'); } else if (health.overallHealth < 80) { this.createAlert('system_health', 'Проблемы в системе', 'warning'); } } private checkPerformanceThresholds(metrics: any) { // Логика уже в setupAlerts() } private async createAlert(id: string, message: string, severity: 'info' | 'warning' | 'critical') { const alerts = this.getElement('alerts'); const existingAlert = alerts.getElement(id); if (existingAlert && existingAlert.getData('status') === 'active') { return; // Алерт уже существует } await alerts.addElement(id, { data: new Map([ ['type', 'alert'], ['message', message], ['severity', severity], ['status', 'active'], ['timestamp', Date.now()] ]) }); } // Публичные методы для добавления компонентов async addServer(id: string, data: { name: string; cpu: number; memory: number; disk: number; status: 'healthy' | 'warning' | 'critical'; }) { const servers = this.getElement('servers'); await servers.addElement(id, { data: new Map([ ['type', 'server'], ['name', data.name], ['cpu', data.cpu], ['memory', data.memory], ['disk', data.disk], ['status', data.status], ['lastUpdate', Date.now()] ]) }); } async updateServerMetrics(serverId: string, metrics: { cpu?: number; memory?: number; disk?: number; status?: string; }) { const servers = this.getElement('servers'); const server = servers.getElement(serverId); if (server) { for (const [key, value] of Object.entries(metrics)) { if (value !== undefined) { await server.setData(key, value); } } await server.setData('lastUpdate', Date.now()); } } async resolveAlert(alertId: string) { const alerts = this.getElement('alerts'); const alert = alerts.getElement(alertId); if (alert) { await alert.setData('status', 'resolved'); await alert.setData('resolvedAt', Date.now()); } } getSystemHealth() { return this.systemHealthComputed.value; } getPerformanceMetrics() { return this.performanceMetricsComputed.value; } getActiveAlerts(): CSElement[] { return this.getElement('alerts').query('[status=active]'); } } // Использование панели мониторинга const dashboard = new ReactiveDashboard(); // Подписываемся на события dashboard.on('health:updated', (health) => { if (health.overallHealth < 80) { console.log('⚠️ Внимание: здоровье системы снижено'); } }); dashboard.on('performance:updated', (metrics) => { if (metrics.avgCpu > 70) { console.log('🔥 Высокая загрузка процессора'); } }); // Добавляем серверы await dashboard.addServer('web1', { name: 'Web Server 1', cpu: 45, memory: 60, disk: 30, status: 'healthy' }); await dashboard.addServer('db1', { name: 'Database Server', cpu: 70, memory: 80, disk: 65, status: 'healthy' }); // Симулируем изменения метрик setTimeout(async () => { await dashboard.updateServerMetrics('web1', { cpu: 85, memory: 75 }); }, 2000); setTimeout(async () => { await dashboard.updateServerMetrics('db1', { cpu: 95, memory: 95 }); }, 4000); // Через 6 секунд разрешаем алерт setTimeout(async () => { const alerts = dashboard.getActiveAlerts(); if (alerts.length > 0) { await dashboard.resolveAlert(alerts[0].name); console.log('✅ Алерт разрешен'); } }, 6000); ``` ## 🗑️ Auto-Dispose: Автоматическая очистка памяти CSElement включает продвинутый механизм **auto-dispose** (как в S.js), который автоматически очищает дочерние computed и watchers при пересоздании родительских элементов, предотвращая утечки памяти. ### Базовый принцип ```typescript const counter = CSElement.ref(0); // Родительский computed const parent = CSElement.computed(() => { const value = counter.value; // Дочерние элементы создаются внутри родителя const child1 = CSElement.computed(() => value * 2); const child2 = CSElement.computed(() => value * 3); // При пересоздании parent, child1 и child2 автоматически очищаются return { original: value, double: CSElement.getComputedValue(child1.id), triple: CSElement.getComputedValue(child2.id) }; }); // Первое вычисление создает иерархию console.log(CSElement.getComputedValue(parent.id)); // Изменение counter пересоздает parent и автоматически очищает детей counter.value = 5; console.log(CSElement.getComputedValue(parent.id)); ``` ### Многоуровневая иерархия ```typescript const root = CSElement.ref(1); const deepHierarchy = CSElement.computed(() => { const value = root.value; const level2 = CSElement.computed(() => { const level3 = CSElement.computed(() => { // Watcher на самом глубоком уровне CSElement.watch([root.id], (newVal) => { console.log(`Deep watcher: ${newVal}`); }); return value * 4; }); return CSElement.getComputedValue(level3.id) * 2; }); return CSElement.getComputedValue(level2.id) * 2; }); // При dispose корневого элемента очищается вся иерархия CSElement.disposeComputed(deepHierarchy.id); ``` ### Области видимости (Scopes) ```typescript // Создаем область видимости const scope = CSElement.createScope(); // Выполняем код в контексте области const result = CSElement.runInScope(scope.id, () => { const ref = CSElement.ref(10); const computed = CSElement.computed(() => ref.value * 5); const watcher = CSElement.watch([ref.id], () => { console.log('Scoped watcher'); }); return CSElement.getComputedValue(computed.id); }); // Очищаем всю область и все её элементы CSElement.disposeScope(scope.id); ``` ### React-стиль useEffect ```typescript function useEffect(effect: () => (() => void) | void, deps: any[]) { const computed = CSElement.computed(() => { // Отслеживаем зависимости deps.forEach(dep => { if (dep && typeof dep === 'object' && 'value' in dep) { dep.value; // Читаем для отслеживания } }); const cleanup = effect(); return cleanup; }); return () => CSElement.disposeComputed(computed.id); } const dependency = CSElement.ref(0); const cleanup = useEffect(() => { console.log('Effect runs'); return () => { console.log('Cleanup runs'); }; }, [dependency]); // При изменении зависимости effect пересоздается dependency.value = 1; // Ручная очистка cleanup(); ``` ### Настройка auto-dispose ```typescript CSElement.configureReactivity({ autoDispose: true, // Включить auto-dispose maxDisposeDepth: 50, // Максимальная глубина иерархии warnMemoryLeaks: true, // Предупреждения об утечках debug: true // Отладочная информация }); ``` ### События auto-dispose ```typescript // Слушаем события очистки CSElement.onReactivityEvent('computed-disposed', (data) => { console.log(`Computed ${data.computedId} disposed`); console.log(`Children: ${data.children}`); }); CSElement.onReactivityEvent('watcher-disposed', (data) => { console.log(`Watcher ${data.watcherId} disposed`); }); CSElement.onReactivityEvent('scope-disposed', (data) => { console.log(`Scope ${data.scopeId} disposed`); }); CSElement.onReactivityEvent('memory-leak-detected', (data) => { console.warn(`Potential memory leak:`, data.leakInfo); }); ``` ### Ручное управление ```typescript // Ручная очистка computed CSElement.disposeComputed(computedId); // Ручная очистка watcher CSElement.disposeWatcher(watcherId); // Проверка состояния if (CSElement.isDisposed(elementId, 'computed')) { console.log('Element was disposed'); } // Получение детей const children = CSElement.getChildren(parentId, 'computed'); console.log('Child computed:', children); ``` ### Производительность Auto-dispose оптимизирован для высокой производительности: ```typescript // Тест с большой иерархией const bigHierarchy = CSElement.computed(() => { const value = ref.value; // 100 computed + 100 watchers for (let i = 0; i < 100; i++) { CSElement.computed(() => value + i); CSElement.watch([ref.id], () => {}); } return value; }); // Быстрая очистка всей иерархии console.time('dispose'); CSElement.disposeComputed(bigHierarchy.id); console.timeEnd('dispose'); // < 10ms ``` ## 🎉 Заключение Реактивность в CSElement предоставляет мощные инструменты для создания отзывчивых приложений: - **🔄 Live Queries** - автоматическое обновление результатов поиска - **⚡ Computed свойства** - вычисляемые значения с кэшированием - **🗑️ Auto-dispose** - автоматическая очистка памяти без утечек - **👀 Watchers** - отслеживание изменений с гибкой настройкой - **🎯 Оптимизация** - debouncing, throttling, селективное отслеживание - **🚨 Реактивные алерты** - автоматическое реагирование на изменения **Готовы изучить типизированные элементы?** Переходите к [следующему разделу](04-typed-elements.md)! 🚀