Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save typeetfunc/e1783cdc371751e3eacc24f7cee1ec82 to your computer and use it in GitHub Desktop.
Save typeetfunc/e1783cdc371751e3eacc24f7cee1ec82 to your computer and use it in GitHub Desktop.
Есть такое понятие функциональная структура данных и связаное с ним понятие "ссылочная прозрачность".
Что же это такое?
определение: "Выражение e является ссылочно-прозрачным,
если возможно заменить любое его подвыражение эквивалентным значением без изменения значения e."
Хм... чет как то не стало яснее - посмотрим на примере операций
давайте посоздаем структуры данных в иммутабельном стиле на жсе
```js
var a = {} // создали новый обьект
var b = {...a, key: 2} // создали другой обьект основываясь на первом
var c = {} // создадим еще пустой обьект
var d = {key: 2} а теперь еще один уже с ключом
```
Давайте договоримся что мы никогда не будем изменять единожды созданные обьекты.
Тогда можно заметить что мы можем во всем нашем коде заменить к примеру `a` на `{}`.
И что самое важное наоброт - заменить _везде_ `{}` на `a`,
то есть можно написать не `var c = {}` а `var c = a` (и тоже самое с парой b и d)
И в нашем коде _нигде_ ничего не сломается.
ну вроде с самим понятием разобрались - но вопрос нафига нам это надо?
А давайте представим что у нас есть компонент который подписан на какие то данные(допустим они прилетают из сети - допустим поллингом по интервалу).
Эти данные перед попаданием в компонент надо как то преобразовать - обычно никогда сырые данные не рендерятся.
предположим нам надо добавить в каждый обьект списка ключ основываясь на остальных ключах
Допустим что мы очень умные и используем для преобразования только имутабельные операции - map+assoc и у нас везде прикручен pureRender
то есть получается примерно такая цепочка рендеров
```js
[{key1: 1, key2: 3},{key1: 4, key2: 5}] // первый емит данных в компонент
// преобразовываем map + assoc(key3, key1 + key2)
[{key1: 1, key2: 3, key3: 4},{key1: 4, key2: 5, key3: 9}]
// рендерим!
// проходит интервал поллинга запрашиваются новые данные и _внезапно_ они оказываются такими же!
[{key1: 1, key2: 3},{key1: 4, key2: 5}]
// преобразовываем map + assoc(key3, key1 + key2)
[{key1: 1, key2: 3, key3: 4},{key1: 4, key2: 5, key3: 9}]
// И бах снова ререндерим хотя на самом деле ничего не поменялось!
```
Хм что же пошло не так
давайте посмотрим как работает иммутабельные преобразование в жсе
```js
var a = {} // создали новый обьект
var b = {...a, key: 2} // создали другой обьект основываясь на первом. Здесь будет новая ссылка - все логично обьект ведь изменился
var c = {...b, key: 2} // АЛЯРМЕ! снова создалась новая ссылка - но обьект точно тот же что и был раньше
```
подобные манипуляции будут вызывать ререндер даже не смотря на то что вы все делаете иммутабельно и у вас везде есть pureRender
как же решить эту проблему? давайте сначала решим на частную проблему с преобразованием данных
map+assoc создают новый массив и наполняют его каждый раз новыми обьектами - а давайте просто добавим мемоизацию!
Надо просто завернуть нашу функцию в memoize из лодаша и есть аргумент функции
тот что был прежде - то она не будет создавать новую структуру а возмет старый результат
получается примерно такая цепочка рендеров
```js
[{key1: 1, key2: 3},{key1: 4, key2: 5}] // первый емит данных в компонент
// преобразовываем map + assoc(key3, key1 + key2)
[{key1: 1, key2: 3, key3: 4},{key1: 4, key2: 5, key3: 9}]
// рендерим!
// проходит интервал поллинга запрашиваются новые данные и _внезапно_ они оказываются такими же!
[{key1: 1, key2: 3},{key1: 4, key2: 5}]
// хм наша мемоизированная функция говорит что данные не изменились - просто вернем старый результат который получили на 3 строчке
[{key1: 1, key2: 3, key3: 4},{key1: 4, key2: 5, key3: 9}]
// ничего не рендерим - ссылка не меняется - pureRender работает
```
а как решить проблему в общем случае?
а давайте просто придумаем свои стрктуры данных - например иммутабельные хеши и все операции для них будем мемоизировать - потому что мы можем!
```js
var create = memoize((...args) => {
var empty = {}
zip(args, 2) // группируем по два
.forEach(item => {
empty[item[0]] = item[1]
})
return empty;
}),
assoc = memoize((key, value, obj) => create(key, value, ...flatten(toPairs(obj))),
dissoc = memoize((dropKey, obj) => create(flatten(toPairs(obj).filter(([key]) => key !== dropKey)));
```
Давайте заиспользуем
```js
var a = create('key', 2);
var b = create('key', 2);
a === b // Ура ссылки совпадают
// а теперь посложнее
var c = assoc('key2', 3, a),
d = assoc('key2', 3, b);
c === d // и так сопадают
```
Что это нам дает в фундаментальном смысле? теперь мы можем _везде_ использовать вместо `deepEqual` простое и быстрое как само сатана `===`. Мы всегда можем сказать что если ссылки отличаются то отличаются и сами обьекты. 100%! pureRender теперь будет разрешать рендер _только_ если данные _действительно_ поменялись
Это самое главное что дает нам immutable.js
как небольшая плюшка у него в комплекте идет "структурное переиспользование" - то есть вместо полноценного копирование одного обьекта в внутрь другого он строит дерево из переиспользоваемых обьектов - в результате экономя нам память
```js
var a = Immutable.Map(..... здесь огроменный мап)
var b = a.set('key', 2) // здесь всего лишь внутри дерево типа
b = {
initial: a,
added:{
key: 2
}
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment