Лучшие практики React для создания лучших приложений на ReactJS
Если вы изучали вопрос о том, как лучше структурировать структуру папок приложения React, то, несомненно, столкнулись с большим разнообразием мнений. Несмотря на то что эта тема может быть несколько субъективной, существуют некоторые рекомендации, которым лучше всего следовать. Поэтому, независимо от того, являетесь ли вы новичком в разработке React, или же вы "старая рука" с другим мнением, здесь найдется что-то для каждого.
#1 Лучшие практики структуры папок React
Группируйте папки по функциям
Если вы знаете, какие функции будут включены в ваше ReactJS-приложение, вы можете легко создать структуру папок на основе этих ключевых функций. Это позволит создать очень аккуратную структуру каталогов для приложения и точно знать, какой код куда входит. Это также значительно облегчает отладку и совместную работу.
Избегайте глубокой вложенности
При создании структуры папок не следует допускать слишком большого количества вложенных папок. Если в самом начале вы обнаружите, что это так, то, возможно, вам следует пересмотреть иерархию папок и разделить их на более мелкие части. Одна из главных проблем глубокой вложенности заключается в том, что становится сложно писать относительные импорты между глубоко вложенными папками или обновлять папки при перемещении файлов.
Чрезмерная задумчивость - ваш враг
Приступая к созданию структуры папок для проекта, не стоит слишком задумываться. Если вам трудно начать работу (потому что ваш мозг думает по кругу), просто сбрасывайте все начальные файлы в корень документа. По мере работы над проектом вы поймете, какой должна быть структура папок. Как только это станет ясно, создайте папки и переместите в них файлы. Можно даже начать с папки src в папке основного проекта, а затем перемещать файлы из этой папки по мере того, как все становится очевидным. Только не оставляйте проект в такой однопапочной структуре, поскольку по мере роста проекта в ней может возникнуть путаница.
Можно просто начать с одной базовой папки (src) и двух подпапок (App и List) для размещения компонентов приложения, что может выглядеть примерно так:
- src/
--- App/
----- index.js
----- App.js
----- App.test.js
----- App.style.css
--- List/
----- index.js
----- List.js
----- List.test.js
----- List.style.css
Разделение компонентов и утилит
Можно также структурировать папки, отделив компоненты от хуков. Такая структура папок может выглядеть следующим образом:
- src/
--- components/
----- App/
------- index.js
------- component.js
------- test.js
------- style.css
----- List/
------- index.js
------- component.js
------- test.js
------- style.css
--- hooks/
----- useClickOutside/
------- index.js
------- hook.js
------- test.js
----- useScrollDetect/
------- index.js
------- hook.js
------- test.js
#2 Лучшие практики использования библиотеки тестирования React
Тестируйте поведение, а не реализацию
Существует большая разница между поведением и реализацией. Разница проста:
При тестировании поведения вам не важно, как вы пришли к ответу, главное, чтобы ответ был правильным при определенном стечении обстоятельств.
При тестировании на реализацию вам не важен ответ, важно лишь то, что вы делаете определенную вещь, пока ее выясняете.
Тестирование поведения позволяет с большей вероятностью прийти к жизнеспособному, повторяемому выводу, поэтому тестирование таким способом является наилучшим вариантом.
Использование пользовательских методов рендеринга для макетов зависимостей
Использование пользовательских методов рендеринга для имитации зависимостей позволяет избежать необходимости выполнять одну и ту же настройку при каждом тестировании. По мере расширения проекта вам, конечно же, не захочется выполнять одну и ту же настройку для каждого теста. С помощью пользовательских методов рендеринга можно также повысить повторяемость и удобство сопровождения пробных тестов.
Тестируйте функциональность, а не реализацию
Если вы сосредоточитесь на тестировании деталей реализации, вы будете тестировать то, как написан ваш код, а не то, что он делает. При таком подходе, если вы измените код, ваши тесты будут провалены, даже если функциональность кода не изменилась. Если же тестировать функциональность, то тесты останутся работоспособными, даже если вы измените реализацию кода.
Пишите тесты, которые не ломаются при изменении пользовательского интерфейса
Ваш пользовательский интерфейс будет меняться. Это неизбежно. При создании тестов следует помнить об этом и писать тесты так, чтобы они не ломались при изменении пользовательского интерфейса. Обязательно создавайте тесты, ориентированные на поведение компонента, а не на его реализацию. Например, вместо того чтобы проверять, что меню использует правильный CSS-класс, следует проверить, что нажатие на меню приводит к ожидаемым результатам.
Не тестируйте то, что уже тестирует React
Библиотека React уже очень хорошо протестирована. При использовании компонентов из этой библиотеки тестировать их не нужно. Вместо этого сфокусируйте свои тесты на компонентах и коде, который вы пишете. Избегайте таких излишеств, так как они только отнимают время.
Используйте помощники для моделирования событий
В состав React входит ряд помощников для имитации событий, таких как change(), click() и keydown(), которые имитируют события, не вызывая их на самом деле. Обязательно используйте эти помощники, а не fireEvent, поскольку он действительно отправляет событие в DOM. Учитывая, что узел DOM не может обрабатывать некоторые типы событий, это может привести к проблемам.
Всегда используйте Act() для тестирования асинхронных событий
Аналогичным образом следует использовать функцию act() для тестирования асинхронных событий. При тестировании асинхронных событий библиотека React Testing Library будет ждать завершения выполнения задач, прежде чем запустить какие-либо утверждения. Проблема заключается в том, что это работает только в том случае, если асинхронные задачи запускаются с помощью обработчика событий. Чтобы избежать этой проблемы, оберните асинхронную задачу в вызов act(), и тогда библиотека React Testing будет ждать завершения всех асинхронных задач перед запуском утверждений.
Утверждайте только одну вещь в каждом тесте
Это очень важно. Если вы утверждаете несколько вещей в одном тесте и одна из них не срабатывает, тест будет провален, и вы не узнаете, какое утверждение вызвало проблему. Вместо этого сфокусируйте тестирование на одном утверждении, чтобы в случае неудачи теста точно знать, какое утверждение является проблемным.
Сохраняйте простоту и целенаправленность тестов
Еще один ключевой аспект тестирования - это простота. При создании слишком сложных тестов отладка проблем становится более сложной. Это особенно актуально по мере усложнения проекта.
#3 Лучшие практики обеспечения безопасности React
Использовать стандартную защиту от межсайтового скриптинга с привязкой данных
Хотя React достаточно безопасен, он все же может быть уязвим для таких вещей, как межсайтовый скриптинг (XSS).Например, для привязки данных по умолчанию всегда следует использовать фигурные скобки. Это гарантирует, что React будет автоматически экранировать значения для защиты от XSS-атак. Вот пример.
Вместо этого:
<form action={data}>
Используйте это:
<div>{data}</div>
Избегайте инъекций сценариев на основе Url
Используя протокол javascript:, URL-адреса могут содержать динамическое содержимое, что может привести к инъекции скриптов на основе URL. Чтобы избежать этого, используйте собственную функцию разбора URL и затем сопоставьте свойство разобранного протокола со списком разрешений.

