Неизменяемость в Javascript, React и Immutable.js

В этом посте обобщается мое погружение в Неизменяемость в программировании - что это такое, почему это полезно и как работает в Javascript, React и Immutable.js! Я получил массу удовольствия от рефакторинга кода реакции / сокращения на использование Immutable.js. Я ни в коем случае не эксперт в Immutable.js, но я думаю, что Immutable.js docs чрезвычайно полезны и просты в использовании :) Вы можете проверить мой записанный технический доклад на YouTube ЗДЕСЬ!

Неизменяемость кажется довольно безобидной, основной темой, не так ли? Когда мы рассмотрели основы неизменяемости в течение первой недели программы Грейс Хоппер, я почувствовал, что понял ее суть. Однако после использования React / Redux для управления текущим состоянием приложения я понял, что мне нужно более глубокое понимание того, как оно на самом деле работает под поверхностью.

Что такое неизменность?

Основная идея довольно проста. Сначала вы начинаете со структуры данных, например массива или строки. Эта структура данных находится где-то в памяти, что похоже на цифровой адрес. Неизменяемость означает, что как только вы вызываете эту структуру данных в бытие, вы не можете вносить изменения (также известные как мутации) в структуру данных и данные, которые она содержит в этой конкретной области памяти. Существует два возможных типа мутаций, которые могут произойти в структуре данных: видимые и невидимые побочные эффекты.

Видимая мутация

  • Заметен сторонними наблюдателями через API
  • Легко обнаружить / отладить
  • ex) Манипуляции с массивами
  • У вас был массив из четырех элементов, затем выскочил элемент, и теперь, когда вы ссылаетесь на тот же массив, у вас есть три элемента
  • Заметно для пользователя, что длина и содержимое массива были изменены.

Невидимая мутация

  • Без звука для сторонних наблюдателей, работает как побочный эффект
  • Сложнее обнаружить / отладить
  • пример) Хеширование / мемоизация
  • Создание хеша внутри вашей функции с использованием мемоизации для улучшения временной сложности вашей функции
  • Со стороны сложно определить, что хеш создается и обновляется по мере выполнения вашей функции.

В любом случае неизменность не допускает возникновения ни одного типа мутации.

Неизменяемость в Javascript

Вот несколько примеров, которые покажут вам, как вы уже поняли неизменяемость в Javascript. Вот несколько примеров, которые я написал сам, чтобы поэкспериментировать с идеей неизменности - не стесняйтесь исследовать эти примеры на Repl.it!

Неизменяемые DS (строки / числа) в Javascript

Как вы можете видеть выше, когда вы впервые объявляете строку, любые методы изменения (такие как 'slice'), которые применяются к этой структуре данных, возвращают копию исходной структуры данных с изменениями, внесенными в Это. Когда вы обращаетесь к исходной структуре данных (например, «объявление»), она остается неизменной.

Числа также являются неизменяемыми структурами (как и все другие примитивные типы в Javascript). Когда y установлено равным x, он создает копию значения «3» в памяти вместо создания ссылки на исходное значение x. Вот почему, когда теперь x устанавливается равным другому значению, это не влияет на значение y.

Изменяемые DS (массивы / объекты) в Javascript

В Javascript есть как неизменяемые, так и изменяемые структуры данных, которые делают этот язык более гибким и разочаровывающим. Например, массивы можно изменять с помощью методов-мутаторов, таких как .push (), .pop (), .splice (), .sort () и т. Д. Это означает, что вы заменяете предыдущую версию массива с помощью мутированная / измененная версия массива. Существуют также немутантные методы доступа, которые обрабатывают изменяемые объекты, как если бы они были неизменяемыми объектами (например, .map ())

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

Если вы используете неизменяемость, вы всегда создаете копию старой структуры данных и применяете изменения к копии вместо изменения исходной структуры данных!

Итак, почему неизменность?

Думаю, я уже вкратце обсуждал, почему функциональное программирование использует неизменяемость, но для ясности:

Самый большой выигрыш от использования неизменяемости - это более простое программирование!

Легче отлаживать

  • Каждый раз, когда вы вносите изменения, вы фактически оставляете следы старых структур данных, которые вы можете пересмотреть. Поскольку неизменяемость подчеркивает необходимость удаления всех побочных эффектов и использования чистых функций, это позволяет программистам сосредоточиться на одной функции, которая могла создать ошибку, вместо того, чтобы следовать этой цепочке ссылок, чтобы увидеть, какая функция, возможно, изменила наши исходные данные. состав…

Настойчивость

  • У вас есть доступ к более старым версиям вашей структуры данных, пока вы не воспользуетесь методом сборки мусора, чтобы избавиться от нее!

Более простое глубокое сравнение ценностей

  • Поскольку вы всегда создаете новые копии старой структуры данных в памяти, вы можете просто сравнить ячейки памяти структур данных, чтобы увидеть, совпадают ли их значения или нет (вместо того, чтобы углубляться в структуру данных, закрепляя вниз значения в обеих структурах данных, затем выполнение сравнения - более прямой пример ниже в реакции / редукции)

Параллелизм

  • В настоящее время Javascript является однопоточным, поэтому у него нет проблем с многопоточностью, как в других языках… пока. Однако он использует параллелизм (и асинхронность!) - неизменяемость упрощает управление параллелизмом (без блокировок или моментальных снимков). Больше информации об этом потоке stackoverflow, если вы хотите в него разобраться!

Как структурное совместное использование делает его лучше

Таким образом, вы могли заметить, что все это «копирование» создает некоторые препятствия для неизменности. Во-первых, изготовление копий требует ВРЕМЕНИ. Во-вторых, изготовление копий и их хранение требует ПРОСТРАНСТВА. Наконец, создание точной репрезентативной копии исходной структуры данных может быть трудным, особенно для больших и вложенных структур данных.

