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
Markdown
# ⚡ Реактивность и 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)! 🚀