Недавно британские ученые открыли, что на свете бывают непослушные разработчики, которые все делают наоборот. Им дают полезный совет: «Не подключай целую библиотеку ради одной функции», — они берут и подключают. Им говорят: «Будь внимателен на код-ревью», — они тут же начинают апрувить все подряд. Ученые придумали, что таким разработчикам нужно давать не полезные, а вредные советы. Они все сделают наоборот, и получится как раз правильно.

ВРЕДНЫЙ СОВЕТ №1

Компоненты в 500 строк — 

Это так, для всех игрушки.

Делай больше! Делай тыщу!

Чтоб вообще слететь с катушек.

 На практике:

Попробуйте спросить себя: «Этот участок кода делает/описывает ОДНУ вещь?» Независимо от того, находится ли это в области метода или класса, если он делает несколько вещей, вам, вероятно, следует отделить их друг от друга. А затем уже создать другой метод, который вызывает их последовательно.

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

ВРЕДНЫЙ СОВЕТ №2

Чтоб жизнь медом не казалась,

Логику программы всей

В компонент один засунь ты.

Время ведь совсем неважно,

Не поспишь ты месяцок,

Лишний рендер — так прекрасно.

Подождем еще, дружок.

На практике:

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

Разберем это на примере и посмотрим, какие советы есть для повышения производительности:

 const App = () => {
  const [counter, setCounter] = useState(0);
   
     return (
       <div>
         <button onClick={() => setCounter(counter + 1)}>Counter</button>
         <FirstComponent />
         <SecondComponent />
       </div>
     );
   };

Компонент App с состоянием, состоящий из 3 компонентов, в состав которых входит кнопка, обновляющая значение counter. К чему это приводит? К ререндеру App, FirstComponent и SecondComponent.

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

const App = () => {
       return (
         <div>
           <CounterBtn>
          …
         </div>
       );
};

const CounterBtn = () => {
    const [counter, setCounter] = useState(0);
    return (
        <button onClick={() => setCounter(counter + 1)}>Counter</button>
    );
}

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

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

Redux

Возьмем подключение компонента к Redux:

@withRouter
@connect(
  (state) => (
    {
      loading: getLoading(state),
      blocks: getBlocks(state),
      sections: getSections(state),
    }
  ),
  {
    loadData,
    updateSections,
    updateBlocks,
  },
)

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

Чего мы действительно хотим, так это повторно запускать эти дорогостоящие шаги только в том случае, если state.somedata действительно изменился. Вот тут-то и возникает идея мемоизации, то есть «запоминания».

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

Еще одна полезная вещь для redux — Reselect. Это простая библиотека селекторов, которую можно использовать для создания запоминаемых селекторов. Вы можете определить селекторы как функцию, извлекая фрагменты состояния Redux для компонентов React.

Рассмотрим участок кода без использования reselect:

const App = ({ blocks, tableSize}) => (
    <div>
      <Template blocks={blocks} />
      <UDTable tableSize={tableSize} />
    </div>
  );
  
  const addRowsLength = rows=> ({
    tableSize: rows.length,
  });
  
  App = connect(state => {
    return {
        blocks: state.blocks,
        tableSize: addRowsLength (state.rows)
    };
  })(App);

Каждый раз, когда данные блоков в состоянии изменяются, как Template, так и UDTable, компоненты будут перерисовываться. Это происходит даже тогда, когда addRowsLength не вносит никаких изменений в данные tableSize, поскольку addRowsLength вернет новый объект с другим идентификатором (помните {} != {}). Теперь, если мы перепишем addRowsLength с помощью Reselect, проблема исчезнет, так как Reselect будет возвращать последний результат функции до тех пор, пока ему не будут переданы новые входные данные.

import { createSelector } from "reselect";

const rowsSelector = state => state.rows;

const addRowsLength = createSelector(
  rowsSelector ,
  rows => ({
    tableSize: rows.length
  })
);

Допустим, мы перевели все на функциональные компоненты, используем reselect, что дальше? Хуки!

useSelector. Хук useSelector (или connect HOC) подписывается на хранилище и прослушивает изменения в дереве состояний. В результате каждое изменение состояния Redux распространяет обновления через подписки.

