Заметки про React – Telegram
Заметки про React
3.78K subscribers
34 photos
8 videos
485 links
Короткие заметки про React.js, TypeScript и все что с ним связано
Download Telegram
Тонкости работы useTransition

В React появился хук useTransition, который позволяет делает UI переходы без зависаний. Хук возвращает флаг «загрузки» перехода и функцию для запуска перехода. Изменение стейта, вызванное в функции запуска перехода, выполняется в «фоне», пока нет активных критических изменений стейта.

const [isPending, startTransition] = useTransition();

// …

<TabButton
isLoading={isPending}
onClick={() => {
startTransition(() => {
setTab('projects');
});
}}
/>


Надя Макаревич сделала пример использования useTransition и показала с какой проблемой производительности можно столкнуться. В качестве примера рассматривается компонент с табами. Один из табов – тяжелый компонент, который долго рендерится. Если не использовать useTransition, то при переключении на тяжелый компонент UI фризится до окончания рендера. Если использовать useTransition, то UI не фризится, но возникает другая проблема: когда переключаемся на таб тяжелого компонента, а после на другой таб, то переключение будет занимать больше времени.

Дело в том, что хук useTransition возвращает два аргумента, первый из которых это флаг «загрузки» перехода. Когда мы вызываем функцию перехода, то происходит ре-рендер компонента с текущим состоянием, т.к. флаг «загрузки» перехода становится true. Из-за этого могут возникнуть проблемы с производительностью приложения. Чтобы избежать этого, используйте мемоизацию компонентов и их пропсов.

https://www.developerway.com/posts/use-transition
🔥5👍3
Как оптимизировали импорт пакетов в Next.js

В релизе Next.js 13.5 рассказали, что оптимизировали импорт пакетов. Это улучшило скорость локальной разработки и холодный запуск прод сборки, когда использовались пакеты, которые реэкспортировали большое количество модулей в баррель файле, например, иконки и UI библиотеки.

Если вы хотите импортировать один модуль из баррель-файла, который реэкспортирует тысячи модулей, то в сборку попадут все тысячи модулей. Для решения данной проблемы, сборщики используют три-шейкинг.

Первой попыткой оптимизировать импорт пакетов в Next.js был modularizeImports. Это опция позволяла настроить маппинг между именем экспорта и расположением его файла. То есть при компиляции проекта import { module2 } from 'my-lib’ превращался в import module2 from 'my-lib/module2’.

Проблема этого подхода была в том, что оптимизация ломалась, когда менялась внутренняя структура пакета. Для решения проблемы ввели новую опцию – optimizePackageImports, с помощью которой Next.js автоматически сам замапит имена экспортов и расположение файла с помощью предзаписанного списка популярных библиотек. Этот список библиотек со временем будет расширяться.

https://vercel.com/blog/how-we-optimized-package-imports-in-next-js
👍7
Rspress – генератор статических сайтов на Rspack

Команда ByteDance представила стабильную версию Rspress – генератора статических сайтов для документации. Под капотом использует Rspack – сборщик, написанный на Rust. Основные фичи генератора:
- Скорость сборки и HMR. Гораздо быстрее своих конкурентов.
- Поддержка MDX, а значит можно вставлять React компоненты в разметку.
- Встроенный полнотекстовый поиск на основе FlexSearch.
- Встроенная поддержка i18n и версионирования.
- Возможность кастомизации темы и сборки.

Основные конкуренты – Docusaurus, Nextra от Vercel и Vitepress.

https://rspress.dev/
👍6
Релиз Next.js 14

Вышел релиз Next.js 14. Основные изменения:

- Улучшение в Turbopack. Сборщик, написанный на Rust, стал на 53% быстрее запускаться и на 94% быстрее работать HMR.

- Серверные действия. Появилась возможность в компоненте на клиенте вызывать серверные действия внутри функции:

export default function Page() {
async function create(formData: FormData) {
'use server';
const id = await createItem(formData);
}

return (
<form action={create}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
);
}


Next.js сам разделит код на клиентский и серверный. Если используете TypeScript, то эта фича обеспечит полную сквозную безопасность типов между сервером и клиентом.

Также в серверных действиях можно ревалидировать кэш, делать редирект, читать и устанавливать куки. То есть можно делать практически все то же самое, что и в обычном серверном API.


- Частичный пререндер. Появилась возможность частичного пререндера страницы с помощью Suspense. Например:

export default function Page() {
return (
<main>
<header>
<h1>My Store</h1>
<Suspense fallback={<CartSkeleton />}>
<ShoppingCart />
</Suspense>
</header>
{/* --snip-- */}
</main>
);
}


