Что такое 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
Хуки useTransition и useDeferredValue в ReactJS 18
2 года назад·3 мин. на чтение
В React 18, релиз которого произошел в марте 2022, появилось много новых инструментов для написания производительных и отзывчивых приложений. Одним из заметных изменений является механизм рендеринга с новой ключевой концепцией: конкурентный рендеринг (concurrent rendering).
В этой статье повнимательнее рассмотрим два новых хука:
Какое обновление можно считать срочным, а какое обычным?
Хук
До React 18 все обновления состояния помечались как "срочные". Это означает, что все обновления состояния обрабатывались одинаково с одинаковым приоритетом.
С помощью
Когда использовать
Одним из примеров может быть список товаров с параметрами фильтрации.
Когда вы переключаете чекбоксы, чтобы выбрать размер или цвет одежды, вы ожидаете, что чекбоксы сразу же отобразят отмеченное или снятое состояние.
А сам список товаров, которые необходимо обновить согласно фильтрам, может быть отдельным и менее срочным обновлением.
Как использовать
Хук
Когда использовать
С помощью
Как использовать
useTransition()
и useDeferredValue()
.
Эти два хука дают возможность определять приоритет обновления состояния, или, скорее, указывать, является ли обновление менее важным, чем другие, и откладывать его в пользу более срочных.
- Срочные обновления: отражают прямое взаимодействие, такое как набор текста, клики, нажатия и т. д., т.е. то с чем взаимодействует пользователь. Когда вы вводите текст в поле ввода, вы хотите сразу увидеть введенный вами текст. В противном случае UI будет казаться медленным и подлагивать. Поэтому мы хотим сделать такие обновления приоритетным.
- Обычные обновления: переход пользовательского интерфейса из одного вида в другой. Пользователи знают, что представление должно измениться или обновиться (например, когда ожидается ответ на запрос данных). Даже если есть небольшая задержка, это можно рассматривать как ожидаемое поведение, и это не будет восприниматься как медлительность приложения.
Хук useTransition()
и функция startTransition()
До React 18 все обновления состояния помечались как "срочные". Это означает, что все обновления состояния обрабатывались одинаково с одинаковым приоритетом.
С помощью useTransition()
теперь можно пометить некоторые обновления состояния как несрочные.
Когда использовать useTransition()
?
Одним из примеров может быть список товаров с параметрами фильтрации.
Когда вы переключаете чекбоксы, чтобы выбрать размер или цвет одежды, вы ожидаете, что чекбоксы сразу же отобразят отмеченное или снятое состояние.
А сам список товаров, которые необходимо обновить согласно фильтрам, может быть отдельным и менее срочным обновлением.
Как использовать useTransition()
?
function App() { const [isPending, startTransition] = useTransition(); const [searchQuery, setSearchQuery] = useState(''); // запрос данных, который занимает некоторое время const filteredResults = getProducts(searchQuery); function handleQueryChange(event) { startTransition(() => { // оборачивая setSearchQuery() в startTransition(), // мы помечаем эти обновления как менее важные setSearchQuery(event.target.value); }); } return ( <div> <input type="text" onChange={handleQueryChange} /> {isPending && <span>Loading...</span>} <ProductsList results={filteredResults} /> </div> ); }
Хук useDeferredValue()
useDeferredValue()
очень похож на useTransition()
в том, что он позволяет отложить несрочное обновление состояния, но применяется его к части дерева.
Это похоже методы debounce и throttle, которые мы часто используем для отложенных обновлений. React будет работать с такими обновлениями, как только завершатся срочные обновления.
Когда использовать useDeferredValue()
?
С помощью useTransition()
вы сами решаете, когда конкретное обновление состояния может быть помечено как менее срочное. Но иногда такой возможности может и не быть, например, если фрагмент кода находится в сторонней библиотеке.
В таких случаях можно воспользоваться хуком useDeferredValue()
. С помощью useDeferredValue()
вы можете обернуть значение и пометить его изменения как менее важные и, следовательно, отложить повторный рендеринг.
useDeferredValue()
будет возвращать предыдущее значение до тех пор, пока есть более срочные обновления для завершения и отображения дерева с обновленным значением.
Как использовать useDeferredValue()
?
function ProductsList({ results }) { // deferredResults получат обновленные данные // когда завершатся срочные обновления const deferredResults = useDeferredValue(results); return ( <ul> {deferredResults.map((product) => ( <li key={product.id}>{product.title}</li> ))} </ul> ); }