Почему неизменность настолько важна (или необходима) в JavaScript?

В настоящее время я работаю над платформами React JS и React Native. На полпути я натолкнулся на Immutable или библиотеку Immutable-JS, когда читал о реализации Facebook Flux и Redux.

Вопрос в том, почему неизменность так важна? Что не так в мутирующих объектах? Разве это не упрощает ситуацию?

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

Если я установлю, скажем, массив объектов со значением изначально, я не смогу им манипулировать. Это то, что говорит принцип неизменности, верно? (Поправьте меня, если я ошибаюсь.) Но что, если у меня есть новый объект новостей, который должен быть обновлен? В обычном случае я мог бы просто добавить объект в массив. Как мне добиться в этом случае? Удалить магазин и воссоздать его? Не является ли добавление объекта в массив менее дорогой операцией?

Ответ 1

Я недавно исследовал ту же тему. Я сделаю все возможное, чтобы ответить на ваши вопросы и постараюсь поделиться тем, что я узнал до сих пор.

Вопрос в том, почему неизменность так важна? Что не так в мутирующих объектах? Разве это не упрощает ситуацию?

В основном это сводится к тому, что неизменность повышает предсказуемость, производительность (косвенно) и позволяет отслеживать мутации.

Предсказуемость

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

Спектакль

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

Все обновления возвращают новые значения, но внутренние структуры используются совместно, чтобы значительно сократить использование памяти (и перебор GC). Это означает, что если вы добавляете вектор с 1000 элементами, он фактически не создает новый вектор длиной 1001 элемент. Скорее всего, внутри выделяется всего несколько небольших объектов.

Вы можете прочитать больше об этом здесь.

Отслеживание мутаций

Помимо уменьшения использования памяти, неизменяемость позволяет оптимизировать ваше приложение, используя reference- и равенство значений. Это позволяет легко увидеть, изменилось ли что-нибудь. Например, изменение состояния в компоненте реакции. Вы можете использовать shouldComponentUpdate чтобы проверить идентичность состояния, сравнивая объекты состояния и предотвращая ненужный рендеринг. Вы можете прочитать больше об этом здесь.

Дополнительные ресурсы:

Если я установлю, скажем, массив объектов со значением изначально. Я не могу манипулировать этим. Это то, что говорит принцип неизменности, верно? (Поправьте меня, если я ошибаюсь). Но что, если у меня есть новый объект новостей, который должен быть обновлен? В обычном случае я мог бы просто добавить объект в массив. Как мне добиться в этом случае? Удалить магазин и воссоздать его? Не является ли добавление объекта в массив менее дорогой операцией?

Да, это правильно. Если вы не понимаете, как реализовать это в своем приложении, я бы порекомендовал вам посмотреть, как это делает Redux, чтобы ознакомиться с основными концепциями, это мне очень помогло.

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

Существует отличный курс по редуксу на egghead.io, где Дэн Абрамов, автор редукса, объясняет эти принципы следующим образом (я немного изменил код, чтобы лучше соответствовать сценарию):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
  switch(action.type) {
    case 'ADD_NEWS_ITEM': {
      return [ ...state, action.newsItem ];
    }
    default: {
        return state;
    }
  }
};

// Store.
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const subscribe = (listener) => {
    listeners.push(listener);

    return () => {
      listeners = listeners.filter(cb => cb !== listener);
    };
  };

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach( cb => cb() );
  };

  dispatch({});

  return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
  onAddNewsItem() {
    const { newsTitle } = this.refs;

    store.dispatch({
      type: 'ADD_NEWS_ITEM',
      newsItem: { title: newsTitle.value }
    });
  },

  render() {
    const { news } = this.props;

    return (
      <div>
        <input ref="newsTitle" />
        <button onClick={ this.onAddNewsItem }>add</button>
        <ul>
          { news.map( ({ title }) => <li>{ title }</li>) }
        </ul>
      </div>
    );
  }
});

// Handler that will execute when the store dispatches.
const render = () => {
  ReactDOM.render(
    <News news={ store.getState() } />,
    document.getElementById('news')
  );
};

// Entry point.
store.subscribe(render);
render();

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

Ответ 2