Механизм Redux уверяет нас, что для каждого используемого нами useSelector — только те компоненты, которые используют «useSelector», и только если состояние, возвращаемое хуком, изменится, компонент перерендерится. В результате все компоненты, которые он отображает, и их дочерние элементы будут повторно визуализированы.

Рассмотрим, как будет выглядеть теперь, когда нам в главном файле нужны только все блоки, секции и плейсхолдеры:

const Main = () => {
  const blocks = useSelector(templateBlocksSelector);
  const sections = useSelector(templateSectionsSelector);
  const placeholders = useSelector(templatePlaceholdersSelector);
  
  …
  
  return (
    <div>
        …
    </div>
  );
};
 import { createSelector } from "reselect";

 const templateSelector = state => state.template;

 const templateBlocksSelector= createSelector(
    templateSelector ,
    state=> state.blocks
 );

 const templateSectionsSelector= createSelector(
    templateSelector ,
    state=> state.sections
 );

 const templatePlaceholdersSelector= createSelector(
    templateSelector ,
    state=> state.placeholders 
 );

Стало гораздо читабельнее, а также повысилась производительность. 

Разберем еще один способ использования мемоизации — React.Memo:

const ComponentInfo = ({ contractor}) => {
  const {name, address, inn} = contractor;
  
  return (
    <div>
      <p>{name}</p>
      {address && <p>Адрес: {address}</p>}
      {inn && <p>ИНН: {inn}</p>}
    </div>
  );
};

export default ContractorInfo;

Здесь все дочерние элементы в ComponentInfo основаны на свойствах. Этот компонент без сохранения состояния будет перерисовываться при изменении реквизита. Если маловероятно, что атрибут компонента ComponentInfo изменится, то он является хорошим кандидатом для мемоизации.

const ComponentInfo = ({ contractor}) => {
  const {name, address, inn} = contractor;
  
  return (
    <div>
      <p>{name}</p>
      {address && <p>Адрес: {address}</p>}
      {inn && <p>ИНН: {inn}</p>}
    </div>
  );
};

export default React.Memo(ContractorInfo);

ВРЕДНЫЙ СОВЕТ №3

Либа в проекте — это прекрасно,

Жизнь облегчает сразу она.

Главное помни — спустя даже годы

Не обновляй ты её никогда.

На практике:

Безопасное программирование – это то, о чем нужно знать каждому программисту, и чему следует учить еще на начальном уровне. Но что есть безопасное программирование, а если быть точнее, что нужно считать небезопасным?

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

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

Также бонусом можно немного скомпоновать код. У нас есть настройки для двух версий — production и development.  И сразу, что может бросаться в глаза — это повторяющиеся куски кода. Для решения этой задачи на помощь приходит webpack-merge.

Создадим новый файл и назовем его, к примеру, webpack.base.js:

const baseConfig= {
  module: {
    rules: [
      {
        oneOf: [jsLoader, cssLoader, imageLoader],
      },
    ],
  },
  resolve: {
    alias: {
      process: "process/browser",
    },
    extensions: [".jsx", ".js"],
  },
  plugins: [
    new ProgressPlugin(),
    new HtmlWebpackPlugin({
      template: "public/index.html",
    }),
   ],
  optimization: {
    splitChunks: {
      minSize: 0,
      chunks: "async",
    },
  },
};

export default baseConfig;

Теперь, когда мы захотим объединить эту конфигурацию с development версией, выглядеть это будет так:

import merge from "webpack-merge";

const config= {
  mode: "development",
  …
};

export = merge(config, baseConifg);

ВРЕДНЫЙ СОВЕТ №4

Если всего одну функцию надо,

И самому ее лень написать,

Подключи либу — и лучше всю сразу,

И пусть проект будет весить больше раз в пять.

На практике:

Этот кейс мы разберем на примере того же проекта с повышением производительности, что и в предыдущем случае. Первым делом было принято решение —  посмотреть, насколько часто используется та или иная библиотека. Что изначально бросалось в глаза — express. Фреймворк использовался только для авторизации — получения настроек пользователя и информации о нем. В остальных местах везде axios.

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

Решение: переписываем все вызовы на axios, а переменные запоминаем с помощью webpack.

