cs-element
Version:
Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support
1,326 lines (1,133 loc) • 38.9 kB
Markdown
# ⚛️ React интеграция
CSElement предоставляет мощные хуки и компоненты для бесшовной интеграции с React приложениями, обеспечивая реактивность и автоматические обновления UI.
## 📋 Содержание
1. [Основы React интеграции](#основы-react-интеграции)
2. [Хуки для работы с элементами](#хуки-для-работы-с-элементами)
3. [Хуки для системы CSElement](#хуки-для-системы-cselement)
4. [Реактивные компоненты](#реактивные-компоненты)
5. [Управление состоянием](#управление-состоянием)
6. [Оптимизация производительности](#оптимизация-производительности)
7. [Полный пример: React приложение с CSElement](#полный-пример-react-приложение-с-cselement)
## Основы React интеграции
CSElement интегрируется с React через специальные хуки, которые автоматически подписываются на изменения элементов и обновляют компоненты.
### Установка и настройка
```bash
npm install cs-element react react-dom
```
```typescript
import React from 'react';
import { CSElement } from 'cs-element';
import { useElement, useCSElementById, useQuery } from 'cs-element/react';
// Инициализация CSElement для React
CSElement.configureReactivity({
debug: true,
batchUpdates: true
});
```
### Простой пример
```tsx
import React from 'react';
import { useElement } from 'cs-element/react';
function SimpleCounter() {
const { element, loading, error, update } = useElement({
name: 'counter',
data: { count: 0 }
});
if (loading) return <div>Загрузка...</div>;
if (error) return <div>Ошибка: {error.message}</div>;
if (!element) return <div>Элемент не найден</div>;
const count = element.getData('count') || 0;
const increment = () => {
update({ count: count + 1 });
};
return (
<div>
<h2>Счетчик: {count}</h2>
<button onClick={increment}>+1</button>
</div>
);
}
```
## Хуки для работы с элементами
### useElement - Управление одним элементом
```tsx
import React from 'react';
import { useElement } from 'cs-element/react';
interface UserProfileProps {
userId?: string;
parentElement?: CSElement;
}
function UserProfile({ userId, parentElement }: UserProfileProps) {
const { element, loading, error, update, remove, save } = useElement({
name: userId ? `user-${userId}` : undefined,
parent: parentElement,
data: {
name: '',
email: '',
avatar: '',
preferences: {
theme: 'light',
notifications: true
}
}
});
if (loading) return <div className="loading">Загрузка профиля...</div>;
if (error) return <div className="error">Ошибка: {error.message}</div>;
if (!element) return <div>Профиль не найден</div>;
const handleUpdateProfile = async (updates: Record<string, any>) => {
try {
await update(updates);
console.log('Профиль обновлен');
} catch (err) {
console.error('Ошибка обновления:', err);
}
};
const handleSaveProfile = async () => {
try {
await save();
console.log('Профиль сохранен');
} catch (err) {
console.error('Ошибка сохранения:', err);
}
};
const handleDeleteProfile = async () => {
if (confirm('Удалить профиль?')) {
try {
await remove();
console.log('Профиль удален');
} catch (err) {
console.error('Ошибка удаления:', err);
}
}
};
return (
<div className="user-profile">
<h2>Профиль пользователя</h2>
<div className="form-group">
<label>Имя:</label>
<input
type="text"
value={element.getData('name') || ''}
onChange={(e) => handleUpdateProfile({ name: e.target.value })}
/>
</div>
<div className="form-group">
<label>Email:</label>
<input
type="email"
value={element.getData('email') || ''}
onChange={(e) => handleUpdateProfile({ email: e.target.value })}
/>
</div>
<div className="form-group">
<label>Тема:</label>
<select
value={element.getData('preferences')?.theme || 'light'}
onChange={(e) => handleUpdateProfile({
preferences: {
...element.getData('preferences'),
theme: e.target.value
}
})}
>
<option value="light">Светлая</option>
<option value="dark">Темная</option>
</select>
</div>
<div className="actions">
<button onClick={handleSaveProfile}>Сохранить</button>
<button onClick={handleDeleteProfile} className="danger">
Удалить
</button>
</div>
</div>
);
}
```
### useCSElementById - Получение элемента по ID
```tsx
import React from 'react';
import { useCSElementById } from 'cs-element/react';
interface ElementViewerProps {
elementId: string;
}
function ElementViewer({ elementId }: ElementViewerProps) {
const element = useCSElementById(elementId);
if (!element) {
return <div>Элемент с ID {elementId} не найден</div>;
}
return (
<div className="element-viewer">
<h3>Элемент: {element.name}</h3>
<div className="element-info">
<p><strong>ID:</strong> {element.id}</p>
<p><strong>Создан:</strong> {element.getData('createdAt')}</p>
<p><strong>Тип:</strong> {element.getData('type')}</p>
</div>
<div className="element-data">
<h4>Данные:</h4>
<pre>{JSON.stringify(element.getAllData(), null, 2)}</pre>
</div>
</div>
);
}
```
### useQuery - Поиск элементов
```tsx
import React, { useState } from 'react';
import { useQuery } from 'cs-element/react';
interface TaskListProps {
project: CSElement;
}
function TaskList({ project }: TaskListProps) {
const [filter, setFilter] = useState('all');
// Динамический селектор на основе фильтра
const selector = React.useMemo(() => {
switch (filter) {
case 'completed':
return '[type="task"][completed="true"]';
case 'pending':
return '[type="task"][completed="false"]';
default:
return '[type="task"]';
}
}, [filter]);
const tasks = useQuery(project, selector);
return (
<div className="task-list">
<div className="filters">
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => setFilter('all')}
>
Все ({tasks.length})
</button>
<button
className={filter === 'pending' ? 'active' : ''}
onClick={() => setFilter('pending')}
>
В работе
</button>
<button
className={filter === 'completed' ? 'active' : ''}
onClick={() => setFilter('completed')}
>
Завершенные
</button>
</div>
<div className="tasks">
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</div>
</div>
);
}
function TaskItem({ task }: { task: CSElement }) {
const [isEditing, setIsEditing] = useState(false);
const toggleComplete = async () => {
const completed = task.getData('completed');
await task.setData('completed', !completed);
};
const updateTitle = async (newTitle: string) => {
await task.setData('title', newTitle);
setIsEditing(false);
};
return (
<div className={`task-item ${task.getData('completed') ? 'completed' : ''}`}>
<input
type="checkbox"
checked={task.getData('completed') || false}
onChange={toggleComplete}
/>
{isEditing ? (
<input
type="text"
defaultValue={task.getData('title')}
onBlur={(e) => updateTitle(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
updateTitle(e.currentTarget.value);
}
}}
autoFocus
/>
) : (
<span
className="task-title"
onDoubleClick={() => setIsEditing(true)}
>
{task.getData('title')}
</span>
)}
<div className="task-meta">
<span className="priority">{task.getData('priority')}</span>
<span className="assignee">{task.getData('assignee')}</span>
</div>
</div>
);
}
```
### useQueryOne - Получение одного элемента
```tsx
import React from 'react';
import { useQueryOne } from 'cs-element/react';
interface ActiveUserProps {
workspace: CSElement;
}
function ActiveUser({ workspace }: ActiveUserProps) {
// Находим текущего активного пользователя
const activeUser = useQueryOne(workspace, '[type="user"][status="active"]');
if (!activeUser) {
return <div>Нет активного пользователя</div>;
}
return (
<div className="active-user">
<img
src={activeUser.getData('avatar')}
alt={activeUser.getData('name')}
className="avatar"
/>
<div className="user-info">
<h4>{activeUser.getData('name')}</h4>
<p>{activeUser.getData('role')}</p>
<span className="status online">В сети</span>
</div>
</div>
);
}
```
## Хуки для системы CSElement
### useCSElementSystem - Управление всей системой
```tsx
import React, { useState } from 'react';
import { useCSElementSystem } from 'cs-element/react';
function AppRoot() {
const [autoConnect] = useState(true);
const {
root,
connected,
error,
stats,
connect,
disconnect,
reset
} = useCSElementSystem({
autoConnect,
enableHistory: true,
enableReactivity: true,
enablePersistence: true
});
if (error) {
return (
<div className="error-screen">
<h2>Ошибка инициализации</h2>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>
Перезагрузить
</button>
</div>
);
}
if (!connected || !root) {
return (
<div className="loading-screen">
<h2>Инициализация CSElement...</h2>
<div className="spinner"></div>
</div>
);
}
return (
<div className="app">
<header className="app-header">
<h1>CSElement React App</h1>
<div className="system-stats">
<span>Элементов: {stats.elementCount}</span>
<span>Подключено: {connected ? '✅' : '❌'}</span>
{stats.lastUpdate && (
<span>Обновлено: {stats.lastUpdate.toLocaleTimeString()}</span>
)}
</div>
<div className="system-controls">
<button onClick={disconnect}>Отключить</button>
<button onClick={connect}>Подключить</button>
<button onClick={reset} className="danger">Сброс</button>
</div>
</header>
<main className="app-content">
<WorkspaceManager root={root} />
</main>
</div>
);
}
function WorkspaceManager({ root }: { root: CSElement }) {
const workspaces = useQuery(root, '[type="workspace"]');
return (
<div className="workspace-manager">
<h2>Рабочие пространства</h2>
{workspaces.length === 0 ? (
<EmptyWorkspaceState root={root} />
) : (
<div className="workspaces">
{workspaces.map(workspace => (
<WorkspaceCard key={workspace.id} workspace={workspace} />
))}
</div>
)}
</div>
);
}
```
## Реактивные компоненты
### Компонент с автоматическими обновлениями
```tsx
import React, { useEffect, useState } from 'react';
import { CSElement } from 'cs-element';
interface ReactiveElementProps {
element: CSElement;
children: (element: CSElement, data: any) => React.ReactNode;
}
function ReactiveElement({ element, children }: ReactiveElementProps) {
const [data, setData] = useState(element.getAllData());
const [updateCount, setUpdateCount] = useState(0);
useEffect(() => {
// Подписываемся на изменения элемента
const unsubscribe = element.subscribe('dataChanged', (newData) => {
setData({ ...newData });
setUpdateCount(prev => prev + 1);
});
// Подписываемся на изменения структуры
const unsubscribeStructure = element.subscribe('elementAdded', () => {
setUpdateCount(prev => prev + 1);
});
const unsubscribeRemoval = element.subscribe('elementRemoved', () => {
setUpdateCount(prev => prev + 1);
});
return () => {
unsubscribe();
unsubscribeStructure();
unsubscribeRemoval();
};
}, [element]);
return (
<div className="reactive-element" data-updates={updateCount}>
{children(element, data)}
</div>
);
}
// Использование реактивного компонента
function DocumentEditor({ document }: { document: CSElement }) {
return (
<ReactiveElement element={document}>
{(doc, data) => (
<div className="document-editor">
<h2>{data.title || 'Без названия'}</h2>
<div className="document-meta">
<span>Автор: {data.author}</span>
<span>Изменен: {data.lastModified}</span>
<span>Элементов: {doc.elementsCount()}</span>
</div>
<div className="document-content">
<textarea
value={data.content || ''}
onChange={(e) => doc.setData('content', e.target.value)}
placeholder="Введите содержимое документа..."
/>
</div>
<div className="document-sections">
<h3>Разделы</h3>
<SectionList document={doc} />
</div>
</div>
)}
</ReactiveElement>
);
}
function SectionList({ document }: { document: CSElement }) {
const sections = useQuery(document, '[type="section"]');
const addSection = async () => {
const section = await document.addElement(`section-${Date.now()}`) as CSElement;
await section.setData('type', 'section');
await section.setData('title', 'Новый раздел');
await section.setData('content', '');
};
return (
<div className="section-list">
{sections.map(section => (
<SectionItem key={section.id} section={section} />
))}
<button onClick={addSection} className="add-section">
+ Добавить раздел
</button>
</div>
);
}
function SectionItem({ section }: { section: CSElement }) {
const { update, remove } = useElement({ name: section.name });
return (
<div className="section-item">
<input
type="text"
value={section.getData('title') || ''}
onChange={(e) => update({ title: e.target.value })}
className="section-title"
/>
<textarea
value={section.getData('content') || ''}
onChange={(e) => update({ content: e.target.value })}
className="section-content"
rows={3}
/>
<button onClick={remove} className="remove-section">
Удалить
</button>
</div>
);
}
```
## Управление состоянием
### Глобальное состояние через CSElement
```tsx
import React, { createContext, useContext } from 'react';
import { useCSElementSystem } from 'cs-element/react';
// Создаем контекст для глобального состояния
const AppStateContext = createContext<{
appState: CSElement | null;
updateAppState: (updates: Record<string, any>) => Promise<void>;
} | null>(null);
function AppStateProvider({ children }: { children: React.ReactNode }) {
const { root } = useCSElementSystem({ autoConnect: true });
const appState = useQueryOne(root, '[name="app-state"]') ||
React.useMemo(() => {
if (root) {
const state = new CSElement('app-state');
root.addElement(state);
// Инициализируем начальное состояние
state.setData('theme', 'light');
state.setData('language', 'ru');
state.setData('user', null);
state.setData('notifications', []);
return state;
}
return null;
}, [root]);
const updateAppState = async (updates: Record<string, any>) => {
if (appState) {
for (const [key, value] of Object.entries(updates)) {
await appState.setData(key, value);
}
}
};
return (
<AppStateContext.Provider value={{ appState, updateAppState }}>
{children}
</AppStateContext.Provider>
);
}
// Хук для использования глобального состояния
function useAppState() {
const context = useContext(AppStateContext);
if (!context) {
throw new Error('useAppState must be used within AppStateProvider');
}
return context;
}
// Компонент для управления темой
function ThemeToggle() {
const { appState, updateAppState } = useAppState();
if (!appState) return null;
const currentTheme = appState.getData('theme');
const toggleTheme = () => {
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
updateAppState({ theme: newTheme });
};
return (
<button onClick={toggleTheme} className="theme-toggle">
{currentTheme === 'light' ? '🌙' : '☀️'}
{currentTheme === 'light' ? 'Темная тема' : 'Светлая тема'}
</button>
);
}
// Компонент уведомлений
function NotificationCenter() {
const { appState, updateAppState } = useAppState();
if (!appState) return null;
const notifications = appState.getData('notifications') || [];
const addNotification = (message: string, type: 'info' | 'success' | 'error' = 'info') => {
const newNotification = {
id: Date.now(),
message,
type,
timestamp: new Date()
};
updateAppState({
notifications: [...notifications, newNotification]
});
// Автоматически удаляем через 5 секунд
setTimeout(() => {
removeNotification(newNotification.id);
}, 5000);
};
const removeNotification = (id: number) => {
updateAppState({
notifications: notifications.filter((n: any) => n.id !== id)
});
};
return (
<div className="notification-center">
<div className="notifications">
{notifications.map((notification: any) => (
<div
key={notification.id}
className={`notification ${notification.type}`}
>
<span>{notification.message}</span>
<button onClick={() => removeNotification(notification.id)}>
×
</button>
</div>
))}
</div>
<div className="notification-controls">
<button onClick={() => addNotification('Информационное сообщение')}>
Добавить уведомление
</button>
<button onClick={() => addNotification('Успешная операция', 'success')}>
Успех
</button>
<button onClick={() => addNotification('Произошла ошибка', 'error')}>
Ошибка
</button>
</div>
</div>
);
}
```
## Оптимизация производительности
### Мемоизация и оптимизация рендеров
```tsx
import React, { memo, useMemo, useCallback } from 'react';
import { useQuery } from 'cs-element/react';
// Оптимизированный компонент списка
const OptimizedList = memo(function OptimizedList({
container,
filter
}: {
container: CSElement;
filter: string;
}) {
// Мемоизируем селектор
const selector = useMemo(() => {
if (!filter) return '*';
return `[type="${filter}"]`;
}, [filter]);
const items = useQuery(container, selector);
// Мемоизируем обработчики
const handleItemClick = useCallback((item: CSElement) => {
console.log('Clicked item:', item.name);
}, []);
const handleItemUpdate = useCallback(async (item: CSElement, updates: any) => {
for (const [key, value] of Object.entries(updates)) {
await item.setData(key, value);
}
}, []);
return (
<div className="optimized-list">
{items.map(item => (
<OptimizedListItem
key={item.id}
item={item}
onClick={handleItemClick}
onUpdate={handleItemUpdate}
/>
))}
</div>
);
});
// Оптимизированный элемент списка
const OptimizedListItem = memo(function OptimizedListItem({
item,
onClick,
onUpdate
}: {
item: CSElement;
onClick: (item: CSElement) => void;
onUpdate: (item: CSElement, updates: any) => Promise<void>;
}) {
// Мемоизируем данные элемента
const itemData = useMemo(() => item.getAllData(), [item]);
const handleClick = useCallback(() => {
onClick(item);
}, [item, onClick]);
const handleUpdate = useCallback((updates: any) => {
onUpdate(item, updates);
}, [item, onUpdate]);
return (
<div className="list-item" onClick={handleClick}>
<h4>{itemData.title}</h4>
<p>{itemData.description}</p>
<div className="item-controls">
<button onClick={() => handleUpdate({ featured: !itemData.featured })}>
{itemData.featured ? 'Убрать из избранного' : 'В избранное'}
</button>
</div>
</div>
);
});
// Виртуализированный список для больших коллекций
function VirtualizedList({ container }: { container: CSElement }) {
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 });
const allItems = useQuery(container, '*');
// Показываем только видимые элементы
const visibleItems = useMemo(() => {
return allItems.slice(visibleRange.start, visibleRange.end);
}, [allItems, visibleRange]);
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
const { scrollTop, clientHeight, scrollHeight } = e.currentTarget;
const itemHeight = 60; // Примерная высота элемента
const itemsPerView = Math.ceil(clientHeight / itemHeight);
const start = Math.floor(scrollTop / itemHeight);
const end = Math.min(start + itemsPerView + 10, allItems.length);
setVisibleRange({ start, end });
}, [allItems.length]);
return (
<div
className="virtualized-list"
onScroll={handleScroll}
style={{ height: '400px', overflowY: 'auto' }}
>
<div style={{ height: allItems.length * 60 }}>
<div style={{ transform: `translateY(${visibleRange.start * 60}px)` }}>
{visibleItems.map(item => (
<OptimizedListItem
key={item.id}
item={item}
onClick={() => {}}
onUpdate={async () => {}}
/>
))}
</div>
</div>
</div>
);
}
```
## Полный пример: React приложение с CSElement
```tsx
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import {
useCSElementSystem,
useElement,
useQuery,
useQueryOne
} from 'cs-element/react';
import './App.css';
// Главный компонент приложения
function App() {
return (
<AppStateProvider>
<div className="app">
<Header />
<main className="main-content">
<ProjectManager />
</main>
</div>
</AppStateProvider>
);
}
// Провайдер состояния приложения
function AppStateProvider({ children }: { children: React.ReactNode }) {
const { root, connected, error } = useCSElementSystem({
autoConnect: true,
enableHistory: true,
enableReactivity: true
});
if (error) {
return <div className="error">Ошибка: {error.message}</div>;
}
if (!connected || !root) {
return <div className="loading">Загрузка...</div>;
}
return (
<AppContext.Provider value={{ root }}>
{children}
</AppContext.Provider>
);
}
const AppContext = React.createContext<{ root: CSElement | null }>({ root: null });
function useAppContext() {
const context = React.useContext(AppContext);
if (!context.root) {
throw new Error('useAppContext must be used within AppStateProvider');
}
return context;
}
// Компонент шапки
function Header() {
const { root } = useAppContext();
const projects = useQuery(root, '[type="project"]');
return (
<header className="header">
<h1>🚀 Project Manager</h1>
<div className="header-stats">
<span className="stat">
📁 Проектов: {projects.length}
</span>
<span className="stat">
✅ Задач: {projects.reduce((total, project) => {
return total + project.query('[type="task"]').length;
}, 0)}
</span>
</div>
<UserProfile />
</header>
);
}
// Профиль пользователя
function UserProfile() {
const { root } = useAppContext();
const { element: user, update } = useElement({
name: 'current-user',
parent: root,
data: {
name: 'Пользователь',
avatar: '👤',
preferences: {
theme: 'light'
}
}
});
if (!user) return null;
const toggleTheme = () => {
const currentTheme = user.getData('preferences')?.theme || 'light';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
update({
preferences: {
...user.getData('preferences'),
theme: newTheme
}
});
// Применяем тему к документу
document.body.className = newTheme;
};
return (
<div className="user-profile">
<span className="user-avatar">{user.getData('avatar')}</span>
<span className="user-name">{user.getData('name')}</span>
<button onClick={toggleTheme} className="theme-toggle">
{user.getData('preferences')?.theme === 'light' ? '🌙' : '☀️'}
</button>
</div>
);
}
// Менеджер проектов
function ProjectManager() {
const { root } = useAppContext();
const projects = useQuery(root, '[type="project"]');
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
const selectedProject = selectedProjectId
? projects.find(p => p.id === selectedProjectId) || null
: null;
const createProject = async () => {
const projectName = prompt('Название проекта:');
if (!projectName) return;
const project = await root.addElement(`project-${Date.now()}`) as CSElement;
await project.setData('type', 'project');
await project.setData('name', projectName);
await project.setData('description', '');
await project.setData('status', 'active');
await project.setData('createdAt', new Date().toISOString());
setSelectedProjectId(project.id);
};
return (
<div className="project-manager">
<aside className="sidebar">
<div className="sidebar-header">
<h2>Проекты</h2>
<button onClick={createProject} className="create-btn">
+ Создать
</button>
</div>
<div className="project-list">
{projects.map(project => (
<ProjectItem
key={project.id}
project={project}
isSelected={project.id === selectedProjectId}
onSelect={() => setSelectedProjectId(project.id)}
/>
))}
</div>
</aside>
<main className="content">
{selectedProject ? (
<ProjectDetails project={selectedProject} />
) : (
<div className="empty-state">
<h2>Выберите проект</h2>
<p>Выберите проект из списка или создайте новый</p>
</div>
)}
</main>
</div>
);
}
// Элемент проекта в списке
function ProjectItem({
project,
isSelected,
onSelect
}: {
project: CSElement;
isSelected: boolean;
onSelect: () => void;
}) {
const tasks = useQuery(project, '[type="task"]');
const completedTasks = tasks.filter(task => task.getData('completed'));
return (
<div
className={`project-item ${isSelected ? 'selected' : ''}`}
onClick={onSelect}
>
<h3>{project.getData('name')}</h3>
<div className="project-stats">
<span className="task-count">
{completedTasks.length}/{tasks.length} задач
</span>
<span className={`status ${project.getData('status')}`}>
{project.getData('status')}
</span>
</div>
</div>
);
}
// Детали проекта
function ProjectDetails({ project }: { project: CSElement }) {
const { update } = useElement({ name: project.name });
const tasks = useQuery(project, '[type="task"]');
const [showCompleted, setShowCompleted] = useState(true);
const filteredTasks = showCompleted
? tasks
: tasks.filter(task => !task.getData('completed'));
const createTask = async () => {
const taskTitle = prompt('Название задачи:');
if (!taskTitle) return;
const task = await project.addElement(`task-${Date.now()}`) as CSElement;
await task.setData('type', 'task');
await task.setData('title', taskTitle);
await task.setData('description', '');
await task.setData('completed', false);
await task.setData('priority', 'medium');
await task.setData('createdAt', new Date().toISOString());
};
return (
<div className="project-details">
<header className="project-header">
<div className="project-info">
<input
type="text"
value={project.getData('name') || ''}
onChange={(e) => update({ name: e.target.value })}
className="project-title"
/>
<textarea
value={project.getData('description') || ''}
onChange={(e) => update({ description: e.target.value })}
placeholder="Описание проекта..."
className="project-description"
/>
</div>
<div className="project-actions">
<select
value={project.getData('status') || 'active'}
onChange={(e) => update({ status: e.target.value })}
>
<option value="active">Активный</option>
<option value="paused">Приостановлен</option>
<option value="completed">Завершен</option>
</select>
</div>
</header>
<div className="tasks-section">
<div className="tasks-header">
<h3>Задачи</h3>
<div className="tasks-controls">
<label>
<input
type="checkbox"
checked={showCompleted}
onChange={(e) => setShowCompleted(e.target.checked)}
/>
Показать завершенные
</label>
<button onClick={createTask} className="create-task-btn">
+ Добавить задачу
</button>
</div>
</div>
<div className="tasks-list">
{filteredTasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</div>
</div>
</div>
);
}
// Элемент задачи
function TaskItem({ task }: { task: CSElement }) {
const { update, remove } = useElement({ name: task.name });
const [isEditing, setIsEditing] = useState(false);
const toggleCompleted = () => {
update({ completed: !task.getData('completed') });
};
const updatePriority = (priority: string) => {
update({ priority });
};
const deleteTask = () => {
if (confirm('Удалить задачу?')) {
remove();
}
};
return (
<div className={`task-item ${task.getData('completed') ? 'completed' : ''}`}>
<div className="task-main">
<input
type="checkbox"
checked={task.getData('completed') || false}
onChange={toggleCompleted}
/>
{isEditing ? (
<input
type="text"
value={task.getData('title') || ''}
onChange={(e) => update({ title: e.target.value })}
onBlur={() => setIsEditing(false)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
setIsEditing(false);
}
}}
autoFocus
/>
) : (
<span
className="task-title"
onDoubleClick={() => setIsEditing(true)}
>
{task.getData('title')}
</span>
)}
</div>
<div className="task-controls">
<select
value={task.getData('priority') || 'medium'}
onChange={(e) => updatePriority(e.target.value)}
className={`priority ${task.getData('priority')}`}
>
<option value="low">Низкий</option>
<option value="medium">Средний</option>
<option value="high">Высокий</option>
</select>
<button onClick={deleteTask} className="delete-btn">
🗑️
</button>
</div>
</div>
);
}
// CSS стили
const styles = `
.app {
min-height: 100vh;
background: var(--bg-color, #f5f5f5);
color: var(--text-color, #333);
}
.header {
background: white;
padding: 1rem 2rem;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.project-manager {
display: flex;
height: calc(100vh - 80px);
}
.sidebar {
width: 300px;
background: white;
border-right: 1px solid #eee;
padding: 1rem;
}
.content {
flex: 1;
padding: 2rem;
}
.project-item {
padding: 1rem;
border: 1px solid #eee;
border-radius: 8px;
margin-bottom: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.project-item:hover {
background: #f9f9f9;
}
.project-item.selected {
background: #e3f2fd;
border-color: #2196f3;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border: 1px solid #eee;
border-radius: 8px;
margin-bottom: 0.5rem;
}
.task-item.completed {
opacity: 0.6;
}
.task-item.completed .task-title {
text-decoration: line-through;
}
/* Темная тема */
.dark {
--bg-color: #1a1a1a;
--text-color: #ffffff;
}
.dark .header,
.dark .sidebar,
.dark .project-item,
.dark .task-item {
background: #2d2d2d;
border-color: #444;
color: white;
}
`;
// Добавляем стили
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
// Рендерим приложение
const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(<App />);
}
```
## 🎯 Заключение
React интеграция CSElement предоставляет:
- **⚛️ Нативную интеграцию** - хуки для всех основных операций
- **🔄 Автоматическую реактивность** - UI обновляется при изменении данных
- **⚡ Оптимизированную производительность** - мемоизация и виртуализация
- **🎛️ Гибкое управление состоянием** - от локального до глобального
- **🔧 Простоту использования** - минимум кода для максимума функциональности
Используйте React интеграцию для создания современных, реактивных приложений с CSElement в качестве источника данных и состояния.
---
**Следующий раздел:** [Продвинутые возможности](07-advanced-features.md) - изучите плагины, DevTools, миграции и другие продвинутые функции.