Противоположный взгляд на неизменность

Короткий ответ: неизменность - это скорее модная тенденция, чем необходимость в JavaScript. Есть несколько узких случаев, когда это становится полезным (в основном React/Redux), хотя обычно по неправильным причинам.

Длинный ответ: читайте ниже.

Почему неизменность так важна (или необходима) в JavaScript?

Ну, я рад, что ты спросил!

Некоторое время назад очень талантливый парень по имени Дэн Абрамов написал библиотеку управления состоянием javascript под названием Redux, которая использует чистые функции и неизменность. Он также сделал несколько действительно крутых видео, которые сделали идею очень простой для понимания (и продажи).

Время было идеальным. Новинка Angular исчезала, и мир JavaScript был готов зацикливаться на последних новинках, обладающих должной степенью крутизны, и эта библиотека была не только инновационной, но и идеально подходящей для React, которую торгует другая электростанция в Силиконовой долине.

Как ни печально, мода правит миром JavaScript. Теперь Абрамова провозглашают полубогом, и все мы, простые смертные, вынуждены подчиняться Дао Неизменности... имеет ли это смысл или нет.

Что не так в мутирующих объектах?

Ничего такого!

На самом деле программисты мутировали объекты вечно... до тех пор, пока были объекты, которые нужно мутировать. 50+ годы разработки приложений, другими словами.

И зачем все усложнять? Когда у вас есть объект cat и он умирает, вам действительно нужен второй cat чтобы отслеживать изменения? Большинство людей просто скажут cat.isDead = true и cat.isDead = true с этим.

Разве (мутирующие объекты) не делают вещи простыми?

ДА! .. Конечно, это так!

Особенно в JavaScript, который на практике наиболее полезен для отображения некоторого состояния, которое поддерживается в другом месте (например, в базе данных).

Что если у меня есть новый объект новостей, который нужно обновить?... Как мне добиться в этом случае? Удалить магазин и воссоздать его? Не является ли добавление объекта в массив менее дорогой операцией?

Ну, вы можете пойти по традиционному подходу и обновить объект News, так что ваше представление этого объекта в памяти изменится (и представление, отображаемое пользователю, или так можно было бы надеяться)...

Или в качестве альтернативы...

Вы можете попробовать подход "FP/Immutability" и добавить свои изменения в объект News в массив, отслеживающий каждое историческое изменение, чтобы затем можно было перебрать массив и выяснить, каким должно быть правильное представление состояния (фу!).

Я пытаюсь узнать, что прямо здесь. Пожалуйста, просветите меня :)

Мода приходит и уходит, приятель. Есть много способов снять кожу с кошки.

Я сожалею, что вы должны нести путаницу постоянно меняющегося набора парадигм программирования. Но привет, добро пожаловать в клуб!

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

1) Неизменность исключительна, поскольку позволяет избежать условий гонки в многопоточных средах.

Многопоточные среды (такие как C++, Java и С#) виновны в практике блокировки объектов, когда более чем один поток хочет изменить их. Это плохо влияет на производительность, но лучше, чем альтернатива повреждения данных. И все же не так хорошо, как сделать все неизменным (хвала Господу Хаскеллу!).

НО УВЫ! В JavaScript вы всегда работаете с одним потоком. Даже веб-работники (каждый работает в отдельном контексте). Так как у вас не может быть состояния гонки, связанного с потоком, внутри контекста выполнения (все эти милые глобальные переменные и замыкания), главное в пользу неизменности выходит за рамки.

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

2) Неизменность может (каким-то образом) избежать состояния гонки в состоянии вашего приложения.

И вот в этом суть вопроса, большинство разработчиков (React) скажут вам, что неизменность и FP могут каким-то образом использовать эту магию, которая позволяет состоянию вашего приложения стать предсказуемым.

Конечно, это не означает, что вы можете избежать состязаний в базе данных, чтобы справиться с этим, нужно координировать действия всех пользователей во всех браузерах, и для этого вам понадобится внутренняя технология push, такая как WebSockets (подробнее об этом ниже), которая будет транслировать изменения всем, кто запускает приложение.

