Используем приёмы из функционального программирования, чтобы улучшить свой код на JavaScript
Статья расскажет о том, что делает код на JavaScript нанадёжным, и как это исправить с помощью приёмов из функционального программирования.
17К открытий17К показов
Рассказывает Илья Суздальницкий, senior full-stack-разработчик
JavaScript — самый распространённый язык программирования в мире. Простота и обилие учебных ресурсов делают его доступным для начинающих. Множество талантливых разработчиков делают этот язык привлекательным для компаний любого размера. Огромная экосистема инструментов и библиотек — настоящий дар для производительности разработчиков. Когда один язык позволяет управлять и фронтендом и бэкендом, а один и тот же набор навыков может использоваться во всём стеке, это становится огромный преимуществом.
Ядерная сила JavaScript
JavaScript предоставляет множество инструментов и это очень хорошо. Но горькая правда заключается в том, что он практически не налагает никаких ограничений на разработчика. Если посадить за JavaScript неопытного разработчика, это будет равносильно тому, как дать двухлетнему ребёнку коробку спичек и канистру бензина.
Сила JavaScript — ядерная, и её можно направить на снабжение городов электричеством или на разрушение. Создать что-то, что работает на JavaScript, легко. А вот создать программное обеспечение, одновременно надёжное и обслуживаемое, — нет.
Надёжность кода
При строительстве плотины инженеры в первую очередь заботятся о надёжности. Возведение плотины без каких-либо планов или мер безопасности опасно. То же самое относится к строительству мостов, самолётов, автомобилей. Ни одна из новых «плюшек» не имеет значения, если автомобиль небезопасен и ненадёжен — ни лошадиные силы, ни громкость выхлопа, ни тип кожи, используемой в интерьере.
Точно так же цель каждого разработчика — написать надёжное программное обеспечение. Ничто другое не имеет значения, если код глючит. Самый лучший подход к написанию такого надёжного кода — простота. Простота противоположна сложности. Следовательно, первая и главная задача разработчиков должна заключаться в упрощении кода.
Опытного разработчика от неопытного отличает умение писать надёжный софт. Надёжность также включает в себя поддержку — только поддерживаемая кодовая база может быть надёжной. Вся эта статья посвящена написанию именно надёжного кода.
Хоть я и сторонник функционального программирования, я не собираюсь ничего проповедовать. Вместо этого я собираюсь взять несколько полезных понятий из функционального программирования и продемонстрировать, как их можно применять в JavaScript.
Действительно ли важна надёжность программного обеспечения? Это решать вам. Некоторые утверждают, что достаточно того, чтобы клиенты могли продолжать использовать софт. Я не согласен. На самом деле ничто иное не имеет значения, если ПО ненадёжно и его трудно поддерживать. Кто купил бы машину, которая ломается и разгоняется случайным образом? Сколько людей будет использовать телефон, который теряет связь несколько раз в день и перезагружается? Вероятно, не слишком много. Программное обеспечение не сильно отличается в этом плане.
Недостаток ОЗУ
В разработке необходимо учитывать количество доступной оперативной памяти. Любой специалист знает, что разрабатывать программы надо так, чтобы они эффективно использовали память и никогда не использовали всю доступную. Если это произойдёт, начнёт действовать механизм подкачки. Всё, что не помещается в ОЗУ, будет сохраняться на жёстком диске, и тогда производительность всех запущенных программ начнёт ухудшаться.
Как это связано с написанием надёжного программного обеспечения? Человеческий мозг имеет собственную версию оперативной памяти, называемую рабочей. Наш мозг — самая мощная машина в известной вселенной, но он имеет свои ограничения. Мы можем хранить только около пяти фрагментов информации в нашей рабочей памяти в любой момент времени.
Это можно прямо перевести на программирование — простой код потребляет меньше умственных ресурсов, делает нас более эффективными и в результате приводит к более надёжному программному обеспечению. Эта статья вместе с некоторыми из доступных инструментов JavaScript поможет вам достичь этого.
Заметка для новичков
В этой статье будут использоваться функции ES6. Убедитесь, что вы знакомы с ES6, прежде чем читать. Как краткое напоминание:
Инструментарий
Одна из самых сильных сторон JavaScript — доступный инструментарий. Ни один другой язык программирования не может похвастаться доступом к такой большой экосистеме инструментов и библиотек.
И этими инструментами нужно пользоваться, особенно ESLint — инструмент для статического анализа кода. Это самый важный инструмент, который позволяет находить потенциальные проблемы в кодовой базе и обеспечивать её высокое качество. Самое приятное, что linting — это полностью автоматизированный процесс, который можно использовать, чтобы низкокачественный код не попал в базу.
Многие практически не используют ESLint. Они просто включают предварительно созданную конфигурацию вроде eslint-config-airbnb и думают, что всё готово. Это допустимый подход, но он едва затрагивает то, что может предложить ESLint. JavaScript — язык без ограничений. Неправильная настройка линтинга может иметь далеко идущие последствия.
Знать все возможности языка полезно, но опытный разработчик знает, какие функции не следует использовать. JavaScript — старый язык, он поставляется с большим количеством багажа, который он пытается реализовывать. Важно уметь отличать хорошие функции от плохих.
Настройка ESLint
Я бы порекомендовал ознакомиться с указаниями по очереди, а также включить правила ESLint в ваш проект. Изначально настройте их как предупреждение (warn
). А когда вам будет удобно, можете преобразовать некоторые указания как ошибки (error
).
В корневом каталоге проекта запустите:
Затем создайте там же файл .eslintrc.yml
:
Если вы используете IDE вроде VSCode, обязательно установите плагин ESLint.
Также можно запустить ESLint вручную из командной строки:
Важность рефакторинга
Рефакторинг — процесс упрощения существующего кода. При правильном применении рефакторинг становится лучшим оружием против страшного монстра — технического долга. Без постоянного рефакторинга технический долг будет накапливаться, что сделает разработчиков медленнее и разочарованнее. Технический долг является одной из основных причин выгорания разработчиков.
Рефакторинг сводится к процессу очистки существующего кода с одновременной проверкой правильности его работы. Это считается хорошей практикой в разработке программного обеспечения и должно быть нормальной частью процесса разработки в любой здоровой организации.
В качестве предостережения перед проведением рефакторинга лучше всего покрыть код автоматическими тестами. Легко непреднамеренно нарушить существующую функциональность, а комплексный набор тестов — отличный способ ограничить любые потенциальные проблемы.
Самый большой источник сложности
Это может звучать странно, но сам код является самым большим источником сложности. На самом деле, отсутствие кода — лучший способ написания безопасного и надёжного софта. Это не всегда возможно, поэтому второй лучший способ — уменьшить объём кода. Всё просто: чем меньше кода, тем меньше сложности, ведь тогда пространства для ошибок меньше. Существует даже поговорка, что джуниоры пишут код, тогда как сениоры его удаляют.
Длинные файлы
Признаем, люди ленивы. Лень — это краткосрочная стратегия выживания, заложенная в мозгу. Это помогает сохранять энергию, избегая вещей, которые не являются критическими для выживания.
Некоторые из нас немного ленивы и не очень дисциплинированы. Многие программисты продолжают помещать всё больше кода в один и тот же файл. Если нет ограничений на длину файла, то они могут расти бесконечно. По моему опыту, файлы с более чем 200 строками кода становятся слишком большими для восприятия человеческим мозгом. Их становится трудно поддерживать. Длинные файлы также являются признаком более серьёзной проблемы — код делает слишком много, что нарушает принцип единственной ответственности.
Это можно решить очень легко. Просто разбейте большие файлы на более мелкие и более детализированные модули.
Предлагаемая конфигурация ESLint:
Длинные функции
Другими важными источниками сложности являются длинные и сложные функции. Обычно они имеют слишком много обязанностей и их сложно проверить.
Рассмотрим фрагмент кода express.js
для обновления записи в блоге:
Тело функции имеет длину 40 строк и выполняет несколько задач: анализирует идентификатор сообщения, находит существующее сообщение в блоге, проверяет введённые пользователем данные, возвращает ошибки проверки в случае неправильного ввода, обновляет коллекцию сообщений и возвращает обновлённые сообщения в блоге.
Это может быть преобразовано в несколько функций меньшего объёма. Полученный обработчик маршрута может выглядеть примерно так:
Предлагаемая конфигурация ESLint:
Сложные функции
Сложные функции идут рука об руку с длинными — более длинные функции всегда сложнее, чем короткие. Некоторые вещи делают функции более сложными. То, что можно исправить среди прочего, — это вложенные колбеки (callback) и высокая цикломатическая сложность.
Вложенные колбеки часто приводят к колбек-аду (callback hell). Это может быть легко исправлено использованием промисов (promise) и асинхронных функций async()
и await()
.
Вот пример функции с глубоко вложенными колбеками:
Цикломатическая сложность
Ещё функции перегружает цикломатическая сложность. Это относится к количеству операторов (логических) в любой функции: операторы if
, циклы и switch
-утверждения. Такие функции трудно воспринимать. Их использование должно быть ограничено. Вот пример:
Предлагаемая конфигурация ESLint:
Декларативный код — другой важный способ уменьшить объём кода, но об этом позже.
Изменчивое состояние
Состояние — это любые временные данные, хранящиеся в памяти, например переменные или поля внутри объектов. Состояние само по себе довольно безобидно. Изменчивое состояние является одним из источников сложности программного обеспечения, особенно в сочетании с объектной ориентацией (подробнее об этом позже).
Ограничения человеческого мозга
Почему изменчивое состояние такая большая проблема? Наш мозг плохо работает с состоянием, поскольку мы можем хранить в рабочей памяти одновременно только около пяти элементов. Гораздо проще рассуждать о куске кода, если вы думаете только о том, что делает код, а не о том, какие переменные он изменяет вокруг кодовой базы.
Программирование с изменяемым состоянием — это акт умственного жонглирования. Жонглировать двумя шарами довольно просто. Тремя или больше — гораздо сложнее. С написанием кода так же. Я стал намного продуктивнее и мой код стал намного надёжнее, как только я отбросил изменчивое состояние.
Проблемы с изменчивым состоянием
Посмотрим на практике, как изменчивость может сделать код проблематичным:
Ошибка очень тонкая. Изменяя аргументы функции, вы случайно изменили цену исходного элемента. Предполагалось, что её значение равно 10, но на самом деле значение изменилось на 13.
Этого можно избежать, создавая и возвращая новый объект (неизменность):
Следует отметить, что копирование с использованием ES6-оператора «spread» делает поверхностную, а не глубокую копию — она не будет копировать ни одно из вложенных свойств. Например, если у товара выше есть что-то вроде item.seller.id
, seller
нового товара всё равно будет ссылаться на старый товар. Другие более надёжные альтернативы для работы с неизменяемым состоянием в JavaScript включают в себя immutable.js и Ramda lenses.
Предлагаемая конфигурация ESLint:
Не используйте push для массивов
Те же проблемы присущи изменениям массива с использованием таких методов, как push
:
Вы могли ожидать, что массив b
останется прежним. Эту ошибку можно легко избежать, если создать новый массив вместо вызова push
.
Недетерминизм
Недетерминизм — это причудливый термин, который описывает просто неспособность программ производить одинаковый результат при одинаковых входных данных. Вы можете думать, что 2 + 2 == 4
, но это не всегда так с недетерминированными программами. Два плюс два в большинстве случаев равно четырём, но иногда и трём, и пяти, и даже 1004.
Хоть изменяемое состояние само по себе не является недетерминированным, оно делает код склонным к недетерминизму (как показано выше). Ирония в том, что недетерминизм повсеместно считается нежелательным в программировании, однако наиболее популярные парадигмы программирования (ООП и императивное программирование) особенно подвержены ему.
Неизменность
Использование неизменности — очень хорошая альтернативная практика по отношению к недетерменизму. Некоторые могут не согласиться и сказать, что изменчивое состояние великолепно. Одно я могу сказать наверняка — изменчивость не делает кодовую базу более надёжной.
Предлагаемая конфигурация ESLint:
Как избежать ключевого слова Let
Все знают, что не нужно использовать ключевое слово var
для объявления переменных в JavaScript. Но вы точно будете удивлены, узнав, что ключевого слова let
также следует избегать. Переменные, объявленные с помощью него, могут быть переназначены. Это усложняет анализ кода. При его использовании необходимо учитывать все побочные эффекты и возможные пограничные случаи. Можно случайно присвоить переменной неверное значение и потратить время на отладку.
Это особенно относится к модульному тестированию. Совместное использование изменяемого состояния между несколькими тестами — это путь к катастрофе, учитывая, что большинство раннеров тестов запускают тесты параллельно.
Альтернативой использования let
является ключевое слово const
. Хоть оно и не гарантирует неизменности, оно упрощает анализ кода, запрещая переназначения. Вам на самом деле не нужен let
— в большинстве случаев код, который переназначает значения переменных, может быть извлечён в отдельную функцию. Рассмотрим пример:
Тот же пример, извлечённый в функцию:
Кодить без let
поначалу может показаться трудным, но это сделает ваш код менее сложным и более читабельным.
Привычка программировать без let
сделает вас более дисциплинированным, а это хороший побочный эффект. Это заставит вас разбивать ваш код на более мелкие и более управляемые функции. Что, в свою очередь, сделает ваши функции более сфокусированными, обеспечит лучшее разделение задач, а также сделает кодовую базу намного более читаемой и поддерживаемой.
Предлагаемая конфигурация ESLint:
Объектно-ориентированное программирование
Даже не вдаваясь в подробности недостатков ООП, я твёрдо убеждён, что современное ООП является одним из крупнейших источников сложности программного обеспечения. Есть успешные проекты, построенные с использованием ООП, однако это не означает, что такие проекты не страдают от необоснованной сложности.
ООП в JavaScript является особенно плохой идеей, поскольку в языке отсутствуют такие вещи, как статическая проверка типов, обобщения и интерфейсы. Ключевое слово this
в JavaScript довольно ненадёжно.
Сложность ООП является хорошим упражнением для мозга. Но если целью является написание надёжного ПО, необходимо стремиться к уменьшению сложности. В идеале это означает избегать ООП. Если вы хотите узнать больше, ознакомьтесь со статьей «Object-Oriented Programming — The Trillion Dollar Disaster»Прим. ред. ( Читайте перевод на нашем сайте).
Ключевое слово this
Поведение ключевого слова this
последовательно непоследовательное. Оно привередливо и может означать совершенно разные вещи в разных контекстах. Его поведение зависит даже от того, кто вызвал данную функцию. Использование этого ключевого слова часто приводит к неуловимым и странным ошибкам, которые трудно отладить.
Это может быть забавный вопрос в интервью для кандидатов на работу, но на самом деле знание этого ключевого слова ни о чём не говорит. Только то, что кандидат потратил несколько часов на изучение наиболее распространённых вопросов интервью на JavaScript.
Что бы я ответил, если бы мне дали хитрый кусок кода с ключевым словом this
? Как канадец, я бы сказал: «Простите… я не знаю». Реальный код не должен быть подвержен ошибкам. Он должен быть читаемым, бесхитростным. Слово this
— очевидный недостаток архитектуры языка, и его не следует использовать.
Предлагаемая конфигурация ESLint:
Декларативный код
Если у вас уже есть небольшой опыт программирования, скорее всего, вы использовали императивный стиль. Он описывает набор шагов для достижения желаемого результата. Декларативный стиль описывает желаемый результат, а не конкретные инструкции. Некоторые примеры часто используемых декларативных языков — SQL, HTML и даже JSX в React.
Базе данных не сообщаются точные шаги, как получить данные. Вместо этого используется SQL для описания, что нужно получить из базы.
Это примерно может быть представлено в императивном JavaScript:
Или в декларативном JavaScript, используя экспериментальный оператор конвейера:
Предпочтение выражений над операторами
Выражения должны быть предпочтительнее операторов, если наша цель — написать декларативный код. Выражения всегда возвращают значение, тогда как операторы используются для выполнения действий и не возвращают никаких результатов. Это также называется побочными эффектами в функциональном программировании. Кстати, изменение состояния, которое обсуждалась ранее, также является побочным эффектом.
Наиболее часто используемые операторы: if
, return
, switch
, for
, while
.
Рассмотрим простой пример:
Это может быть легко переписано как тройное выражение (которое является декларативным):
И если return
является единственным оператором в лямбда-функции, JavaScript позволяет также полностью избавиться и от лямбда-выражения:
Тело функции было сокращено с пяти строк кода до двух.
К другим недостаткам использования операторов относится также то, что они вызывают побочные эффекты и изменения, что свойственно недетерминизму. Это делает код менее читаемым и менее надёжным. Операторы небезопасны для переупорядочивания. Они полагаются на порядок, в котором они были использованы. Операторы (включая циклы) трудно распараллелить, поскольку они изменяют состояние за пределами своей области видимости. Работа с операторами подразумевает дополнительные умственные издержки из-за повышенной сложности.
Выражения можно безопасно переупорядочивать, не вызывая побочных эффектов, и их легко распараллелить.
Декларативное программирование требует усилий
Декларативное программирование не может быть изучено за одну ночь. Особенно учитывая, что большинство людей в основном обучалось императивному программированию. Декларативное требует дисциплины и умения мыслить совершенно по-новому. Хороший первый шаг для изучения декларативного программирования — научиться писать код без изменяемого состояния и без использования ключевого слова let
. Можно сказать одно наверняка: если вы попробуете декларативное программирование, вы будете удивлены, насколько элегантным станет ваш код.
Предлагаемая конфигурация ESLint:
Избегайте передачи нескольких параметров в функции
JavaScript не является статически типизированным языком, и нет способа гарантировать, что функция вызывается с правильными и ожидаемыми параметрами. ES6 привносит множество замечательных функций. В том числе деструктурирование объекта, которое также можно использовать для аргументов функций.
Считаете ли вы следующий фрагмент кода интуитивно понятным? Можете сразу сказать, какие у него параметры?
Что насчёт следующего примера?
Скорее всего, последний гораздо удобнее для чтения, чем первый. Это особенно относится к вызовам функций из другого модуля. Порядок аргументов не имеет значения, когда они используются объектом, и это хорошее преимущество.
Предлагаемая конфигурация ESLint:
Старайтесь возвращать объекты из функций
Что следующий фрагмент кода может рассказать о сигнатуре функции? Что она возвращает: объект пользователя, идентификатор пользователя, статус операции? Трудно сказать, не понимая контекста.
Возвращение объекта из функции делает намерение разработчика ясным. Код становится значительно более читабельным:
Управление потоком выполнения с исключениями
Вам не захочется наблюдать множество внутренних ошибок сервера при вводе неверных данных в форму. Как насчёт работы с API, которые не дают никаких подробностей, а вместо этого выдают ошибку «500»? Думаю, многие сталкивались с такими проблемами. И опыт был далеко не приятным.
Хоть многих и учат генерировать исключения, когда происходит что-то непредвиденное, это не лучший способ обработки ошибок. Рассмотрим, почему.
Исключения нарушают безопасность типов
Исключения нарушают безопасность типов даже в статически типизированных языках. Согласно своей сигнатуре, функция fetchUser(id: number): User
должна вернуть пользователя. Ничто в сигнатуре функции не говорит о том, что будет сгенерировано исключение, если пользователь не может быть найден. Если ожидается исключение, то более подходящей сигнатурой функции будет: fetchUser(...): User|throws UserNotFoundError
. Конечно, такой синтаксис недопустим независимо от языка.
Анализ программ, которые генерируют исключения, становится сложным. Никто никогда не знает, будет ли функция генерировать исключение. Можно обернуть каждый вызов функции в блок try/catch
, но это непрактично и значительно ухудшит читаемость кода.
Исключения нарушают композицию функций
Исключения делают практически невозможным использование композиции функций. В следующем примере сервер вернёт внутреннюю ошибку сервера («500»), если одна из публикаций в блоге не найдена.
Что, если одно из сообщений было удалено, но пользователь всё ещё пытается получить доступ к сообщению из-за какого-то непонятного бага? Это значительно ухудшит пользовательский опыт.
Кортежи как альтернативный способ обработки ошибок
Способ обработки ошибок состоит в возврате кортежа, содержащего результат и ошибку, вместо генерирования исключения. JavaScript не поддерживает кортежи, но их можно легко эмулировать, используя массив из двух значений в виде [error, result]
. Кстати, это также стандартный метод обработки ошибок в Go:
Иногда исключения хороши
Исключения всё ещё занимают своё место в кодовой базе. Вы должны задать себе вопрос: хотите ли вы, чтобы ваша программа аварийно завершилась? Любое брошенное исключение может уронить весь процесс. Даже если вы думаете, что тщательно рассмотрели все потенциальные пограничные случаи, исключения всё же являются небезопасными и приведут к аварийному завершению программы в будущем. Выбрасывайте исключения только в том случае, если вы действительно намерены вывести программу из строя. Например, из-за ошибки разработчика или сбоя соединения с базой данных.
Исключения так называются не просто так. Их следует использовать только тогда, когда произошло что-то исключительное, и у программы нет другого выбора, кроме как аварийно завершиться. Бросать и перехватывать исключения — не очень хороший способ контролировать поток выполнения. Прибегать к выбрасыванию исключений следует только в случае неисправимых ошибок. Неверный пользовательский ввод, кстати, к таковым не относится.
Позвольте коду аварийно завершиться — избегайте перехвата исключения
Окончательное правило обработки ошибок — избегайте перехвата исключений. Всё правильно, вам разрешено выдавать ошибки, если вы намерены аварийно завершить работу программы. Но вы никогда не должны отлавливать такие ошибки. Фактически, это подход, рекомендуемый функциональными языками, такими как Haskell и Elixir.
Единственным исключением из этого правила является использование сторонних API. Даже тогда лучше использовать вспомогательную функцию, которая оборачивает основную функцию, возвращающую кортеж [error, result]
. Для этого вы можете использовать такие инструменты, как Saferr.
Задайтесь вопросом — кто несёт ответственность за ошибку? Если это пользователь, то ошибка должна быть обработана изящно. Пользователю необходимо показать приятное сообщение вместо внутренней ошибки «500».
К сожалению, в ESLint нет правила no-try-catch
. Его ближайший сосед — no-throw
. Убедитесь, что вы отбрасываете ошибки ответственно, в исключительных случаях, когда вы ожидаете сбой программы.
Предлагаемая конфигурация ESLint:
Частичное применение функций
Частичное применение функций, вероятно, является одним из лучших когда-либо изобретённых механизмов совместного использования кода. Вы можете внедрить зависимости в свой код, не прибегая ко всем типичным шаблонам ООП.
В следующем примере оборачивается библиотека Axios. Она печально известна тем, что выдаёт исключения (вместо возврата ошибочного ответа). Работать с такими библиотеками сложно, особенно при использовании async/await.
В следующем примере рассмотрим использование карринга и частичное использование функций, чтобы сделать небезопасную функцию безопасной.
Обратите внимание, что функция safeApiCall
записывается как func = (params) => (data) => {...}
. Это распространённая техника в функциональном программировании, называемая каррингом. Она всегда идёт рука об руку с частичным применением функций. Это означает, что функция func
при вызове с params
возвращает другую функцию, которая фактически выполняет работу. Другими словами, функция частично применяется с params
.
Это также может быть записано как:
Обратите внимание, что зависимости (params
) передаются как первый параметр, а фактические данные передаются как второй параметр.
Чтобы упростить задачу, вы можете использовать npm-пакет saferr, который также работает с промисами и async/await:
Несколько маленьких хитростей
Вот несколько маленьких, но полезных трюков. Они необязательно делают код более надёжным, но могут сделать вашу жизнь немного проще.
Немного безопасности типов
JavaScript не является статически типизированным языком. Но вы можете прибегнуть к небольшой хитрости, чтобы сделать код более надёжным, пометив аргументы функции как необходимые. Следующий код будет выдавать ошибку всякий раз, когда требуемое значение не было передано. Обратите внимание, что оно не будет работать для нулевых значений, но по-прежнему отлично защищает от undefined
-значений.
Но, разумеется, существует гораздо больше способов обработать undefined в JavaScript.
Вычисление условий короткого замыкания
Условия короткого замыкания широко известны и полезны для доступа к значениям во вложенных объектах.
Вычисление короткого замыкания полезно для предоставления альтернативного значения, если значение является ложным:
Bang bang!!
Отрицание значения дважды — отличный способ сделать любое значение логическим. Имейте в виду, что любое ложное значение будет преобразовано в false
. Это не всегда то, чего вы хотите. Никогда не используйте это для чисел, так как 0
также будет преобразован в false
.
Отладка с логированием на месте
Вы можете использовать короткое замыкание и тот факт, что результат console.log
не подходит для отладки функционального кода, даже включая компоненты React:
Что дальше?
Действительно ли вам нужна надёжность кода? Это решать вам. Вы работаете на фабрике фич, где количество важнее качества? Надеюсь, что нет. Но если это так, то вы можете подумать о поиске лучшего места работы.
Не пытайтесь применить сразу всё из этой статьи. Поместите её в закладки и продолжайте возвращаться к ней время от времени. Каждый раз убирайте одну вещь, на которой вы в данный момент сосредоточились. И включите соответствующие правила ESLint, чтобы себе помочь.
17К открытий17К показов