Итак, мы представляем структурное совместное использование! В Ресурсах есть много более подробных статей по этому поводу, на которые я буду ссылаться в конце этой статьи, но ниже я смог почерпнуть то, что я смог почерпнуть (снимок со слайдов).

из Immutable.js, постоянные структуры данных и структурное совместное использование

Итак, сначала мы начнем с таблицы ключей и значений, которую мы можем аккуратно организовать в структуру trie. Если вы не знаете, что такое trie, загляните в Википедию! В значительной степени ключи используются для организации структуры в алфавитном порядке, а значения хранятся в каждом узле. Эта конкретная структура называется направленным ациклическим графом (DAG). В этом примере узел, на котором мы хотим сосредоточиться, - это чай со значением 3.

из Immutable.js, постоянные структуры данных и структурное совместное использование

Допустим, мы хотим обновить значение «tea» с 3 до 14. Зеленые узлы выше - это узлы, которые необходимо будет напрямую обновить или «скопировать», чтобы отразить это изменение. Однако оставшиеся узлы (которые остаются белыми) не нуждаются в обновлении. Итак, что мы можем сделать, чтобы сэкономить больше места и времени, потраченного на попытки обновить нашу структуру данных?

из Immutable.js, постоянные структуры данных и структурное совместное использование

Все, что нам нужно сделать, это указать эти новые узлы на старые узлы, тем самым по-прежнему сохраняя старую структуру данных (при необходимости до сборки мусора) и позволяя нам повторно использовать старые узлы, которые не были обновлены. Мы можем минимизировать время, затрачиваемое на создание новых копий, а также сэкономить много места, которое мы могли бы потратить на копирование всей структуры данных! Ура структурное разделение! Immutable.js использует структурное совместное использование, но гораздо более сложными способами (как вы можете себе представить), используя попытки индекса и попытки хеширования. Если вам интересно, это гораздо более сложные темы, на которые будут ссылаться другие статьи ниже!

Неизменяемость в React / Redux

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

Вот пример того, как мы делаем это в React / Redux. Это образец моего кода из моего старшего проекта обогащения. Этот проект включал создание веб-сайта Межпланетной академии с разными кампусами (названия основаны на планетах) и студентами, посещающими разные кампусы.

Сначала мы начнем с начального состояния кампусов, которое я позже назову «campusData» в редукторе. Состояние, вероятно, можно было бы выровнять, но я хотел привести пример вложенного объекта в качестве состояния. Как видите, начальное состояние включает в себя массив кампусов и объект, который является «выбранным кампусом».

Чтобы получить GET_CAMPUSES или ADD_CAMPUS, мы создаем новое состояние и вносим изменения в массив кампусов предыдущего состояния с помощью Object.assign. По окончании изменений, внесенных нашими действиями, мы возвращаем newState, сохраняя старое состояние.

Проблема возникает, когда у меня очень много вложенных состояний. В этом примере я защищенно копирую все кампусы в случае «ADD_CAMPUS», используя […, state.campuses]. На одном уровне защитное копирование не так уж и страшно, но если у вас было несколько уровней вложенности объектов / массивов, это может стать немного смешным.

Итак, вот он ... Immutable.js!

Неизменяемость с Immutable.js

Immutable.js - это библиотека, созданная Facebook для работы с React, и в ней используется структурное совместное использование, которое мы исследовали ранее. Вы можете установить его, как любой другой модуль npm (npm install immutable), и потребовать его в вашем файле javascript по мере необходимости (const Immutable = require (‘immutable’)).

В Immutable.js есть много методов, которые вы можете проверить в документах Immutable.js, однако я использовал здесь несколько, которые, как мне казалось, могут быть использованы часто. Начиная сверху, у нас все еще такое же исходное состояние кампусов. Однако мы преобразуем любой объект javascript в неизменяемую структуру с помощью метода Immutable.fromJS (). Это также преобразует любой вложенный объект / массив в неизменяемую карту / список. Карта - это неизменная версия объекта, а List - неизменная версия массива.

Когда изменения внесены в предыдущее состояние, нам больше не нужно использовать Object.assign (). Однако вы должны убедиться, что любые изменения, которые вы вносите в исходное состояние, также имеют неизменяемую форму. Например, если вы хотите добавить университетский городок (ADD_CAMPUS), вы можете указать .update () со свойством, которое вы хотите обновить ('кампусы'), которое позволяет вам получить доступ к списку кампусов, а затем поместите новый кампус в список только после того, как вы преобразуете новый объект кампуса в карту, используя для этого '.fromJS ()' (вы также можете использовать Immutable.Map ({key: value}))

Теперь, когда вы передаете кампусы штата в качестве опоры компоненту Campuses, вы хотите получить доступ к состоянию с помощью специальных методов Immutable.js, таких как «state.get () или state.getIn ()». state.get () нацелен только на одно свойство за раз, в то время как state.getIn () принимает массив вложенных свойств.

Когда кампусы фактически используются при рендеринге компонента, вы можете видеть, что и здесь вы используете метод «campus.get ()» вместо простого доступа к значению путем расстановки точек на объекте кампуса. Я считаю, что это позволяет вашим структурам данных быть более защищенными от случайных мутаций и ограничивать все возможные изменения вашего состояния в области ваших редукторов.

Вот конечный результат после рефакторинга Immutable.js. Наслаждаться!

Пожалуйста, свяжитесь со мной с любыми комментариями :) Я все еще учусь, поэтому мне очень хотелось бы обсудить эту тему!

Полезные статьи

Полезные видео