Что такое npm workspaces?
2 года назад·2 мин. на чтение
Набор функций в npm cli, с помощью которого можно управлять несколькими пакетами из единого корня проекта.
Workspaces (рабочие области) — это набору функций в npm cli, с помощью которого можно управлять несколькими пакетами из единого корневого пакета верхнего уровня. Этот функционал появился с 7й версии.
Workspaces упрощают работу с монорепозиториями. Монорепозиторий - это способ организации проекта в котором множество подпроектов хранится в одном и том же репозитории.
Workspaces упрощает работу со связанными пакетами. В нем автоматизирован процесс связывания (linking) и нет необходимости вручную использовать
npm link
для добавления ссылок на пакеты, которые должны быть связаны символическими ссылками в текущую папку node_modules
. Каждый отдельный workspaces - это отдельный изолированный модуль, пакет или подпроект.
Создание проекта и workspace’ов
Проинициализируем проект, запустивnpm init
. Далее добавим в package.json
поле workspaces
. В это поле добавляем имена workspace'ов. packages/
обозначает, что мы поместим подпроекты в папку packages
.
Создадим папки"workspaces": [ "packages/components", "packages/app" ]
packages/components
и packages/app
и запустим npm init
в каждой из них. Получаем такую структуру:
Запустимpackages/ app/ package.json components/ package.json package.json
npm install
. После запуска npm install
папки packages/components
и packages/app
будут связаны символическими ссылками с node_modules
. В результате структура папок будет выглядеть следующим образом:
packages/ app/ package.json components/ package.json node_modules/ // добавилось app/ // symlink на ../packages/app components/ // symlink на ../packages/components package.json package-lock.json // добавилось
Автоматическое создание нового workspace
Для того чтобы не создавать папки самим, можно запустить готовую команду. Добавим еще один подпроект с названиемapi
.
Эта команда автоматически:npm init -w ./packages/api
- создаст папку
api
сpackage.json
внутри; - добавит новый workspace в конфигурацию корневого
package.json
; - создаст symlink.
Добавление зависимостей в workspace
Для того чтобы добавить зависимость в определенный workspace нужно указать этот workspace в командеnpm install
:
Подобным образом можно запускать командыnpm install lodash -w components
uninstall
, ci
и т.д.
Как ссылаться на workspace и использовать его
Воспользуемся функцией из одного workspace’а в другом. Создадим небольшую функцию в пакете components:Вызовем эту функцию в пакете// packages/components/index.js module.exports = { toUpperCase: (str) => String(str).toUpperCase() }
app
:
Запустим скрипт:// packages/app/index.js const { toUpperCase } = require('components') console.log(toUpperCase('hello, world!')) // => 'HELLO, WORLD!'
node ./packages/app/index.js
Запуск команд в контексте workspace’а
Можно использовать параметрworkspace
для запуска команд в контексте выбранного пакета. Кроме того, если текущий каталог находится внутри workspace’а, конфигурация workspace’а задается неявно, а префикс устанавливается для корневого workspace’а.
Из корня проекта запустим команду start
для workspace’а app
.
Внутри workspace’а можно запустить команду обычном образом.npm run start --workspace=app
Аргументcd packages/app npm run start
--workspace
можно указать несколько раз для запуска команды для нескольких workspace’ов:
Для запуска команды в контексте всех workspace’ов нужно указать флагnpm run start --workspace=app --workspace=api
--workspaces
. Эта команда запустит команду start
для каждого workspace’а в порядке указанном в поле workspaces
в package.json
.
Однако, может оказаться, что не во всех workspace’ах будет определен вызываемый скрипт. Чтобы избежать ошибокnpm run start --workspaces
npm ERR! Error: Missing script: "start"
, нужно указать флаг --if-present
. В этом случае npm пропустит workspace’ы, в которых вызываемый скрипт не определен.
Исходный кодnpm run start --workspaces --if-present
5 лучших практик для React разработчиков
2 года назад·6 мин. на чтение
В этой статье мы рассмотрим пять наиболее распространенных лучших практик для React разработчиков. React был разработан для того, чтобы его можно было настроить именно так, как вам нужно - он не имеет жестких ограничений. Поэтому, если эти пять сценариев лучших практик вам не подходят, вы можете найти свой собственный подход.
Существует множество способов структурировать код так, чтобы он был читабельным, и у каждого свой подход к этому. Возникает вопрос, есть ли лучший способ сделать это?
Мы поговорим о лучших практиках:
- Организация каталога
- Компоненты и разделение ответственности
- Работа с состояниями
- Абстракция
- Соглашения об именовании
1. Организация каталогов
В документации React упоминается, что в целом существует два основных способа организации вашего приложения React: Группировка по функциям или маршрутам и Группировка по типу файлов. Главное здесь - не перемудрить. Если вы начинаете с небольшого приложения, вы можете органично организовать свой код по ходу работы так, как вам удобно. Помните, что React не имеет собственной жесткой структуры, поэтому на 100% зависит от вас, как все будет структурировано. Если у вас есть логическое объяснение тому, как организованы файлы, то все хорошо. Однако, поскольку в документации React упоминаются эти две стратегии организации, давайте рассмотрим каждую из них, чтобы понять, как они структурированы. Допустим, у нас есть приложение для электронной коммерции, в котором есть пользователь, список товаров, подробная страница товара, корзина и процесс оформления заказа.Группировка по функциям
Корневой каталог здесь - это каталогsrc
, который является одним из двух базовых каталогов в вашем приложении React (второй - папка public
). В каталоге src
у нас будут основные файлы App.js
и index.js
в корне папки. Затем у нас будут вложенные каталоги для каждой из функций вашего приложения.
Ваш подход к структуре может варьироваться в зависимости от того, как все организовано: в проекте может быть больше каталогов, меньше каталогов или даже более глубокая вложенность на компоненты, стилизацию и тестирование.
src/ App.js App.css App.test.js index.js global/ <= общие для всего приложения сущности AppContext.js ThemeContext.js UserContext.js Button.js cards/ index.js Cards.css Cards.js Card.css Card.js Card.test.js detailed-product/ DetailedProduct.css DetailedProduct.js DetailedProduct.test.js checkout/ ReviewOrder.css ReviewOrder.js ReviewOrder.test.js ShoppingCart.css ShoppingCart.js ShoppingCart.test.js user/ index.js User.css User.js User.test.js
Группировка по типу файлов
Корневой каталог по-прежнему является каталогомsrc
. Все, что будет отображаться на экране клиента, по-прежнему находится в этой папке. Как и раньше, мы будем хранить файлы App.js
и index.js
в корне этого каталога, а затем каталоги, представляющие составные части приложения: компоненты, контекст, CSS, хуки и тесты.
Как и прежде, способ настройки проекта зависит от вашего приложения и от того, как вы хотите его реализовать. Основная структура здесь зависит от типа файла и не более того. В конечном итоге структура вашего файла должна быть сделана так, чтобы в ней было легко ориентироваться. Как вы это сделаете, зависит только от вас. О других вариантах структурирования React приложения читайте в:src/ App.js index.js components/ App.css Card.js Cards.js ConfirmationPage.js DetailedProduct.js Footer.js Navbar.js ReviewOrder.js Settings.js ShoppingCart.js User.js context/ AppContext.js ThemeContext.js UserContext.js css/ Card.css Cards.css ConfirmationPage.css DetailedProduct.css Footer.css Navbar.css ReviewOrder.css Settings.css ShoppingCart.css User.css hooks/ useAuth.js useAxios.js tests/ App.test.js Card.test.js Cards.test.js ConfirmationPage.test.js DetailedProduct.test.js Footer.test.js Navbar.test.js ReviewOrder.test.js Settings.test.js ShoppingCart.test.js User.test.js
2. Компоненты и разделение ответственности
До появления React Hooks было довольно легко определить, что считается компонентом класса с состоянием, а что - презентационным функциональным компонентом. Некоторые разработчики также называли их "умными" компонентами и "глупыми" компонентами. Разумеется, умные компоненты - это те, которые несут состояние и обрабатывают логику, а глупые компоненты - это те, которые просто принимают передаваемые им пропсы. После появления React Hooks и обновления Context API почти все можно считать функциональным компонентом, что приводит к разговору о том, когда следует разделять компоненты, содержащие локальное состояние, и компоненты, которые его не содержат, и как это делать. В конечном счете, это зависит от вас и/или вашей команды, как построить ваш паттерн проектирования, но лучшая практика показывает, что логика и компоненты с локальным состоянием должны быть отделены от статических компонентов. Подробнее о разделении ответственности читайте в статье Разделение ответственности в React. Как использовать контейнерные и презентационные компоненты..3. Работа с состоянием и пропсами
Поток данных в React-приложении очень важен. Есть два способа работы с данными: использование состояния или передача состояния в виде пропсов. Давайте рассмотрим лучшие практики.Состояние
При работе с состоянием, будь то глобально в контекстном API или локально, его нельзя изменять напрямую, переназначая свойство state с новым значением:Вместо этого при работе с состоянием в классовых компонентах используйте методaddOne = () => { // Так обновлять состояние нельзя this.state.counter += 1; }
this.setState()
для обновления состояния.
При использовании React Hooks вы будете использовать любое название своегоimport React from "react"; import "./styles.css"; class Counter extends React.Component{ constructor(props) { super(props); this.state = { counter: 0 } } addOne = () => { this.setState({counter: this.state.counter + 1}) } subtractOne = () => { this.setState({counter: this.state.counter - 1}); } reset = () => { this.setState({ counter: 0 }); } render() { return ( <div className="App"> <h1>Simple React Counter</h1> <h2>{this.state.counter}</h2> <button onClick={this.addOne}> + </button> <button onClick={this.reset}> Reset </button> <button onClick={this.subtractOne}> - </button> </div> ); } } export default Counter;
set
метода:
import React, { useState } from "react"; import "./styles.css"; export default function App() { const [ counter, setCounter ] = useState(0); const addOne = () => { setCounter(counter + 1) } const subtractOne = () => { setCounter(counter - 1); } const reset = () => { setCounter(0); } return ( <div className="App"> <h1>Simple React Counter</h1> <h2>{counter}</h2> <button onClick={subtractOne}> - </button> <button onClick={reset}> Reset </button> <button onClick={addOne}> + </button> </div> ); }
Пропсы
При работе с пропсами и передаче состояния другим компонентам для использования, может наступить момент, когда вам потребуется передать пропсы пяти дочерним компонентам. Такой метод передачи пропсов от родителя к ребенку на протяжении нескольких поколений называется prop drilling, и его следует избегать. Хотя код, безусловно, будет работать, если вы будете передавать проп на много уровней вниз, он подвержен ошибкам, и поток данных может быть трудно отслеживать. Вам следует создать какой-либо паттерн проектирования для глобального управления состоянием с помощью Redux или Context API (Context API в настоящее время является более простым и предпочтительным способом). Подробнее о вариантах передачи данных между реакт компонентами читайте в статье Как передавать данные между компонентами в ReactJS4. Абстракция
React процветает благодаря возможности повторного использования. Когда мы говорим о лучших практиках React, часто встречается термин абстракция. Абстракция означает, что есть части большого компонента или приложения, которые могут быть изъяты, превращены в собственный функциональный компонент, а затем импортированы в более крупный компонент. Если сделать компонент как можно проще, часто так, чтобы он служил только одной цели, это увеличивает шансы на многократное использование кода. В простом приложении счетчика, созданном выше, есть возможность абстрагировать некоторые элементы от компонентаApp
. Кнопки могут быть абстрагированы в собственный компонент, где мы передаем метод и метку кнопки в качестве пропсов.
Заголовок и название приложения также могут быть размещены в собственных компонентах. После того как мы абстрагировали все элементы, компонент App
может выглядеть примерно так:
Основная цель абстракции - сделать дочерние компоненты как можно более общими, чтобы их можно было использовать повторно в любом нужном вам виде. App-компонент должен содержать только ту информацию, которая специфична для приложения, и выводить или возвращать только более мелкие компоненты.import React, { useState } from "react"; import { Button } from "./Button"; import { Display } from "./Display"; import { Header } from "./Header"; import "./styles.css"; export default function App() { const addOne = () => { setCounter(counter + 1) } const subtractOne = () => { setCounter(counter - 1); } const reset = () => { setCounter(0); } const initialState = [ {operation: subtractOne, buttonLabel:"-"}, {operation: reset, buttonLabel: "reset"}, {operation: addOne, buttonLabel: "+"} ] const [ counter, setCounter ] = useState(0); const [ buttonContents, ] = useState(initialState) return ( <div className="App"> <Header header="Simple React Counter"/> <Display counter={counter}/> {buttonContents.map(button => { return ( <Button key={button.operation + button.buttonLabel} operation={button.operation} buttonLabel={button.buttonLabel} /> ) })} </div> ); }
5. Соглашения об именовании
В React есть три основных соглашения об именовании, которые следует рассматривать как лучшие практики.- Компоненты должны быть написаны в PascalCase - а также и названы по их основной функциональности, а не по специфике приложения (на случай, если вы измените ее позже).
- Элементы, которым нужны ключи, должны быть уникальными, неслучайными идентификаторами (например, отдельные карты или записи в карточной колоде или списке). Лучшая практика - не использовать для ключей только индексы. Допустимо назначение ключа, состоящего из конкатенации двух различных свойств объекта.
key={button.operation + button.buttonLabel}
- Методы должны быть в camelCase и называться по их назначению, а не быть специфичными для приложения. По тем же причинам, что и компоненты в PascalCase, методы должны быть названы по их назначению, а не по их особенности в приложении.