Заметки про React – Telegram
Заметки про React
3.78K subscribers
34 photos
8 videos
485 links
Короткие заметки про React.js, TypeScript и все что с ним связано
Download Telegram
Лучшие практики создания форм с react-hook-form

Обзор лучших практик создания больших форм с использованием react-hook-form и TypeScript:

1. Разделите форму на несколько небольших компонентов. Например, если форма состоит из нескольких секций, то вынесите каждую секцию в отдельный компонент.

2. Используйте пропы value и onChange для ввода и вывода для кастомных компонентов. Такой подход позволит бесшовно интегрировать react-hook-form.

3. Используйте проп name у полей формы для идентификации полей.

4. Используйте кастомный хук для типизации контекста формы:

import { useFormContext } from 'react-hook-form';
export const useMyFormContext = () => useFormContext<MyFormInterface>();


https://orizens.com/blog/best_practices_for_developing_complex_form-based_apps_with_react_hook_form_and_typenoscript_support/
👍8
Sugar High – легковесная подсветка кода

Sugar High - это легковесная подсветка кода на JSX, которая весит 1КБ после минификации и сжатия. Есть возможность кастомизации: можно поменять цвета токенов через CSS переменные.

https://sugar-high.vercel.app/
👍11
Тестирование своих библиотек локально с помощью Yalc

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

Yalc решает проблему локального тестирования библиотек и делает это довольно просто. Используйте команду yalc publish для публикации библиотеки и yalc add my-package в приложении, где хотите протестировать библиотеку.

https://github.com/wclr/yalc
👍121
Апдейт по компилятору React Forget

Сатья Гунасекаран, член основной команды React, рассказал почему затянулось создание компилятора React Forget и насколько он сложнее чем задумывался.

Компилятор будет поддерживать весь JavaScript, включая все его особенности. React Forget будет обратно совместим, поэтому приходится работать с существующим кодом, что усложняет задачу.

Есть задачи, которые на первый взгляд кажутся простыми, но на деле гораздо сложнее – это алиасы. Например, если создать алиас и присвоить переменную x другой переменной y:

function Component({a, b}) {
const x = [];
x.push(a);

const y = x;
y.push(b);

return <Foo x={x}/>;
}


Для компилятора есть вариант запомнить вычисление x отдельно и сделать его зависимостью для y:

// Неправильно
function Component({a, b}) {
const x = useMemo(() => {
const x = [];
x.push(a);
return x;
}, [a]);

const y = useMemo(() => {
const y = x;
y.push(b);
return y;
}, [x, b])

return <Foo x={x}/>;
}


Однако, правильным способом компиляции будет объединение этих вычислений в группу под одним useMemo:

function Component({a, b}) {
const x = useMemo(() => {
const x = [];
x.push(a);

const y = x;
y.push(b);
return y;
}, [a, b]);

return <Foo x={x}/>;
}


Этот пример довольно простой. А теперь представьте, что между x и y добавится поток управления или вызов функции, тогда такой код добавит еще больше сложности для анализа компилятору.

https://www.reddit.com/r/reactjs/comments/16nnh4z/comment/k1jbr4t/
3👍3
Интеграция веб-компонентов и React

Библиотека @lit/react позволяет безболезненно интегрировать веб-компоненты в React. Основная проблема при интеграции веб-компонентов в React – передача пропсов и прием событий. Данная библиотека решает эту проблему через функцию createComponent, которая мапит пропсы в веб-компонент.

Например, чтобы принимать событие onfoo нужно передать объект {onfoo: 'foo'}. Это означает, что событие onfoo будет вызываться каждый раз, когда кастомный элемент отправляет событие foo.

Пример интеграции веб-компонента и мапинг событий:

import * as React from 'react';
import {createComponent} from '@lit/react';
import {MyElement} from './my-element.js';

export const MyElementComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onactivate: 'activate',
onchange: 'change',
},
});


Пример использования компонента в React:

<MyElementComponent
active={isActive}
onactivate={(e) => (isActive = e.active)}
/>


https://www.npmjs.com/package/@lit/react
👍10
Гайд по серверным компонентам

servercomponents.dev – сайт с гайдами по серверным компонентам, в которых объясняется, что это такое, чем это отличается от других решений и как этим пользоваться.
Основная информация для сайта была взята из разных источников, например из подкаста JavaScript Jabber, где Дэн Абрамов и Джо Савона обсуждали серверные компоненты в React. Причина этого в том, что на сайте документации react.dev отсутствует подробная информация про серверные компоненты, поэтому информацию про нее приходится искать в разных источниках.

Автор написал на следующие темы:
- Что такое серверные компоненты и чем они отличаются?
- Какую проблему решают серверные компоненты?
- Поэтапное внедрение серверных компонентов?

https://servercomponents.dev/
👍15
Релиз TanStack Query v5

Вышел релиз библиотеки TanStack Query v5 (react-query). Новая версия меньше предыдущей на 20% и более интуитивно понятна.

Критические изменения:
- Удалено большинство перегрузок функций и унифицировано использование useQuery и других хуков.
- Переименован параметр cacheTime на gcTime.
- Объединены keepPreviousData и placeholderData.
- Переименован loading в pending.
- Удалены колбеки из useQuery.

Новые фичи:
- Упрощение оптимистических изменений. Хук useMutation возвращает переданные аргументы в variables.
- Полная поддержка suspense. Появились хуки useSuspenseQuery, useSuspenseInfiniteQuery и useSuspenseQueries.
- Возможность предзагрузки нескольких страниц в Infinity Query с помощью prefetchInfiniteQuery.
- Новый DevTools.

https://tanstack.com/blog/announcing-tanstack-query-v5
🔥13👍5
Тонкости работы 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