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

Фон

Чтобы понять этот антипаттерн, нужно понять, как ссылки на объекты работают в JavaScript. Рассмотрим этот фрагмент кода:

const a = { apple: "red" };
const b = { apple: "red" };
a === b // evaluates false

Почему это неверно?

Когда объект создается в JS и сохраняется в переменной, переменная фактически хранит «ссылку» на объект, а не значение самого объекта. Итак, в приведенном выше примере создаются два разных объекта, а затем ссылки на эти объекты сохраняются в двух отдельных переменных.

Вы также можете думать об этом так: когда объект создается, переменные сохраняют адреса этих объектов.

const a = { apple: "red" }; // a = Address-#123
const b = { apple: "red" }; // b = Address-#456
a === b // this is essentially: Address-#123 === Address-#456

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

const a = { apple: "red" }; // a = Address-#123
const b = a; // b = Address-#123
a === b; // Evaluates to true, since Address-#123 === Address-#123

Антипаттерн

Разобравшись с этим, мы можем понять причины, по которым immutable.js'stoJS - это плохо. Это основано на следующем основном утверждении: toJS всегда генерирует новые ссылки на объекты.

Это означает, что если у меня есть эта карта:

const myMap = Map({ apple: "red" });

Тогда это всегда будет оцениваться как false:

myMap.toJS() === myMap.toJS()

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

Например, в React, если вы используете PureComponent / memo, React не будет повторно выполнять рендеринг, если свойства не изменились. Но чтобы определить, изменились ли свойства, React выполняет неглубокую проверку равенства, что является просто причудливым способом сказать, что React использует ===, как в приведенных выше примерах. Это означает, что ваш компонент всегда будет повторно отображать, если вы передадите данные, полученные с помощью toJS.

// `BlogPosts` below will always re-render, even if `posts`
// has not changed. This is because `toJS` is always returning a
// brand new object reference, which means `BlogPosts` always thinks
// the data has changed.
<BlogPosts
  posts={posts.toJS()} 
/>

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

// In this example, the output of selectBlogState will always be
// different
const selectBlogState = state => state.get("blogs").toJS();
// Because the result of the input selector here is always
// different, then `selectBlogPosts` always re-calculates.
const selectBlogPosts = createSelector(
  selectBlogState,
  (blogState) => blogState.posts.map((post) => {
    // some complex perf intensive task here
  });
);

Последние мысли

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

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