С частичным пререндером Next.js сгенерит статическую оболочку (shell) страницы, а вместо Suspense подставит fallback. Затем вместо fallback подставится динамический компонент, который пререндерится на сервере и стримится клиенту через тот же HTTP запрос, что и статическая оболочка.

- Улучшение метаданных

Появилась опция управлять мета-тегом viewport, colorScheme и themeColor через переменную viewport или функцию generateViewport:

export const viewport: Viewport = {
themeColor: 'black',
}

export default function Page() {}


https://nextjs.org/blog/next-14
12🔥7👍6
Оптимизация производительности браузерного расширения

Гайд по оптимизации браузерного расширения на React от Casca. Какие шаги команда предприняла:

- Оптимизация изображений. Чтобы не показывать пользователю тяжелые изображения, использовали Node.js библиотеку sharp для оптимизации и уменьшения изображений. Это увеличивает скорость загрузки изображений и уменьшает потребление памяти RAM.

- Кэширование запросов локально для работы в автономном режиме.

- Предотвращение повторных ре-рендеров. Использование useMemo и реализации useEvent для мемоизации пропсов. Также реализовали свою библиотеку, работающую с массивом данных – keep-unchanged-values. Она предотвращает ререндеры, сохраняя неизмененные значения и обновляя только измененные данные.

https://cascaspace.substack.com/p/optimizing-performance-how-our-extension
👍5
Погружение в Storybook

Команда Formidable рассказала, как они работают со Storybook. Обычно принято использовать Storybook как витрину UI компонентов. Кроме этого, Storybook можно использовать для локальной разработки сложных UI компонентов и проведения визуального тестирования.

При работе со Storybook есть одна из сложностей – управление зависимостями компонентов, например контекст, CSS стили, работа сторонних скриптов. Для решения проблемы можно создать компонент-обертку, который будет настраивать окружение для компонентов. Например:


export const TestHarness = ({ children, route = "/", cart = {} }) => {
return (
<MemoryRouter initialEntries={[route]}>
<CartContext.Provider value={{ ...emptyCart, ...cart }}>
{children}
</CartContext.Provider>
</MemoryRouter>
);
};

Использовать его можно как декоратор:
// ~/.storybook/preview.ts
export const decorators = [
(Story, ctx) => <TestHarness {...ctx.parameters}><Story /></TestHarness>
];


В Storybook есть встроенная возможность визуального тестирования. В чем его преимущества:
- Нет необходимости в дополнительной настройке окружения для тестирования.
- Можно визуально увидеть работу тестов и использовать браузерную консоль и отладчик.
- Работает браузерное API и внедрить тестирование гораздо проще, чем использовать сторонние библиотеки.

Как это работает: у каждой Story есть функция play, внутри которой можно взаимодействовать со Story и выполнять проверки. Функция play выполняется при открытии Story и можно визуально увидеть ее работу. Пример:


// ~/components/Search.stories.tsx
export const WithSearchTerm: Story = {
async play({ canvasElement, step }) {
const ui = wrap(canvasElement); // The `wrap` function is defined later in this article

await step("type 'baguette' into the search box", async () => {
ui.searchbox.focus();
await userEvent.type(ui.searchbox, "baguette");
});
},
};


https://formidable.com/blog/2023/unlocking-the-power-of-storybook/
🔥12👍4
Создание игровых интерфейсов с помощью React

Для разработки игровых интерфейсов гораздо удобнее использовать экосистему React, HTML и CSS, чем делать это с помощью Unity. Например, Minecraft и Battlefield в той или иной форме используют React для своих игровых интерфейсов.

Есть два способа использовать React.

- Встраивание браузера или Webview. Если игра на ПК, то будет встраиваться Chromium. Если этот способ сильно утяжеляет игру, то можно воспользоваться Coherent Gameface. Это облегченная версия браузера, адаптированная к игровым UI и его можно использовать в Unity и Unreal Engine.

- Безбраузерный подход. Этот подход пропускает браузерный рендеринг и сразу интерпретирует JS в нативный UI код игрового движка. Наиболее полноценная библиотека для этого – OneJS. Он спроектирован для Unitiy и поддерживает TypeScript и Preact. Этот вариант работает более быстро, чем браузерный подход, но у него есть свои ограничения. Нельзя использовать сторонние библиотеки, Canvas, SVG и сложные CSS анимации.

https://www.adammadojemu.com/blog/intro-to-building-game-uis-with-react
👍61
Headless компонент: паттерн в React

