Что такое 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.
npm run start --workspace=app
Внутри workspace’а можно запустить команду обычном образом.
cd packages/app
npm run start
Аргумент --workspace можно указать несколько раз для запуска команды для нескольких workspace’ов:
npm run start --workspace=app --workspace=api
Для запуска команды в контексте всех workspace’ов нужно указать флаг --workspaces. Эта команда запустит команду start для каждого workspace’а в порядке указанном в поле workspaces в package.json.
npm run start --workspaces
Однако, может оказаться, что не во всех workspace’ах будет определен вызываемый скрипт. Чтобы избежать ошибок 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).

В этой статье повнимательнее рассмотрим два новых хука: 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>
 );
}

Итоги

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