UNPKG

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
# ⚛️ 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, миграции и другие продвинутые функции.