В блоге Мартина Фаулера вышла статья от инженера Atlassian о паттерне Headless компонента.

Headless компонент – это компонент, который обычно реализован в виде React хуков и отвечает исключительно за логику и управление состоянием. По сути он предоставляет функционал без UI.

При визуализации Headless компонент выглядит как тонкий слой по середине между UI и моделью данных.

Этот подход уже используется в нескольких библиотеках: React ARIA, Headless UI, React Table и Downshift.

В своей статье Цзюньтао Цю рассказал, как пошагово создать Headless компонент для элемента Dropdown, подключить его к UI и написать тесты.

https://martinfowler.com/articles/headless-component.html
👍16
Генерация SourceMap для артефактов прод сборки React

В React были смержены изменения, в которых добавилась генерация SourceMap для артефактов прод сборки React, таких как react-dom.production.min.js.

Это означает, что при отладке проблем на проде, код React будет читаемым, поэтому будет проще читать стектрейс ошибки. Также, это улучшит опыт работы с инструментами для анализа ошибок, например с Replay.

https://github.com/facebook/react/pull/26446
👍17
React memo на самом деле хорош

Тимоти Пиллард в своем блоге собрал список наиболее популярных заблуждений о мемоизации в React и объяснил, почему он с этим не согласен. Речь идет о React.memo, React.useMemo, React.useCallback. Кратко о чем он написал:

- «React в любом случае настолько быстр, что React.memo не нужен». В большинстве кейсов React действительно быстрый. API мемоизации существует, чтобы заполнить пробел в модели рендеринга React.
Производительность приложения зависит от множества факторов, а проблемы производительности React приложения в большинстве случаев можно решить с помощью сочетания архитектурных паттернов, рефакторинга и разных инструментов, включая React.memo.

- «React.memo контрпродуктивен». React.memo может быть контрпродуктивным, если им неправильно пользоваться, например, если компонент постоянно получает разные пропсы, то это сделает мемоизацию неэффективной, т.к. повлечет за собой ненужные сравнения.

- «React.memo не работает с пропсом children». Эта проблема происходит из-за того, что React создает элемент через вызов React.createElement(…), который постоянно возвращает новый объект. Этого не произойдет, если передавать в children переменную с элементом, объявленную вне функции компонента, пример:


const someUIElement = <SomeUIComponent />;
function App() {
return <MemoizedComponent>{someUIElement}</MemoizedComponent>;
}


https://timtech.blog/posts/react-memo-is-good-actually/
👍22👎1
Media is too big
VIEW IN TELEGRAM
react-magic-motion – простая анимация для ваших компонентов

react-magic-motion позволяет автоматически добавить в приложение простую анимацию для элементов. Можно использовать для создания анимации появления/исчезновения элементов в списке, открытия карточки, переключения табов. Библиотека сделана на основе Framer Motion.

https://www.react-magic-motion.com/
👍13🔥7
Как и почему Daily Dev перешла с Preact на React

В Daily Dev рассказали, как мигрировали с Preact на React. Изначально они начали проект с Preact и использовали его с Next.js для SSR. Preact был выбран из-за его размера и совместимости с Next.js.

С этим стеком приложение в продакшене работало хорошо, но при локальной разработке были проблемы, связанные с горячей перезагрузкой модулей, обработкой ошибок и медленным рендерингом. Последнее было наиболее критичным, потому что Preact в режиме разработки работал гораздо медленнее, чем ожидали.

Были попытки исправить проблемы в текущем стеке, но это не особо повлияло на результат. Например, если отключить плагин совместимости Preact для Next.js, то улучшится производительность приложения, но перестанет работать горячая перезагрузка модулей.

Единственным вариантом решения проблемы стала замена Preact на React. Т.к. в проекте было много адаптеров Preact для работы с Next.js, то переезд на React не был долгим. Также команда Daily Dev замерила размер бандла – для главной страницы с использованием React он был всего на 34КБ больше.

https://daily.dev/blog/moving-back-to-react
👍9
Новый хук useOptimistic

В React Canary появился хук useOptimistic. Он позволяет показывать другой стейт, пока выполняется асинхронная функция. Пример:

function AppContainer() {
const [optimisticState, addOptimistic] = useOptimistic(
state,
// updateFn
(currentState, optimisticValue) => {
// merge and return new state
// with optimistic value
}
);
}


Разберем как работает этот хук:

optimisticState - это оптимистическое состояние, по умолчанию оно соотвествует state.
addOptimistic - это функция диспатчер, которая принимает optimisticValue и будет вызывать updateFn.
state - это источник правды, если state был изменен, то optimisticState будет установлен в state.
updateFn - это функция мутации, которая принимает текущее состояние и значение optimisticValue и возвращает новое состояние optimisticState.

Чтобы на UI пометить текущий стейт как оптимистичный, можно в updateFn возвращать новый объект с полем sending: true и при рендере показывать сообщение об отправке при наличии sending. Пример:

function Thread({ messages, sendMessage }) {
const formRef = useRef();
async function formAction(formData) {
addOptimisticMessage(formData.get("message"));
formRef.current.reset();
await sendMessage(formData);
}
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true
}
]
);

return (
<>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}


https://react.dev/reference/react/useOptimistic
🔥12👍7
Релиз TypeScript 5.3

Вышла новая версия TypeScript 5.3. Основные изменения:

- Атрибуты импорта. Их можно использовать для указания ожидаемого формата модуля во время выполнения. TS не проверяет содержимое атрибутов, указанный тип и т.д. и оставляет эту проверку браузеру или другой среде выполнения. Пример использования:

// Модуль будет интерпретироваться как JSON
import obj from "./something.json" with { type: "json" };

- Стабильная поддержка resolution-mode для импорта типов. Можно импортировать типы как будто бы импорт происходит с помощью require() или import. Пример:

// Resolve `pkg` as if we were importing with a
require()
import type { TypeFromRequire } from "pkg" with {
"resolution-mode": "require"
};

- Улучшение сужение типов. Если использовать switch (true), то можно сужать тип в условии case. Пример:

switch (true) {
case typeof x === "string":
// 'x' is a 'string' here
console.log(x.toUpperCase());
}

TS начал понимать сужение типов при сравнении с true/false. Пример:

function isA(x: MyType): x is A {
return "a" in x;
}

if (isA(x) === true) {
console.log(x.a); // x is A
}

https://devblogs.microsoft.com/typenoscript/announcing-typenoscript-5-3/
👍18
Как работает React

Проект, в котором написаны статьи с глубоким погружением во внутреннее устройство React. Это не курс и не руководство по React, а объяснение кодовой базы React. Цель проекта – объяснить основные концепции, показать важные «пути кода», чтобы комфортно разбираться в исходном коде React.

Проект еще находится в стадии заполнения, но уже сейчас можно почитать:
- Как создается приложение и работает функция createRoot.
- Как работает рабочий цикл рендера.
- Какая модель хуков и как они работают и т.д.

https://incepter.github.io/how-react-works/docs/intro
👍24🔥5
Как создать ESM+CJS библиотеку React

Гайд по созданию собственной React библиотеки, которая поддерживает экспорт в ESM и CommonJS. При создании библиотеки на JS/TS стоит учитывать следующие моменты:
- Разные системы модулей (ESM, CJS, IIFE, UMD и т.д.), которые не совместимы с друг другом. Поддержка всех не обязательна, но будет хорошей идеей поддерживать ESM и CJS.
- Разные окружения: браузер, Node.js, Deno, Bun.
- В случае JS, возможно придется ориентироваться на более старые версии языка.
- В случае TS, нужно компилировать код в JS перед публикацией, чтобы он работал в проектах на чистом JS.
- Для TS проекта надо отдельно генерировать типы и source map для упрощения отладки.

В своей статье автор представил конфигурацию проекта на Rollup, показал как настроить tsconfig.json с комментарием каждого значения и описал package.json с блоком экспорта.

https://blog.coderspirit.xyz/blog/2023/09/15/create-a-react-component-lib/
👍13🔥1
Типы событий в React и TypeScript

Когда работаешь с React и TypeScript можно столкнуться со следующей ошибкой:

const onChange = (e) => {}; // Parameter 'e' implicitly has an 'any' type.
<input onChange={onChange} />;


Не всегда ясно, какой тип стоит присвоить e в функции onChange. Это может произойти в onClick, onSubmit и другом обработчике DOM событий. Есть несколько вариантов решения проблемы:

1. Навести и узнать тип самому, потом переиспользовать его в объявлении колбека:

// Узнать тип
<input onChange={onChange} /> // React.ChangeEventHandler<HTMLInputElement> | undefined

// Использование
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
console.log(e);
};


2. Встроить функцию и узнать тип аргумента:

// Узнать тип аргумента
<input onChange={(e) => {}} /> // React.ChangeEvent<HTMLInputElement>

// Использование
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e);
};


