Last active
September 24, 2016 18:40
-
-
Save typeetfunc/e1783cdc371751e3eacc24f7cee1ec82 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Есть такое понятие функциональная структура данных и связаное с ним понятие "ссылочная прозрачность". | |
Что же это такое? | |
определение: "Выражение 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