Это довольно запутанное утверждение просто означает, что последние значения, присвоенные объекту состояния (определяемые одним пользователем, работающим в его браузере), становятся предсказуемыми. Что на самом деле не прогресс вообще. Потому что вы могли бы использовать старую мутированную переменную для отслеживания своего состояния, и вы знали бы, что имеете дело с самой последней информацией при каждом обращении к ней, и это будет работать с любым, кроме React/Redux.

Зачем? Поскольку React является особенным... и состояние компонента управляется через цепочку событий, которые вы не можете контролировать, и вы полагаетесь на то, что не поменяете состояние напрямую. Это было замечательно обработано с точки зрения PR, поскольку реклама React превратила недостаток в сексуальную моду. Помимо моды, я бы предпочел увидеть неизменность того, что это такое, то есть инструмент для устранения пробела, когда выбранный вами фреймворк не обрабатывает состояние интуитивно.

3) Гоночные условия категорически плохие.

Ну, они могут быть, если вы используете React. Но они редки, если вы берете другие рамки.

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

Ты знаешь. Реальность. Но эй, кого это волнует?

4) Неизменяемость использует эталонные типы, чтобы уменьшить влияние на производительность отслеживания каждого изменения состояния.

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

5) Неизменность позволяет вам отменить вещи.

Потому что... это особенность номер один, которую попросит менеджер проекта, верно?

6) Неизменное состояние имеет много интересных возможностей в сочетании с WebSockets

И последнее, но не менее важное: накопление дельт состояний приводит к убедительным аргументам в сочетании с WebSockets, что позволяет легко использовать состояние в виде потока неизменяемых событий...

Как только копейка падает на эту концепцию (состояние - это поток событий, а не грубый набор записей, представляющих новейшую точку зрения), неизменный мир становится волшебным местом для обитания. Страна чудесных событий и возможностей, выходящих за пределы самого времени. И если все сделано правильно, это определенно может упростить выполнение приложений реального времени, вы просто транслируете поток событий всем заинтересованным, чтобы они могли создать свое собственное представление о настоящем и записать свои изменения в общий поток.

Но в какой-то момент вы просыпаетесь и понимаете, что все это чудо и магия приходят не бесплатно. В отличие от ваших нетерпеливых коллег, ваши заинтересованные стороны (да, люди, которые вам платят) мало заботятся о моде и много о деньгах, которые они платят за создание продукта, который они могут продать. И суть в том, что труднее написать неизменяемый код и легче его взломать, плюс мало смысла в том, чтобы иметь неизменный внешний интерфейс, если у вас нет внутреннего интерфейса для его поддержки. Когда (и если!) Вы, наконец, убеждаете своих заинтересованных лиц, что вы должны публиковать и использовать события с помощью технологии push, такой как WebSockets, вы обнаруживаете, насколько сложно масштабировать в производстве.


Теперь для некоторых советов, если вы решите принять его.

Выбор для написания JavaScript с использованием FP/Immutability - это также выбор, чтобы сделать кодовую базу вашего приложения больше, сложнее и сложнее в управлении. Я бы настоятельно посоветовал ограничить этот подход вашими редукторами Redux... если вы не знаете, что делаете. Другими словами: просто держи это просто ™. В большинстве случаев вам будет лучше. И где бы вы ни находились, я бы сосредоточился на получении преимуществ неизменяемых данных, проходящих через ваше (целое) приложение, а не на создании чисто функционального интерфейса и на этом все закончилось.

If you paid someone to draw you a horse, which one would you rather?

Теперь, если вам достаточно повезло, что вы можете делать выбор в своей работе, тогда попытайтесь использовать свою мудрость (или нет) и делайте то, что правильно, человеком, который платит вам. Вы можете основывать это на своем опыте, своей интуиции или на том, что происходит вокруг вас (по общему признанию, если все используют React/Redux, то есть веский аргумент, что будет легче найти ресурс для продолжения вашей работы). В качестве альтернативы, Вы можете попробовать подходы Resume Driven Development или Hype Driven Development. Они могут быть больше в твоем роде.

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


Теперь я добавил это как статью в своем блоге => Неизменность в JavaScript: противоположный взгляд. Не стесняйтесь отвечать там, если у вас есть сильные чувства, которые вы хотели бы снять с груди тоже;).