plugins: [
    …
    new webpack.DefinePlugin({
      // достаем из нашего конфига переменную и записываем
      API_URL: JSON.stringify(apiConfig.API_URL),
    }),
  ],

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

ВРЕДНЫЙ СОВЕТ №5

Копипаста — это сказка.

Ведь удобно и как встарь,

И зачем куда-то лазить —

Ты копируй и вставляй.

На примере:

В каких случаях копипаст может стать серьезной проблемой? И от каких важных факторов это зависит?

Во-первых, качество того, что вы копируете — насколько код понятен, насколько стабилен, сколько в нем багов. Никто не хочет начинать с наследования чужих проблем.

Также стоит обратить внимание на то, сколько копий уже было сделано. Общепринятое эмпирическое правило из книги Фаулера и Бека по рефакторингу гласит: «три удара — и вы рефакторингуете». Это правило исходит из следующего: делая копию того, что уже работает, и изменяя ее, вы создаете небольшую проблему обслуживания. И чем больше копий вы делаете, тем больше проблем с обслуживанием вы создаете — стоимость внесения изменений и исправлений в несколько копий возрастают. В таких случаях лучше вернуться, реструктурировать код и придумать более универсальное решение.

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

Важно это признать и делать с особой осторожностью.

ВРЕДНЫЙ СОВЕТ №6

Функцию ты сам придумай

И пиши как можно кратко.

Назови там как угодно,

Пусть всем будет непонятно.

На примере:

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

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

«Я не могу это сделать, если я не сделаю это идеально», — такой подход погубил множество идей и проектов.

Но также есть и другая сторона медали – пофигисты. Программисты, которым важно только то, чтобы код был рабочим, не обращая внимания на логику, оптимизацию, форматирование и так далее. Ярким примером может послужить такая ситуация, когда кто-то делает обертку над компонентом и называет его «component» или же придумывая название для констант обзывает их как «i».

ВРЕДНЫЙ СОВЕТ №7

Дали задачу? Без промедлений

Сразу садись и выполняй!

В ней разобраться? Зачем тратить время?

Расслабься немного и отдыхай!

На практике:

Анализ задачи — это процесс определения задачи и разложения всей системы на более мелкие подзадачи для определения возможных входных данных, процессов и выходных данных, связанных с проблемой.

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

ВРЕДНЫЙ СОВЕТ №8

Код форматировать и тратить время?

Нет уж, извольте! Зачем это все?

Вот как написано — так прочитают,

Ютубчик мне нужно глянуть еще.

На практике:

Да, форматирование кода не столь важно для работы компонентов. Компьютеру не важно, насколько красиво выглядит код, если он правильный и не выдает ошибок. 

Но тем не менее, кому же это может пригодиться? Людям. Таким же, как вы и я, которым нужно время от времени читать, понимать и изменять код.

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

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

Однако важно отметить, что не существует единого окончательного способа стилизации. К примеру, можно разобрать Prettier c ESlint. Принимая во внимание, что Prettier используется для автоматической корректировки кода, чтобы обеспечить соблюдение самоуверенного формата, ESLint следит за тем, чтобы стиль кода всегда оставался в «хорошей форме».

Заключение

Осознание своих вредных привычек и работа над их исправлением — единственный способ по-настоящему обновиться и стать компетентным разработчиком в долгосрочной перспективе. Надеюсь, каждый из вас сможет для себя что-то почерпнуть и в последующем также делиться своими знаниями и «плохими» советами.

Спасибо за внимание! Полезные материалы для разработчиков мы также публикуем в наших соцсетях – ВК и Telegram

Комментарии (3)


  1. fransua
    19.08.2022 16:56
    +1

    Ну вот debounce чаще всего лучше из lodash взять или еще откуда-нибудь, чем писать самому. Можно импортнуть только его и почти не увеличить размер сборки.
    А для объединения массивов ничего писать не надо, есть concat из коробки.


    1. 4reddy
      22.08.2022 06:41

      А почему лучше из lodash взять?


  1. savostin
    19.08.2022 18:35
    +1

    По теме React только render и redux.... Остальное относится не только к Javascript, а и вообще к программированию.