3. Использовать тип из React для получения пропсов элемента React.ComponentProps:

const onChange: React.ComponentProps<"input">["onChange"] = (e) => {
console.log(e);
};


4. Написать свой тип-помощник для определения типа аргументов:

const onChange = (e: EventFor<"input", "onChange">) => {
console.log(e);
};


https://www.totaltypenoscript.com/event-types-in-react-and-typenoscript
🔥20👍10
Полное руководство по кукам в Next.js

В Next.js есть несколько способов использования куки в разных ситуациях. Например, в серверных компонентах в App Router можно прочитать куки, но нельзя их изменять. В Pages Router в getServerSideProps можно не только прочитать, но и установить куки, включая флаг HttpOnly.

В статье написаны разные способы чтения и изменения куки в Next.js. Пример изменения куки в серверных компонентах с помощью серверных действий:

import {cookies} from "next/headers";

export default function Message() {
async function markAsSeen() {
'use server'
cookies().set("viewedWelcomeMessage", "true");
}

const viewedWelcomeMessage = cookies().has("viewedWelcomeMessage")
if (viewedWelcomeMessage) {
return <div>Welcome back!</div>
}

return <form action={markAsSeen}>
<button type="submit">Mark as seen</button>
</form>
}


https://www.propelauth.com/post/cookies-in-next-js
👍9
Почему мы до сих пор используем React HOC

Если вы используете хуки для асинхронного получения данных, например информация о пользователе, то сталкивались с тем, что надо показывать индикатор загрузки информации. Если это надо сделать только в одном компоненте, то можно сделать прямо в нем. Если же это надо сделать в многих местах, то можно воспользоваться HOC, что поможет уменьшить дублирование кода. Пример:

const withHookWithLoadingState = (InnerComponent) => {
// Промежуточный компонент, который передает hookResponse в InnerComponent
const WrapperComponent = () => {
const hookResponse = useHookWithLoadingState()
if (hookResponse.loading) {
return <Loading />
}
return <InnerComponent hookResponseData={hookResponse.data} />
}

return WrapperComponent
}

// Использование HOC
const Component = withHookWithLoadingState(({hookResponseData}) => {
// Тело компонента
})


Кроме показа индикатора загрузки, HOC можно использовать и для других сценариев, например, отображать другой компонент, если информация о пользователе не загрузилась.

https://www.propelauth.com/post/why-we-have-both-react-hooks-and-hocs
👎6👍51
Самый быстрый способ передать стейт из сервера на клиент

SSR приложения передают стейт на клиент в виде объекта, который парсится на клиенте. Есть несколько способов передать стейт для клиента и важно выбрать наиболее быстрый вариант, т.к. передача больших стейтов на клиент может стать медленным процессом.

В блоге PerfPlanet провели исследование и определили самый быстрый способ передачи стейта на клиенте. Варианты передачи стейта и название способа:

// "Plain object":
window.__STATE__ = {"foo":"bar"}

// "Invalid mime type":
<noscript type="mime/invalid" id="myState">{"foo":"bar"}</noscript>
window.__STATE__ = JSON.parse(window.myState.innerHTML)

// "Just parse":
window.__STATE__ = JSON.parse("{\"foo\":\"bar\"}")


По результат бенчмарков, "Invalid mime type" парсится на 50% быстрее и занимает на 6% меньше памяти браузера. Это связано с тем, что во время парсинга страницы, браузер не парсит и не компилирует код внутри скрипта с типом "mime/invalid". В "Just parse" строка стейта является частью JS кода, поэтому перед выполнением браузер ее парсит и компилирует, что ухудшает производительность.

https://calendar.perfplanet.com/2023/fastest-way-passing-state-javanoscript-revisited/
🔥14👍7
Полиморфизм в React: 2 паттерна, о которых нужно знать

Если вы делаете UI компоненты, то должны знать о полиморфизме. Пример полиморфизма в React - это когда кнопка может выступать в качестве ссылки. При правильном использовании, полиморфизм избавляет от необходимости поддерживать множество вариантов и дает большую гибкость компонента.

В статье рассказывают про два наиболее известных способа реализации полиморфизма в React: через проп as и asChild. Пример реализации asChild на TypeScript с помощью библиотеки radix:

import { Slot } from "@radix-ui/react-slot";

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
asChild?: boolean;
}

export function Button({ asChild, ...props }: ButtonProps) {
const Tag = asChild ? Slot : "button";
return <Tag className="button" {...props} />;
}


https://www.bekk.christmas/post/2023/1/polymorphism-in-react
👍11🔥5