Ответ 3

Вопрос: почему важна неизменность? Что не так в мутирующих объектах? Разве это не делает вещи простыми?

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

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

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

Примечание 1: В зависимости от того, что вы делаете, существует потенциальная стоимость исполнения для неизменяемости, но такие вещи, как Immutable.js, оптимизируются, насколько это возможно.

Примечание 2: В маловероятном случае вы не были уверены, что Immutable.js и ES6 const означают очень разные вещи.

В обычном случае я мог бы просто добавить объект в массив. Как мне добиться в этом случае? Удалить хранилище и воссоздать его? Не добавляет ли объект в массив более дешевую операцию? PS: Если этот пример не является правильным способом объяснить неизменность, пожалуйста, дайте мне знать, какой правильный практический пример.

Да, ваш новостной пример отлично подходит, и ваши рассуждения верны: вы не можете просто изменить существующий список, поэтому вам нужно создать новый:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);

Ответ 4

Хотя другие ответы хороши, чтобы ответить на ваш вопрос о практическом случае использования (из комментариев к другим ответам), давайте на минутку выйдем за пределы вашего рабочего кода и посмотрим на вездесущий ответ прямо у вас под носом: git. Что произойдет, если каждый раз, когда вы нажимаете коммит, вы перезаписываете данные в хранилище?

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

Хотя я мало что знаю о внутренней работе git, я могу только предположить, что он использует стратегию, аналогичную стратегии библиотек, на которую вы ссылаетесь: структурное совместное использование. Под капотом библиотеки используют попытки или другие деревья, чтобы отслеживать только разные узлы.

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

Другой вариант использования: допустим, вы хотите отменить кнопку в своем веб-приложении. С неизменяемыми представлениями ваших данных, реализовать их относительно тривиально. Но если вы полагаетесь на мутацию, это означает, что вам нужно беспокоиться о кэшировании состояния мира и внесении атомарных обновлений.

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

Ответ 5

Почему неизменность так важна (или необходима) в JavaScript?

Неизменность можно отслеживать в разных контекстах, но самое важное - отслеживать ее в зависимости от состояния приложения и пользовательского интерфейса приложения.

Я буду рассматривать шаблон JavaScript Redux как очень модный и современный подход, и потому что вы упомянули об этом.

Для пользовательского интерфейса мы должны сделать его предсказуемым. Это будет предсказуемо, если UI = f(application state).

Приложения (в JavaScript) изменяют состояние с помощью действий, реализованных с использованием функции редуктора.

Функция редуктора просто принимает действие и старое состояние и возвращает новое состояние, сохраняя старое состояние без изменений.

new state  = r(current state, action)

enter image description here

Преимущество состоит в том, что вы перемещаетесь во времени по состояниям, поскольку все объекты состояний сохранены, и вы можете визуализировать приложение в любом состоянии, поскольку UI = f(state)

Таким образом, вы можете отменить/повторить легко.


Бывает, что создание всех этих состояний может все еще быть эффективным с точки зрения памяти, аналогия с Git великолепна, и у нас есть аналогия в ОС Linux с символическими ссылками (на основе inode).

Ответ 6

Вопрос в том, почему неизменность так важна? Что не так в мутирующих объектах? Разве это не упрощает ситуацию?

О изменчивости

Нет ничего плохого в изменчивости с технической точки зрения. Это быстро, это повторное использование памяти. Разработчики привыкли к этому с самого начала (насколько я помню). Проблема существует в использовании изменчивости и проблем, которые может принести это использование.

Если объект не используется совместно с чем-либо, например, существует в области действия функции и не подвергается воздействию извне, то трудно увидеть преимущества неизменности. На самом деле в этом случае нет смысла быть неизменным. Чувство неизменности начинается, когда что-то разделяется.

Головная боль изменчивости

Изменчивая общая структура может легко создать много подводных камней. Любое изменение в любой части кода с доступом к ссылке влияет на другие части с видимостью этой ссылки. Такое воздействие связывает все части вместе, даже когда они не должны знать о различных модулях. Мутация в одной функции может привести к краху совершенно другой части приложения. Это плохой побочный эффект.

