Компромиссное сравнение Immutable.js, Seamless-immutable и Timm

TL;DR Популярность Immutable.js и Seamless-immutable зашкаливает, кому нужна еще одна библиотека неизменности? Возможно, если вы не хотите блокировать доступ для чтения к своим данным через неродной API, вам нужна разумная производительность записи и простой в использовании API. Это именно тот баланс, который Тимм пытается найти.

Мы прочитали посты, посмотрели видео, поняли концепции. Неизменяемость может быть очень полезна во многих сценариях; Например:

  • Мы выполняем длительные вычисления с данными и хотим запомнить результаты, повторно используя их до тех пор, пока не изменятся входные данные.
  • Мы хотим элегантно реализовать функциональность Undo/Redo.
  • Мы хотим распутать состояние нашего приложения и использовать что-то вроде Redux с путешествиями во времени, сериализацией, регидратацией и так далее.
  • Мы застряли с производительностью нашего приложения React и слышали о чудесах shouldComponentUpdate.

Во всех этих случаях, вероятно, лучше всего использовать неизменяемые данные. Он может сделать код более предсказуемым, позволяет проводить тривиальные сравнения объектов и чувствует себя как дома в функциональном программировании. О чем еще мы могли просить?

Как оказалось, неизменяемые операции не лишены недостатков. Они никогда не будут такими быстрыми, как мутации на месте, как бы вы ни старались. Вам нужно будет поразмыслить над некоторыми алгоритмами, особенно рекурсивными. И в некоторых случаях это может стать проблемой, в зависимости от инструментов/библиотек, с которыми вы работаете.

Но что, если бы мы нашли способ облегчить когнитивную нагрузку на разработчика, сохранить производительность на разумном уровне и позволить нам воспользоваться преимуществами неизменности? Войдите в Timm, крошечную библиотеку, которая пытается решить некоторые из этих компромиссов.

Компромисс № 1: бесшовность или защита

Immutable.js — одно из громких имен на арене неизменяемости, надежная библиотека, которая предлагает вам все, что вам когда-либо понадобится, и даже больше. Производительность также превосходна, учитывая многие вещи, которые он делает, а его объекты (карты, списки и многие другие) непрозрачны, поэтому они защищены от ваших случайных мутаций.

Тем не менее, после использования его в нескольких проектах, я всегда чувствовал трудности при смешивании карт и списков Immutable.js с нативными объектами и массивами JS. Было бы невозможно сосчитать, сколько раз у меня возникали странные ошибки во время выполнения, потому что я забыл использовать геттер Map (image.get('url')) вместо старой простой записи через точку ( image.url) или предупреждение, потому что я взял знакомое свойство массива length вместо size списка.

Итак: бесшовный или защищенный? Тимм выбирает бесшовный. Было бы возможно иметь нативные и защищенные объекты с помощью Object.freeze(), но, похоже, есть некоторые узкие места в производительности (или, альтернативно, различное поведение в разработке и производстве). режим, который кажется таким же плохим). В заключение: вы можете случайно видоизменять свои объекты (осторожно!), но:

  • Вы сможете получить доступ к своим данным простым способом; и
  • Вы не будете привязаны к конкретной библиотеке для чего-то столь же повсеместного, как управление данными.

Компромисс № 2: простота/размер против производительности записи

Immutable.js стремится быть всеобъемлющим и производительным. Оптимизация скорости чтения/записи в неизменяемых операциях при сохранении привлекательного API не является тривиальной задачей, и Immutable.js использует передовые методы (постоянные структуры данных и структурное совместное использование) для достижения именно этого.

Не предоставляя всех наворотов, Timm в 10 раз меньше (~ 1 КБ в минимизированном и сжатом виде) и использует гораздо больше базовых инструментов, хотя и фокусируется на производительности. Чтения такие же быстрые, как и нативные (для этого даже не нужна библиотека), и записи тоже достаточно быстрые (бенчмарки):

  • Для небольших массивов/объектов: такая же производительность, как у Immutable.js для неглубоких операций записи, становится все быстрее, чем Immutable.js для более глубоких операций записи.
  • Для больших массивов или объектов (см. Массив: запись на рисунке ниже, где массивы имеют 1000 элементов): все медленнее, чем Immutable.js, по мере того, как массивы становятся длиннее или объекты имеют больше характеристики. Вот где Immutable.js действительно сияет!
  • Быстрее (даже чрезвычайно быстрее), чем Seamless-immutable, который также использует нативные объекты.

…а как насчет оператора спреда JS?

Наконец, почему бы не использовать новый блестящий оператор расширения массива ES6 и его грядущего близнеца для объектов? В конце концов, нативный синтаксис невозможно победить! Почему бы не сделать следующее?

const obj = { foo: 'foo', bar: 'bar' }; 
// Why not do this... 
const obj2 = { ...obj, bar: 'boo' }; 
// ...instead of this? 
const obj2 = timm.merge(obj, { bar: 'boo' });

Оператор распространения объекта, безусловно, выглядит лучше. Однако будьте осторожны:

const obj = { foo: 'foo', bar: 'bar' };
// Future spread operator without changes
const obj2 = { ...obj, bar: 'bar' };
console.log(obj2 === obj);
// -> false
// Timm without changes
const obj2 = timm.merge(obj, { bar: 'bar' });
console.log(obj2 === obj);
// -> true!

Оператор распространения всегда создает новый объект, независимо от того, изменяем ли мы фактически исходный объект. Напротив, Timm и Immutable.js создают новый экземпляр (лениво), только когда обнаруживают, что операция фактически мутирует объект; таким образом вы создадите меньше объектов, снизив требования к памяти и события сборки мусора. И более того: вы получите меньше ложных срабатываний при проверке, изменился ли объект.

Вывод

После всего этого, что является наиболее подходящим вариантом?

  • Не забывайте об очевидном: вам действительно нужна неизменность? Вероятно, да, но используйте свое лучшее суждение. Если производительность имеет решающее значение, и вы можете пожертвовать элегантностью кода и другими приятными преимуществами неизменности, что ж… просто мутируйте спокойно! Я имею в виду на месте.
  • Если вам нужна полная, проверенная в боевых условиях библиотека и вы не возражаете против блокировки, связанной с неродным API, даже для чтения: используйте Immutable.js.
  • Если вы предпочитаете простые массивы/объекты, используйте Timm.
  • Если ваши типичные варианты использования предполагают больше чтения, чем письма, используйте также Timm.
  • Если вы выполняете много операций записи в очень длинные массивы или толстые объекты, используйте Immutable.js.

Первоначально опубликовано на сайте Math.random() 16 июня 2016 г.