UNPKG

cs-element

Version:

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

865 lines (708 loc) 26.9 kB
# 📋 Управление коллекциями в CSElement CSElement превосходно подходит для работы с коллекциями данных. В этом разделе мы изучим все аспекты создания, управления и оптимизации списков элементов. ## 🎯 Основы работы с коллекциями ### Создание коллекций ```typescript import { CSElement } from 'cs-element'; // Создаем корневую коллекцию const library = CSElement.create('Library') .withData('name', 'Городская библиотека') .withData('founded', 1945); // Добавляем коллекции книг const books = await library.addElement('books'); const authors = await library.addElement('authors'); const categories = await library.addElement('categories'); console.log(`Создана библиотека с ${library.elementsCount()} разделами`); ``` ### Добавление элементов в коллекции ```typescript // Простое добавление await books.addElement('book1', { data: new Map([ ['title', 'Война и мир'], ['author', 'Лев Толстой'], ['year', 1869], ['pages', 1225], ['genre', 'роман'], ['available', true] ]) }); // Добавление с индексом (для сортировки) await books.addElement('book2', { index: 0, // Будет первой книгой data: new Map([ ['title', 'Преступление и наказание'], ['author', 'Федор Достоевский'], ['year', 1866], ['pages', 671], ['genre', 'роман'], ['available', false] ]) }); // Fluent API для быстрого создания books.withChild('book3') .withData('title', 'Мастер и Маргарита') .withData('author', 'Михаил Булгаков') .withData('year', 1967) .withData('pages', 384) .withData('genre', 'фантастика') .withData('available', true); ``` ## 🔍 Мощная система поиска ### Базовые селекторы ```typescript // Поиск по атрибутам const availableBooks = books.query('[available=true]'); const novels = books.query('[genre=роман]'); const oldBooks = books.query('[year<1900]'); // Комбинированные селекторы const availableNovels = books.query('[available=true][genre=роман]'); const modernBooks = books.query('[year>=1950]'); console.log(`Доступных романов: ${availableNovels.length}`); ``` ### Продвинутые запросы ```typescript // Поиск по частичному совпадению const tolstoyBooks = books.findElements(book => book.getData('author')?.includes('Толстой') ); // Поиск с множественными условиями const longBooks = books.findElements(book => { const pages = book.getData('pages'); const available = book.getData('available'); return pages > 500 && available; }); // Поиск с сортировкой const booksByYear = books.getAllElements() .sort((a, b) => a.getData('year') - b.getData('year')); // Поиск с пагинацией function getBooks(page: number, pageSize: number = 10) { const start = page * pageSize; const end = start + pageSize; return books.getAllElements().slice(start, end); } const firstPage = getBooks(0); console.log(`Первая страница: ${firstPage.length} книг`); ``` ### Сложные запросы с QueryEngine ```typescript // Создаем селектор с помощью QueryEngine const complexQuery = CSElement.createSelector() .where('available', true) .and('pages', '>', 300) .and('year', '>=', 1950) .orderBy('year', 'desc') .limit(5); const selectedBooks = books.query(complexQuery); console.log(`Найдено книг: ${selectedBooks.length}`); // Группировка по жанрам const booksByGenre = new Map(); books.getAllElements().forEach(book => { const genre = book.getData('genre'); if (!booksByGenre.has(genre)) { booksByGenre.set(genre, []); } booksByGenre.get(genre).push(book); }); console.log('Книги по жанрам:', Object.fromEntries(booksByGenre)); ``` ## 🎨 Продвинутые техники управления коллекциями ### Создание умных коллекций с автоматической сортировкой ```typescript class SmartCollection extends CSElement { private sortKey: string; private sortOrder: 'asc' | 'desc'; constructor(name: string, sortKey: string = 'name', sortOrder: 'asc' | 'desc' = 'asc') { super(name); this.sortKey = sortKey; this.sortOrder = sortOrder; this.setupAutoSort(); } private setupAutoSort() { // Автоматическая сортировка при добавлении элементов this.on('element:added', () => { this.sort(); }); this.on('data:changed', (element, key) => { if (key === this.sortKey) { this.sort(); } }); } private sort() { const elements = this.getAllElements(); elements.sort((a, b) => { const aValue = a.getData(this.sortKey); const bValue = b.getData(this.sortKey); if (this.sortOrder === 'asc') { return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; } else { return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; } }); // Обновляем индексы elements.forEach((element, index) => { element._index = index; }); } setSortOrder(key: string, order: 'asc' | 'desc' = 'asc') { this.sortKey = key; this.sortOrder = order; this.sort(); } } // Использование const sortedBooks = new SmartCollection('SortedBooks', 'title', 'asc'); ``` ### Коллекции с фильтрацией в реальном времени ```typescript class FilteredCollection extends CSElement { private filters: Map<string, (element: CSElement) => boolean> = new Map(); private filteredElements: CSElement[] = []; addFilter(name: string, predicate: (element: CSElement) => boolean) { this.filters.set(name, predicate); this.updateFiltered(); } removeFilter(name: string) { this.filters.delete(name); this.updateFiltered(); } private updateFiltered() { this.filteredElements = this.getAllElements().filter(element => { return Array.from(this.filters.values()).every(filter => filter(element)); }); this.emit('filtered:updated', this.filteredElements); } getFiltered(): readonly CSElement[] { return this.filteredElements; } getFilteredCount(): number { return this.filteredElements.length; } // Переопределяем методы для обновления фильтра async addElement(value: any, options: any = {}) { const result = await super.addElement(value, options); this.updateFiltered(); return result; } async removeElement(nameOrIndex: any) { const result = await super.removeElement(nameOrIndex); this.updateFiltered(); return result; } } // Использование const filteredBooks = new FilteredCollection('FilteredBooks'); // Добавляем фильтры filteredBooks.addFilter('available', book => book.getData('available') === true); filteredBooks.addFilter('modern', book => book.getData('year') >= 1950); filteredBooks.addFilter('fiction', book => ['роман', 'фантастика', 'детектив'].includes(book.getData('genre')) ); // Подписываемся на обновления filteredBooks.on('filtered:updated', (filtered) => { console.log(`Отфильтровано книг: ${filtered.length}`); }); ``` ## 📊 Работа с большими коллекциями ### Виртуализация и ленивая загрузка ```typescript class VirtualizedCollection extends CSElement { private pageSize: number; private loadedPages: Set<number> = new Set(); private totalItems: number = 0; constructor(name: string, pageSize: number = 50) { super(name); this.pageSize = pageSize; } async loadPage(pageNumber: number): Promise<CSElement[]> { if (this.loadedPages.has(pageNumber)) { return this.getPage(pageNumber); } // Симуляция загрузки данных const pageData = await this.fetchPageData(pageNumber); for (const item of pageData) { await this.addElement(`item_${pageNumber}_${item.id}`, { data: new Map(Object.entries(item)) }); } this.loadedPages.add(pageNumber); return this.getPage(pageNumber); } getPage(pageNumber: number): CSElement[] { const start = pageNumber * this.pageSize; const end = start + this.pageSize; return this.getAllElements().slice(start, end); } async getVisibleRange(startIndex: number, endIndex: number): Promise<CSElement[]> { const startPage = Math.floor(startIndex / this.pageSize); const endPage = Math.floor(endIndex / this.pageSize); // Загружаем необходимые страницы for (let page = startPage; page <= endPage; page++) { if (!this.loadedPages.has(page)) { await this.loadPage(page); } } return this.getAllElements().slice(startIndex, endIndex + 1); } private async fetchPageData(pageNumber: number): Promise<any[]> { // Здесь был бы реальный API вызов return Array.from({ length: this.pageSize }, (_, i) => ({ id: pageNumber * this.pageSize + i, title: `Book ${pageNumber * this.pageSize + i}`, author: `Author ${Math.floor(Math.random() * 100)}`, year: 1900 + Math.floor(Math.random() * 124) })); } } ``` ### Индексирование для быстрого поиска ```typescript class IndexedCollection extends CSElement { private indexes: Map<string, Map<any, CSElement[]>> = new Map(); createIndex(fieldName: string) { const index = new Map(); this.getAllElements().forEach(element => { const value = element.getData(fieldName); if (value !== undefined) { if (!index.has(value)) { index.set(value, []); } index.get(value).push(element); } }); this.indexes.set(fieldName, index); // Обновляем индекс при изменении данных this.on('element:added', (element) => { this.updateIndex(fieldName, element); }); this.on('data:changed', (element, key, newValue, oldValue) => { if (key === fieldName) { this.removeFromIndex(fieldName, element, oldValue); this.updateIndex(fieldName, element); } }); } private updateIndex(fieldName: string, element: CSElement) { const index = this.indexes.get(fieldName); if (!index) return; const value = element.getData(fieldName); if (value !== undefined) { if (!index.has(value)) { index.set(value, []); } index.get(value).push(element); } } private removeFromIndex(fieldName: string, element: CSElement, value: any) { const index = this.indexes.get(fieldName); if (!index || !index.has(value)) return; const elements = index.get(value); const elementIndex = elements.indexOf(element); if (elementIndex > -1) { elements.splice(elementIndex, 1); if (elements.length === 0) { index.delete(value); } } } findByIndex(fieldName: string, value: any): CSElement[] { const index = this.indexes.get(fieldName); return index?.get(value) || []; } getIndexStats(fieldName: string): { uniqueValues: number, totalElements: number } { const index = this.indexes.get(fieldName); if (!index) return { uniqueValues: 0, totalElements: 0 }; const uniqueValues = index.size; const totalElements = Array.from(index.values()) .reduce((sum, arr) => sum + arr.length, 0); return { uniqueValues, totalElements }; } } // Использование const indexedBooks = new IndexedCollection('IndexedBooks'); indexedBooks.createIndex('author'); indexedBooks.createIndex('genre'); indexedBooks.createIndex('year'); // Быстрый поиск по индексу const tolstoyBooks = indexedBooks.findByIndex('author', 'Лев Толстой'); const novels = indexedBooks.findByIndex('genre', 'роман'); console.log('Статистика индексов:'); console.log('Авторы:', indexedBooks.getIndexStats('author')); console.log('Жанры:', indexedBooks.getIndexStats('genre')); ``` ## 🔄 Реактивные коллекции ### Live Collections с автообновлением ```typescript class LiveCollection extends CSElement { private liveQuery: any; private selector: string; constructor(name: string, sourceCollection: CSElement, selector: string) { super(name); this.selector = selector; this.setupLiveQuery(sourceCollection); } private setupLiveQuery(sourceCollection: CSElement) { this.liveQuery = CSElement.createLiveQuery(this.selector, { root: sourceCollection, debounce: 50 }); this.liveQuery.onResults((results: CSElement[]) => { // Очищаем текущие элементы this.clear(); // Добавляем новые результаты results.forEach(async (element, index) => { await this.addElement(`item_${index}`, { data: element.data }); }); this.emit('collection:updated', results); }); CSElement.liveQueries.start(this.liveQuery.id); } private clear() { const elements = [...this.getAllElements()]; elements.forEach(element => { this.removeElement(element); }); } updateSelector(newSelector: string) { this.selector = newSelector; CSElement.liveQueries.stop(this.liveQuery.id); // Создаем новый live query с обновленным селектором // this.setupLiveQuery(sourceCollection); // Нужна ссылка на источник } destroy() { if (this.liveQuery) { CSElement.liveQueries.stop(this.liveQuery.id); } super.destroy(); } } ``` ### Computed коллекции ```typescript class ComputedCollection extends CSElement { private computedProperty: any; private computeFunction: () => CSElement[]; constructor(name: string, computeFunction: () => CSElement[]) { super(name); this.computeFunction = computeFunction; this.setupComputed(); } private setupComputed() { this.computedProperty = CSElement.computed(() => { return this.computeFunction(); }); // Обновляем коллекцию при изменении computed свойства CSElement.watch([this.computedProperty.id], (newValue) => { this.updateCollection(newValue); }); } private updateCollection(newElements: CSElement[]) { // Очищаем текущие элементы this.clear(); // Добавляем новые элементы newElements.forEach(async (element, index) => { await this.addElement(`computed_${index}`, { data: element.data }); }); this.emit('computed:updated', newElements); } private clear() { const elements = [...this.getAllElements()]; elements.forEach(element => { this.removeElement(element); }); } get value(): CSElement[] { return this.computedProperty.value; } } // Использование const availableBooks = new ComputedCollection('AvailableBooks', () => { return books.query('[available=true]'); }); const popularBooks = new ComputedCollection('PopularBooks', () => { return books.getAllElements() .filter(book => book.getData('rating') >= 4.5) .sort((a, b) => b.getData('rating') - a.getData('rating')) .slice(0, 10); }); ``` ## 🎯 Полный пример - Система управления библиотекой ```typescript import { CSElement } from 'cs-element'; class LibrarySystem { private library: CSElement; private books: IndexedCollection; private authors: CSElement; private members: CSElement; private loans: CSElement; // Реактивные коллекции private availableBooks: LiveCollection; private overdueLoans: LiveCollection; private popularBooks: ComputedCollection; constructor() { this.initializeLibrary(); this.setupCollections(); this.setupReactivity(); this.setupEventHandlers(); } private initializeLibrary() { this.library = CSElement.create('LibrarySystem') .withData('name', 'Центральная библиотека') .withData('established', 1925) .withData('totalBooks', 0) .withData('totalMembers', 0); } private setupCollections() { // Основные коллекции this.books = new IndexedCollection('Books'); this.authors = new CSElement('Authors'); this.members = new CSElement('Members'); this.loans = new CSElement('Loans'); // Добавляем в библиотеку this.library.withChild(this.books); this.library.withChild(this.authors); this.library.withChild(this.members); this.library.withChild(this.loans); // Создаем индексы для быстрого поиска this.books.createIndex('author'); this.books.createIndex('genre'); this.books.createIndex('isbn'); this.books.createIndex('available'); } private setupReactivity() { // Доступные книги this.availableBooks = new LiveCollection( 'AvailableBooks', this.books, '[available=true]' ); // Просроченные займы this.overdueLoans = new LiveCollection( 'OverdueLoans', this.loans, `[dueDate<${Date.now()}][returned=false]` ); // Популярные книги (computed) this.popularBooks = new ComputedCollection('PopularBooks', () => { return this.books.getAllElements() .filter(book => book.getData('loanCount') > 0) .sort((a, b) => b.getData('loanCount') - a.getData('loanCount')) .slice(0, 20); }); } private setupEventHandlers() { // Обновляем статистику при добавлении книг this.books.on('element:added', () => { this.library.setData('totalBooks', this.books.elementsCount()); }); // Обновляем статистику при добавлении читателей this.members.on('element:added', () => { this.library.setData('totalMembers', this.members.elementsCount()); }); // Обрабатываем займы книг this.loans.on('element:added', (loan) => { const bookId = loan.getData('bookId'); const book = this.books.getElementById(bookId); if (book) { book.setData('available', false); const loanCount = book.getData('loanCount') || 0; book.setData('loanCount', loanCount + 1); } }); // Обрабатываем возврат книг this.loans.on('data:changed', (loan, key, newValue) => { if (key === 'returned' && newValue === true) { const bookId = loan.getData('bookId'); const book = this.books.getElementById(bookId); if (book) { book.setData('available', true); } } }); } // Методы управления книгами async addBook(bookData: { title: string; author: string; isbn: string; genre: string; publishYear: number; pages: number; }) { const bookId = `book_${bookData.isbn}`; const book = await this.books.addElement(bookId, { data: new Map([ ['title', bookData.title], ['author', bookData.author], ['isbn', bookData.isbn], ['genre', bookData.genre], ['publishYear', bookData.publishYear], ['pages', bookData.pages], ['available', true], ['loanCount', 0], ['addedDate', new Date().toISOString()] ]) }); // Добавляем автора если его нет await this.ensureAuthorExists(bookData.author); return book; } private async ensureAuthorExists(authorName: string) { const existingAuthor = this.authors.findElements(author => author.getData('name') === authorName ); if (existingAuthor.length === 0) { await this.authors.addElement(`author_${Date.now()}`, { data: new Map([ ['name', authorName], ['bookCount', 1] ]) }); } else { const author = existingAuthor[0]; const bookCount = author.getData('bookCount') || 0; author.setData('bookCount', bookCount + 1); } } // Методы поиска searchBooks(query: { title?: string; author?: string; genre?: string; available?: boolean; yearFrom?: number; yearTo?: number; }): CSElement[] { let results = this.books.getAllElements(); if (query.title) { results = results.filter(book => book.getData('title')?.toLowerCase().includes(query.title.toLowerCase()) ); } if (query.author) { results = this.books.findByIndex('author', query.author); } if (query.genre) { results = this.books.findByIndex('genre', query.genre); } if (query.available !== undefined) { results = this.books.findByIndex('available', query.available); } if (query.yearFrom || query.yearTo) { results = results.filter(book => { const year = book.getData('publishYear'); if (query.yearFrom && year < query.yearFrom) return false; if (query.yearTo && year > query.yearTo) return false; return true; }); } return results; } // Управление займами async loanBook(bookId: string, memberId: string, durationDays: number = 14): Promise<boolean> { const book = this.books.getElementById(bookId); const member = this.members.getElementById(memberId); if (!book || !member || !book.getData('available')) { return false; } const loanId = `loan_${Date.now()}`; const dueDate = new Date(); dueDate.setDate(dueDate.getDate() + durationDays); await this.loans.addElement(loanId, { data: new Map([ ['bookId', bookId], ['memberId', memberId], ['loanDate', new Date().toISOString()], ['dueDate', dueDate.getTime()], ['returned', false], ['renewalCount', 0] ]) }); return true; } async returnBook(loanId: string): Promise<boolean> { const loan = this.loans.getElementById(loanId); if (!loan || loan.getData('returned')) { return false; } await loan.setData('returned', true); await loan.setData('returnDate', new Date().toISOString()); return true; } // Получение статистики getLibraryStats() { return { totalBooks: this.books.elementsCount(), availableBooks: this.availableBooks.elementsCount(), totalAuthors: this.authors.elementsCount(), totalMembers: this.members.elementsCount(), activeLoans: this.loans.query('[returned=false]').length, overdueLoans: this.overdueLoans.elementsCount(), popularBooks: this.popularBooks.value.slice(0, 5).map(book => ({ title: book.getData('title'), author: book.getData('author'), loanCount: book.getData('loanCount') })) }; } // Получение аналитики по жанрам getGenreAnalytics() { const genreStats = new Map(); this.books.getAllElements().forEach(book => { const genre = book.getData('genre'); if (!genreStats.has(genre)) { genreStats.set(genre, { totalBooks: 0, availableBooks: 0, totalLoans: 0 }); } const stats = genreStats.get(genre); stats.totalBooks++; if (book.getData('available')) { stats.availableBooks++; } stats.totalLoans += book.getData('loanCount') || 0; }); return Object.fromEntries(genreStats); } } // Использование системы const library = new LibrarySystem(); // Добавляем книги await library.addBook({ title: 'Война и мир', author: 'Лев Толстой', isbn: '978-5-17-082417-1', genre: 'роман', publishYear: 1869, pages: 1225 }); await library.addBook({ title: '1984', author: 'Джордж Оруэлл', isbn: '978-5-17-082418-8', genre: 'антиутопия', publishYear: 1949, pages: 328 }); // Поиск книг const novels = library.searchBooks({ genre: 'роман' }); const availableBooks = library.searchBooks({ available: true }); const modernBooks = library.searchBooks({ yearFrom: 1950 }); console.log('Статистика библиотеки:', library.getLibraryStats()); console.log('Аналитика по жанрам:', library.getGenreAnalytics()); ``` ## 🎉 Заключение Управление коллекциями в CSElement предоставляет невероятную гибкость и мощь: - **🔍 Мощный поиск** - от простых селекторов до сложных запросов - **⚡ Реактивность** - автоматическое обновление при изменениях - **📊 Индексирование** - быстрый доступ к данным - **🎯 Виртуализация** - работа с большими объемами данных - **🔄 Live Collections** - коллекции, обновляющиеся в реальном времени **Готовы к изучению реактивности?** Переходите к [следующему разделу](03-reactivity.md)! 🚀