Следующая часто проблема с мутацией - поврежденное состояние. Поврежденное состояние может произойти, когда процедура мутации не удалась в середине, и некоторые поля были изменены, а некоторые нет.

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

Наконец, мутация является причиной дефицита доверия. Как вы можете быть уверены, что какая-то структура имеет желаемое значение, если она может быть изменена.

const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }

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

Неизменность - это ценности

Неизменность означает, что изменение не выполняется для того же объекта, структуры, но изменение представляется в новом. И это потому, что ссылка представляет значение не только указатель памяти. Каждое изменение создает новое значение и не касается старого. Такие четкие правила возвращают доверие и предсказуемость кода. Функции безопасны в использовании, потому что вместо мутаций они имеют дело с собственными версиями с собственными значениями.

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

Неизменяемые структуры представляют ценности.

Я погружаюсь еще больше в тему в средней статье - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310

Ответ 7

Еще одно преимущество неизменяемости в Javascript заключается в том, что оно уменьшает временную связь, что имеет существенные преимущества для дизайна в целом. Рассмотрим интерфейс объекта с двумя методами:

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

Может случиться так, что вызов baz() необходим для приведения объекта в правильное состояние, чтобы вызов bar() работал правильно. Но откуда ты это знаешь?

f.baz();
f.bar(); // this is ok

f.bar();
f.baz(); // this blows up

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

Если Foo неизменен, то это больше не проблема. Можно предположить, что мы можем вызывать baz или bar в любом порядке, потому что внутреннее состояние класса не может измениться.

Ответ 8

Когда-то была проблема с синхронизацией данных между потоками. Эта проблема была большой болью, были решения 10+. Некоторые люди пытались решить это радикально. Это было место, где родилось функциональное программирование. Это как марксизм. Я не мог понять, как Дан Абрамов продал эту идею в JS, потому что она однопоточная. Он гений.

Я могу привести небольшой пример. В gcc есть атрибут __attribute__((pure)). Компиляторы пытаются решить, является ли ваша функция чистой или нет, если вы не объявите ее специально. Ваша функция может быть чистой, даже если ваше состояние изменчиво. Неизменность - это только один из способов 100+, чтобы гарантировать, что ваша работа будет чистой. На самом деле 95% ваших функций будут чистыми.

Вы не должны использовать какие-либо ограничения (например, неизменяемость), если у вас нет серьезных причин. Если вы хотите отменить какое-то состояние, вы можете создавать транзакции. Если вы хотите упростить общение, вы можете отправлять события с неизменными данными. Это тебе решать.

Я пишу это сообщение из республики пост марксизма. Я уверен, что радикализация любой идеи - неправильный путь.

Ответ 9

Другой дубль...

Мой другой ответ касается вопроса с очень практической точки зрения, и он мне все еще нравится. Я решил добавить это как другой ответ, а не как дополнение к этому, потому что это скучная философская напыщенная речь, которая, надеюсь, также отвечает на вопрос, но не совсем соответствует моему существующему ответу.

TL; DR

Даже в небольших проектах неизменность может быть полезной, но не думайте, что, поскольку она существует, она предназначена для вас.

Гораздо дольше отвечу

ПРИМЕЧАНИЕ: для целей этого ответа я использую слово "дисциплина", чтобы означать самоотречение для некоторой выгоды.

По форме это похоже на другой вопрос: "Должен ли я использовать Typescript? Почему типы так важны в JavaScript?". У него тоже есть аналогичный ответ. Рассмотрим следующий сценарий:

Вы являетесь единственным автором и сопровождающим базы кода JavaScript/CSS/HTML, насчитывающей около 5000 строк. Ваш полу-технический начальник читает что-то о Typescript-as-new-hotness и предлагает, чтобы мы могли перейти к нему, но оставляем решение за вами. Итак, вы читаете об этом, играете с ним и т.д.

Итак, теперь у вас есть выбор, вы переходите на Typescript?

