Gathering detailed insights and metrics for cellx
Gathering detailed insights and metrics for cellx
Gathering detailed insights and metrics for cellx
Gathering detailed insights and metrics for cellx
cellx-decorators
cellx-decorators ================
cellx-indexed-collections
Индексируемые коллекции для [cellx](https://github.com/Riim/cellx).
cellx-collections
If you record to the cell an instance of class which inherits of `cellx.EventEmitter`, then the cell will subscribe to its `change` event and will claim it as own:
cellx-lite
<p align="right"> <a href="https://github.com/Riim/cellx/blob/master/README.ru.md">Этот документ на русском</a> </p>
npm install cellx
Typescript
Module System
Node Version
NPM Version
TypeScript (69.66%)
HTML (23.05%)
JavaScript (7.29%)
Love this project? Help keep it running — sponsor us today! 🚀
Total Downloads
269,188
Last Day
1
Last Week
1
Last Month
357
Last Year
8,918
463 Stars
357 Commits
15 Forks
16 Watching
1 Branches
4 Contributors
Minified
Minified + Gzipped
Latest Version
2.0.1
Package Id
cellx@2.0.1
Unpacked Size
88.04 kB
Size
16.06 kB
File Count
21
NPM Version
10.8.2
Node Version
22.5.1
Publised On
08 Aug 2024
Cumulative downloads
Total Downloads
Last day
0%
1
Compared to previous day
Last week
-99.2%
1
Compared to previous week
Last month
-61.2%
357
Compared to previous month
Last year
-39.6%
8,918
Compared to previous year
Сверхбыстрая реализация реактивности для javascript.
npm i -S cellx
1let firstName = cellx('Матроскин'); 2let lastName = cellx('Кот'); 3 4let fullName = cellx(() => firstName.value + ' ' + lastName.value) 5 6fullName.subscribe(() => { 7 console.log('fullName:', fullName.value); 8}); 9 10console.log(fullName.value); 11// => 'Матроскин Кот' 12 13firstName.value = 'Шарик'; 14lastName.value = 'Пёс'; 15// => 'fullName: Шарик Пёс'
Несмотря на то, что изменились две зависимости ячейки fullName
, обработчик её изменения сработал только один раз.
Важной особенностью cellx-а является то, что он старается максимально избавиться как от лишних вызовов обработчиков
изменений, так и от лишних вызовов расчётных формул зависимых ячеек. В сочетании с ещё некоторыми оптимизациями это
приводит к идеальной скорости расчёта сложнейших сеток зависимостей.
Больше об этом можно узнать в статье Big State Managers Benchmark.
Также вам может быть интересна статья Разбираемся в сортах реактивности.
В одном из тестов, который используется для замера производительности, генерируется сетка из множества "слоёв", каждый из которых состоит из 4-x ячеек. Ячейки вычисляются из ячеек предыдущего слоя (кроме самого первого, он содержит исходные значения) по формуле A2=B1, B2=A1-C1, C2=B1+D1, D2=C1. Далее запоминается начальное время, меняются значения всех ячеек первого слоя и замеряется время, через которое все ячейки последнего слоя обновятся. Результаты теста (в милисекундах) с разным числом слоёв:
Library ↓ \ Number of computed layers → | 10 | 20 | 30 | 50 | 100 | 1000 | 5000 |
---|---|---|---|---|---|---|---|
cellx | <~1 | <~1 | <~1 | <~1 | <~1 | 4 | 20 |
VanillaJS (naive) | <~1 | 15 | 1750 | >300000 | >300000 | >300000 | >300000 |
Knockout | 10 | 750, increases in subsequent runs | 67250, increases in subsequent runs | >300000 | >300000 | >300000 | >300000 |
$jin.atom | 2 | 3 | 3 | 4 | 6 | 40 | 230 |
$mol_atom | <~1 | <~1 | <~1 | 1 | 2 | 20 | RangeError: Maximum call stack size exceeded |
Kefir.js | 25 | 2500 | >300000 | >300000 | >300000 | >300000 | >300000 |
MobX | <~1 | <~1 | <~1 | 2 | 3 | 40 | RangeError: Maximum call stack size exceeded |
Исходники теста можно найти в папке perf. Плотность связей в реальных приложениях обычно ниже чем в данном тесте, то есть если в тесте определённая задержка появляется на 100 вычисляемых ячейках (25 слоёв), то в реальном приложении подобная задержка будет либо на большем числе ячеек, либо в формулах ячеек будут какие-то сложные расчёты (например, вычисление одного массива из другого).
Функциональный стиль:
1let num = cellx(1); 2let plusOne = cellx(() => num.value + 1); 3 4console.log(plusOne.value); 5// => 2
ООП стиль:
1import { cellx, define } from 'cellx'; 2 3class User { 4 name: string; 5 nameInitial: string; 6 7 constructor(name: string) { 8 define(this, { 9 name, 10 nameInitial: cellx(() => this.name.charAt(0).toUpperCase()) 11 }); 12 } 13} 14 15let user = new User('Матроскин'); 16 17console.log(user.nameInitial); 18// => 'M'
ООП стиль с декораторами:
1import { Computed, Observable } from 'cellx-decorators'; 2 3class User { 4 @Observable name: string; 5 6 @Computed get nameInitial() { 7 return this.name.charAt(0).toUpperCase(); 8 } 9 10 constructor(name: string) { 11 this.name = name; 12 } 13} 14 15let user = new User('Матроскин'); 16 17console.log(user.nameInitial); 18// => 'M'
Может использоваться для обработки значения при записи и перенаправления записи:
1function User() { 2 this.firstName = cellx(''); 3 this.lastName = cellx(''); 4 5 this.fullName = cellx( 6 () => (this.firstName.value + ' ' + this.lastName.value).trim(), 7 { 8 put: (name) => { 9 name = name.split(' '); 10 11 this.firstName.value = name[0]; 12 this.lastName.value = name[1]; 13 } 14 } 15 ); 16} 17 18let user = new User(); 19 20user.fullName.value = 'Матроскин Кот'; 21 22console.log(user.firstName.value); 23// => 'Матроскин' 24console.log(user.lastName.value); 25// => 'Кот'
Валидирует значение при записи и вычислении.
Валидация при записи в ячейку:
1let num = cellx(5, { 2 validate: (value) => { 3 if (typeof value != 'number') { 4 throw TypeError('Oops!'); 5 } 6 } 7}); 8 9try { 10 num('Йа строчка'); 11} catch (err) { 12 console.log(err.message); 13 // => 'Oops!' 14} 15 16console.log(num.value); 17// => 5
Валидация при вычислении ячейки:
1let someValue = cellx(5); 2 3let num = cellx(() => someValue.value, { 4 validate: (value) => { 5 if (typeof value != 'number') { 6 throw TypeError('Oops!'); 7 } 8 } 9}); 10 11num.subscribe((err) => { 12 console.log(err.message); 13}); 14 15someValue.value = 'Я строчка'; 16// => 'Oops!' 17 18console.log(someValue.value); 19// => 'Я строчка'
Добавляет обработчик изменения:
1let num = cellx(5); 2 3num.onChange((evt) => { 4 console.log(evt); 5}); 6 7num.value = 10; 8// => { prevValue: 5, value: 10 }
Снимает ранее добавленный обработчик изменения.
Добавляет обработчик ошибки:
1let someValue = cellx(1); 2 3let num = cellx(() => someValue.value, { 4 validate: (v) => { 5 if (v > 1) { 6 throw RangeError('Oops!'); 7 } 8 } 9}); 10 11num.onError(evt => { 12 console.log(evt.error.message); 13}); 14 15someValue.value = 2; 16// => 'Oops!'
Снимает ранее добавленный обработчик ошибки.
Подписывает на события change
и error
. В обработчик первым аргументом приходит объект ошибки, вторым — событие.
1fullName.subscribe((err, evt) => { 2 if (err) { 3 // 4 } else { 5 // 6 } 7});
Отписывает от событий change
и error
.
Формула вычисляемой ячейки может быть написана так, что набор зависимостей может со временем меняться. Например:
1let user = { 2 firstName: cellx(''), 3 lastName: cellx(''), 4 5 name: cellx(() => user.firstName.value || user.lastName.value) 6};
Здесь пока firstName
является пустой строкой, ячейка name
подписана и на firstName
и на lastName
,
так как изменение любого из них приведёт к изменению её значения. Если же задать firstName
-у какую-то не пустую
строку, то, при перевычислении значения name
, до чтения lastName
в формуле просто не дойдёт,
то есть значение ячейки name
с этого момента уже никак не зависит от lastName
.
В таких случаях ячейки автоматически отписываются от незначимых для них зависимостей и не перевычисляются
при их изменении. В дальнейшем, если firstName
снова станет пустой строкой, ячейка name
вновь подпишется
на lastName
.
1let foo = cellx(() => localStorage.foo || 'foo', { 2 put: ({ push }, value) => { 3 localStorage.foo = value; 4 5 push(value); 6 } 7}); 8 9let foobar = cellx(() => foo.value + 'bar'); 10 11console.log(foobar.value); // => 'foobar' 12console.log(localStorage.foo); // => undefined 13foo('FOO'); 14console.log(foobar.value); // => 'FOObar' 15console.log(localStorage.foo); // => 'FOO'
1let request = (() => { 2 let value = 1; 3 4 return { 5 get: url => new Promise((resolve, reject) => { 6 setTimeout(() => { 7 resolve({ 8 ok: true, 9 value 10 }); 11 }, 1000); 12 }), 13 14 put: (url, params) => new Promise((resolve, reject) => { 15 setTimeout(() => { 16 value = params.value; 17 18 resolve({ ok: true }); 19 }, 1000); 20 }) 21 }; 22})(); 23 24let foo = cellx(({ push, fail }, next = 0) => { 25 request.get('http://...').then((res) => { 26 if (res.ok) { 27 push(res.value); 28 } else { 29 fail(res.error); 30 } 31 }); 32 33 return next; 34}, { 35 put: (value, cell, next) => { 36 request.put('http://...', { value: value }).then((res) => { 37 if (res.ok) { 38 cell.push(value); 39 } else { 40 cell.fail(res.error); 41 } 42 }); 43 } 44}); 45 46foo.subscribe(() => { 47 console.log('New foo value: ' + foo.value); 48 49 foo.value = 5; 50}); 51 52console.log(foo.value); 53// => 0 54 55// => 'New foo value: 1' 56// => 'New foo value: 5'
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
0 existing vulnerabilities detected
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
Found 0/30 approved changesets -- score normalized to 0
Reason
no SAST tool detected
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
license file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Score
Last Scanned on 2025-02-03
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More