Использование санитарной библиотеки
В ReactJS HTML можно вставлять непосредственно в отрисованные узлы DOM с помощью функции dangerouslySetInnerHTML, что позволяет разработчикам напрямую вставлять HTML-содержимое внутрь HTML-элемента в React-приложении. При вставке содержимого таким образом оно должно быть обязательно обеззаражено с помощью библиотеки обеззараживания, например DOMPurify. Используйте ее для любых значений, прежде чем они будут помещены в реквизит dangerouslySetInnerHTML. Пример этого выглядит следующим образом:
import purify from "dompurify";
<div dangerouslySetInnerHTML={{ __html:purify.sanitize(data) }} />
Проверка на наличие известных уязвимостей зависимостей
Если вы используете компоненты сторонних разработчиков, убедитесь, что вы тщательно проверили их на наличие уязвимостей. Поскольку эти компоненты предварительно создаются другим программистом (или командой программистов), не стоит просто доверять тому, что эти компоненты были проверены на наличие проблем. Если вы не уверены в безопасности компонента, проверьте его самостоятельно, прежде чем внедрять.
Избегайте атак внедрения JSON
Данные в формате JSON часто передаются с рендерингом страниц на стороне сервера в React. При этом всегда следует экранировать символы < любым безопасным значением. Это позволяет избежать инъекционных атак.
Приведем пример, иллюстрирующий эту практику:
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace( /</g, '\\u003c')}.
Используйте безопасные версии React
Всегда следите за тем, чтобы использовать последнюю версию React. В противном случае вы рискуете использовать версию, содержащую уязвимости, некоторые из которых могут быть критическими. Это справедливо как для react, так и для react-dom.
Используйте линтер
Существуют инструменты, называемые линтерами, которые помогают обнаружить любые проблемы безопасности в вашей кодовой базе. Одним из таких линтеров является инструмент ESLint React Security config tool, который имеет открытый исходный код и доступен бесплатно. Линтеры следует использовать не только для собственного кода, но и для библиотечного. Всегда лучше перестраховаться, чем потом жалеть.
#4 Лучшие практики аутентификации в React
Используйте библиотеку
Когда в приложении требуется аутентификация, лучше всего прибегнуть к помощи библиотеки. Помните, что аутентификация - дело серьезное и может быть сопряжена с множеством рисков. Вместо того чтобы писать собственный код, который может быть связан с проблемами безопасности, обратитесь к готовой библиотеке для этой функции. Существует множество библиотек аутентификации React, которые могут выполнять различные функции. Найдите ту, которая соответствует вашим потребностям, и используйте ее.
Не храните конфиденциальные данные в локальном хранилище
В какой-то момент вам придется хранить конфиденциальные данные, среди которых могут быть и данные пользователя/клиента. Если хранить данные в локальном хранилище (на стороне клиента), то к ним может получить доступ любой пользователь устройства (или даже сторонние приложения), что может привести к проблемам с безопасностью. Вместо того чтобы хранить данные в локальном хранилище, используйте более безопасный вариант, например хранилище сеансов. Еще лучше не хранить конфиденциальную информацию на локальном устройстве... и точка.
Защита конечных точек API
Одним из аспектов безопасности, который некоторые упускают из виду, являются конечные точки API. Дело в том, что если злоумышленнику удастся получить доступ к одной (или нескольким) конечным точкам, то он получит доступ ко всему, включая данные клиента. Одним из методов защиты конечных точек API является использование JSON Web Tokens (JWT), которые представляют собой стандартный метод безопасной передачи информации. Перед передачей информации токен должен быть проверен на вашем сервере.
Если использование JWT не представляется возможным, следует, по крайней мере, использовать HTTPS для шифрования данных. В этом случае, если злоумышленник получит доступ к API, данные, по крайней мере, будут зашифрованы и не смогут быть легко прочитаны. Использование HTTPS также дает дополнительное преимущество - защиту от атак типа "человек посередине", что является еще одним способом получения злоумышленниками доступа к вашим данным.
Шифруйте пароли и другую чувствительную информацию
Кстати, о шифровании: никогда не оставляйте пароли и другую конфиденциальную информацию в незашифрованном виде. В этом случае ваше приложение или сервис будут открыты для атак. Еще хуже, если ваше приложение или сервис используется потребителями или клиентами, и они хранят в нем пароли и другую информацию. Если эта информация не зашифрована, хакеры могут получить к ней доступ и прочитать. Оставлять данные клиентов/потребителей в открытом виде ни в коем случае нельзя.
Внедрите ограничение скорости попыток входа в систему
Если вы не используете ограничение скорости попыток входа в систему, вы оставляете свое приложение или сервис открытым для атак методом "грубой силы". В этом случае хакер сможет проникнуть в приложение и похитить данные, находящиеся в нем, - это лишь вопрос времени. Следует ограничить количество попыток входа в систему, скажем, пятью неудачными попытками в течение определенного периода времени. При превышении этого лимита учетная запись должна быть заблокирована на определенное время. При этом у системы будет шанс обнаружить неудачные попытки и сообщить о них. Без ограничения скорости злоумышленники могут продолжать пытаться войти в систему до тех пор, пока не добьются своего и не получат доступ к вашему приложению или сервису.
Использование 2FA или MFA для обеспечения дополнительной безопасности
Многие пользователи не согласны с необходимостью использовать для входа в систему двухфакторную (2FA) или многофакторную (MFA) аутентификацию. После того как они наконец понимают, зачем это нужно (и что это не такое уж и неудобство, как они думали), пользователи соглашаются на эти дополнительные уровни безопасности. Вместо того чтобы делать 2FA или MFA необязательными, подумайте о том, чтобы сделать их обязательными. Хотя 2FA и MFA не идеальны, они, безусловно, усложняют задачу злоумышленников по получению доступа к вашему приложению или сервису. Любая дополнительная защита, которую можно добавить в приложение, сервис или веб-сайт, должна быть обязательной.
Не используйте электронную почту в качестве имени пользователя
Говоря о безопасности учетных записей, следует подумать о запрете использования адресов электронной почты в качестве имен пользователей. Почему? Использование адресов электронной почты в качестве имен пользователей - очень распространенная практика, а значит, у хакеров будет меньше проблем с угадыванием одного из критериев логина. Вместо того чтобы использовать адреса электронной почты в качестве имен пользователей, подумайте о том, чтобы заставить пользователей создавать уникальные имена, что значительно усложнит взлом аутентификации и сделает ваше приложение значительно более безопасным.
Используйте аутентификацию без пароля
Одним из наиболее надежных методов аутентификации является беспарольная аутентификация. Вместо обычного пароля для входа в систему пользователь получает одноразовый код, который отправляется на его номер телефона, и затем использует его для входа в систему. Еще более безопасным является использование кода из приложения-аутентификатора, например Authy или Google Authenticator. Поскольку такие коды используются только один раз, хакер не сможет угадать пароль - ведь его просто не существует.
Единственным недостатком беспарольной аутентификации является отправка кодов на телефон в виде SMS. Такие коды могут быть перехвачены хакерами, что значительно облегчает взлом учетной записи. Конечно, если пользователь работает с уникальным именем пользователя (а не с адресом электронной почты), то даже если хакер перехватит код, ему придется узнать имя пользователя, связанное с учетной записью, чтобы получить доступ.
Выход из системы неактивных пользователей
В мобильных и веб-приложениях есть одна особенность: пользователи работают с ними некоторое время, после чего приложение им надоедает или они перестают его использовать. В случае с мобильными приложениями эти забытые приложения, как правило, остаются установленными, а учетные записи - зарегистрированными. Это может стать проблемой безопасности. Чтобы обойти эту проблему, рассмотрите возможность встроить в приложение/сервис функцию автоматического выхода из системы по истечении заданного периода времени.
При автоматическом выходе из приложения сторонний пользователь не сможет разблокировать телефон и открыть приложение без предварительной аутентификации. Да, это может быть небольшим неудобством для тех пользователей, которые не пользуются приложением регулярно, но такое удобство не сравнится со взломанной учетной записью. Если взломать учетную запись одного пользователя, то неизвестно, не сможет ли злоумышленник получить доступ к другим учетным записям, конечной точке API или серверу хостинга.
Лучшие практики ReactJS: Заключение
ReactJS широко используется во всем мире для создания интерактивных интерфейсов как для веб-приложений, так и для мобильных устройств. Учитывая широкое распространение таких приложений, разработчики обязаны постоянно следовать лучшим практикам, чтобы избежать получения хакерами доступа к конфиденциальным данным, что может привести к большим неприятностям, чем вы можете себе представить.
Если проявить немного осторожности, то можно избежать подобной катастрофы. В итоге ваши клиенты и заказчики будут доверять вашему приложению и вашей компании. Такое доверие нельзя купить или продать, поэтому его следует считать бесценным товаром.
См. также: Разработка ПО, Разработка веб-приложений: Ресурсы, лучшие практики и как это сделать.