Typescript имеет ряд неоспоримых преимуществ: интеллектуальный смысл, раннее обнаружение ошибок, предварительное указание API-интерфейсов, простота исправления ошибок при их рефакторинге, меньшее количество тестов. Typescript также имеет некоторые издержки: некоторые очень естественные и правильные идиомы JavaScript могут быть сложными для моделирования в этой не особенно мощной системе типов, аннотации увеличивают LoC, время и усилия переписывают существующую кодовую базу, дополнительный шаг в конвейере сборки и т.д. Более фундаментально, он выделяет подмножество возможных правильных программ на JavaScript в обмен на обещание, что ваш код, скорее всего, будет правильным. Это произвольно ограничительно. В этом весь смысл: вы навязываете некоторую дисциплину, которая ограничивает вас (надеюсь, от выстрела в себя).

Вернемся к вопросу, перефразированному в контексте вышеприведенного абзаца: стоит ли это того?

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

Сценарий Б:

Вы меняете работу и теперь являетесь бизнес-программистом в Foo Corp. Вы работаете с командой из 10 человек на 90000 LoC (и считаете) кодовую базу JavaScript/HTML/CSS с довольно сложным конвейером сборки, включающим babel, webpack набор полифилов, реагирующих с различными плагинами, система управления состоянием, ~ 20 сторонних библиотек, ~ 10 внутренних библиотек, плагины редактора, такие как линтер с правилами для внутреннего руководства по стилю и т.д. и т.д. и т.д.

Назад, когда вы были 5k LoC парень/девушка, это просто не имело значения. Даже документирование не было таким уж большим делом, даже если вернуться к определенной части кода через 6 месяцев, вы могли бы понять это достаточно легко. Но теперь дисциплина не только хороша, но и необходима. Эта дисциплина может не включать Typescript, но, вероятно, будет включать некоторую форму статического анализа, а также все другие формы дисциплины кодирования (документация, руководство по стилю, сценарии сборки, регрессионное тестирование, CI). Дисциплина уже не роскошь, а необходимость.

Все это применимо к GOTO в 1978 году: ваша изящная маленькая игра в блэкджек на C могла использовать логику GOTO и спагетти, и было не так уж и сложно выбрать свой собственный приключенческий путь, но по мере того, как программы становились больше и более амбициозное, недисциплинированное использование GOTO не могло быть устойчивым. И все это относится к неизменности сегодня.

Точно так же, как статические типы, если вы не работаете с большой кодовой базой с командой инженеров, поддерживающей/расширяющей ее, выбор использования неизменяемости более эстетичен, чем практичен: его преимущества все еще существуют, но могут еще не перевесить затраты.

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

Ответ 10

Я создал независимую от фреймворка библиотеку с открытым исходным кодом (MIT) для изменяемого (или неизменяемого) состояния, которая может заменить все эти неизменяемые хранилища, такие как libs (redux, vuex и т.д.).

Неизменяемые состояния были уродливы для меня, потому что было слишком много работы (много действий для простых операций чтения/записи), код был менее читаемым, а производительность для больших наборов данных была неприемлемой (перерисовка всего компонента:/).

С помощью Deep-State-обозревателя я могу обновить только один узел с точечной нотацией и использовать подстановочные знаки. Я также могу создавать историю состояния (отменить/повторить/путешествие во времени), сохраняя только те конкретные значения, которые были изменены {path:value} = меньше использования памяти.

С Deep-State-обозревателем я могу точно настроить вещи, и у меня есть контроль зерна за поведением компонентов, так что производительность может быть значительно улучшена. Код более читабелен, а рефакторинг намного проще - просто ищите и заменяйте строки пути (не нужно менять код/​​логику).

Ответ 11

Я думаю, что главная причина pro неизменяемых объектов - сохранение состояния объекта.

Предположим, что у нас есть объект с именем arr. Этот объект действителен, когда все элементы являются одной и той же буквой.

// this function will change the letter in all the array
function fillWithZ(arr) {
    for (var i = 0; i < arr.length; ++i) {
        if (i === 4) // rare condition
            return arr; // some error here

        arr[i] = "Z";
    }

    return arr;
}

console.log(fillWithZ(["A","A","A"])) // ok, valid state
console.log(fillWithZ(["A","A","A","A","A","A"])) // bad, invalid state

если arr станет неизменным объектом, тогда мы будем уверены, что arr всегда находится в допустимом состоянии.