Заметки про React – Telegram
Заметки про React
3.78K subscribers
34 photos
8 videos
485 links
Короткие заметки про React.js, TypeScript и все что с ним связано
Download Telegram
Новый хук 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
Как JSX преобразуется в HTML

В блоге Дэна Абрамова появилась статья с названием «Цепная реакция». В ней описано как JSX преобразуется в HTML код, понятный для браузера. В статье приводится пример упрощенной функции, которая трансформирует JSX:

function translateForBrowser(originalJSX) {
const { type, props } = originalJSX;
if (typeof type === 'function') {
const returnedJSX = type(props);
return translateForBrowser(returnedJSX);
} else if (typeof type === 'string') {
return {
type,
props: {
...props,
children: translateForBrowser(props.children)
}
};
}
}

Пример как эта функция работает:

function Greeting({ person }) {
return (
<p className="text">
Hello, <i>{person.firstName}</i>!
</p>
);
}

const originalJSX = <Greeting person={alice} />;
console.log(originalJSX.type); // Greeting
console.log(originalJSX.props); // { firstName: 'Alice', birthYear: 1970 }

const browserJSX = translateForBrowser(originalJSX);
console.log(browserJSX.type); // 'p'
console.log(browserJSX.props); // { className: 'text', children: ['Hello', { type: 'i', props: { children: 'Alice' }, '!'] }


С помощью функции translateForBrowser можно преобразовать JSX код в HTML строку или в набор инструкций для обновления существующего DOM.

https://overreacted.io/a-chain-reaction/
👍26🔥3👎2
Хитрости React: быстро, удобно и весело

Автор React роутера Wouter поделился хитростями для React, которые улучшают производительность и уменьшают размер бандла. Автор рекомендует их применять, когда библиотека уже выпущена и начинается этап доработок, в ходе которых можно улучшить метрики производительности. О чем пишет автор:

- Используйте React.cloneElement для композиций. Если нет доступа к элементу, но нужно изменить его пропсы, то можно использовать клонирование элемента:

cloneElement(<img />, { src: "keyboard-cat.webp" }) 
// <img src="keyboard-cat.webp" />


Также с помощью клонирования можно установить свой реф и получить доступ к DOM элементу.

- Улучшайте производительность. По умолчанию компоненты в React не являются «чистыми». Используйте React.memo, чтобы предотвратить лишние ре-рендеры, и useMemo, useCallback для мемоизации пропсов.

- Используйте useSyncExternalStore для подписки на изменения внешнего состояния. Из-за конкурентного рендеринга разные части приложения могут не соответствовать глобальному состоянию. Если вы используете внешнее состояние напрямую, то могут возникнуть нежелательные сбои, поэтому используйте хук useSyncExternalStore. Например, в Wouter хуки useBrowserLocation, useHashLocation, useSearch используют useSyncExternalStore.

https://molefrog.com/notes/react-tricks
👍111
Оптимизация производительности в React

Обзорная статья на Sitepoint, в которой рассказали, как делать оптимизацию производительности в React. В ней описано какие инструменты использовать для поиска узких мест, как использовать мемоизацию, ленивую загрузку, виртуализацию и другие способы оптимизации. Кратко о чем пишет автор:

- Узкое место в приложении может замедлить работу всей системы, и, чтобы найти его, есть несколько инструментов: React Developer Tools, Chrome DevTools -> вкладка Performance, React Profiler API.

- Один из вариантов ускорения работы медленного компонента – мемоизация React.memo.

- Обращайте внимание на локальный стейт компонента. Минимизация изменения стейта предотвращает от лишних ре-рендеров. Убедитесь, что обновление стейта происходит только по необходимости.

- Используйте ленивую загрузку и разделение кода. Это ускорит загрузку сайта. Например, если только на одной странице используется большая библиотека, то не надо грузить код библиотеки в общий бандл, можно разделить код по страницам.

- Используйте виртуализацию для больших списков. Это сократит потребление памяти браузера.

https://www.sitepoint.com/react-performance-optimization/
👍9
Легкий способ исправить ошибки гидратации – React Hydration Overlay

В блоге Builder.io рассказали про библиотеку React Hydration Overlay, которая помогает исправить ошибки гидратации. Это библиотека пока что работает только для Next.js.

Сейчас в Next.js при возникновении ошибки гидратации появляется не очень информативное сообщение, в котором не совсем понятно в каком месте не совпадают элементы на клиенте и в SSR. Библиотека более точно показывает в каком месте есть несовпадение.

Работает она следующим образом: скрипт сохраняет HTML полученный с сервера и когда происходит ошибка гидратации сохраняет текущий HTML. После чего происходит сравнение двух HTML строк и поиск отличий. То что отличается от серверного HTML и есть причина ошибки гидратации, которую надо исправить.

https://www.builder.io/blog/announcing-react-hydration-overlay
👍12🔥72
Релиз React Aria 1.0

React Aria – библиотека компонентов от Adobe, более 40 компонентов, поддерживающих доступность и интернационализацию, а также кастомизацию стилей.

React Aria поддерживает доступный drag and drop, управление клавиатурой, встроенная валидация формы, изменение размеров колонок таблиц и многое другое.

Компоненты оптимизированы для взаимодействия на разных устройствах и поддерживают мышь, сенсорный экран, клавиатура и screen reader.

Компоненты в React Aria декомпозированы, поэтому можно самому настраивать нужный вид и компоновать их:

<ComboBox>
<Label>Permissions</Label>
<Group>
<Input />
<Button>▼</Button>
</Group>
<Popover>
<ListBox>
<ListBoxItem>Read Only</ListBoxItem>
<ListBoxItem>Edit</ListBoxItem>
<ListBoxItem>Admin</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>


https://react-spectrum.adobe.com/react-aria/index.html
👍20
Конкурентный React, внешний стор и «разрыв» стейта

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

«Разрыв» стейта возникает, когда два и более компонентов рендерят разный UI от одного и того же источника данных.

Библиотеки, использующие внешний стор, дают пользователю точечную реактивность для предотвращения лишних ререндеров, но не поддерживают конкурентный рендеринг, чтобы избежать «разрыва» состояния.

Библиотеки, использующие стор в виде контекста, поддерживают конкурентный рендеринг, но не дают точечную реактивность из коробки – приходится использовать мемоизацию.

И есть библиотеки, которые поддерживают и конкурентный рендеринг и точечную реактивность, например Jotai, но страдают от краткосрочных «разрывов» стейта пока происходит реконсиляция.

Для решения проблемы «разрыва» стейта при доступе к внешнему стору можно использовать хук useSyncExternalStore. С помощью него можно получить доступ к внешнему стейту и использовать конкурентный рендеринг.

https://interbolt.org/blog/react-ui-tearing/
👍9🔥2
Puck: self-hosted drag-and-drop редактор лендинга на React

Puck - это модульный, с открытым исходным кодом, визуальный редактор лендинга на React. Аналог Tilda, компоненты которого можно настроить под себя и опубликовать на своем сервере без вендор-лока. Т.к. это React, то можно использовать Next.js для рендера на сервере.

Можно самостоятельно настроить вид компонентов, список полей и их тип. Пример:

const config = {
components: {
HeadingBlock: {
fields: {
noscript: {
type: "text",
},
},
defaultProps: {
noscript: "Hello, world",
},
render: ({ noscript }) => {
return <h1>{noscript}</h1>;
},
},
},
};


Puck дает два основных компонента: редактор и рендер страницы. Редактор принимает конфиг компонентов, данные по умолчанию и колбек для сохранения изменений, пример:

// Данные по умолчанию
const initialData = {
content: [],
root: {},
};

// Измененные данные в виде JSON
const save = (data) => {};

// Рендер редактора
export function Editor() {
return <Puck config={config} data={initialData} onPublish={save} />;
}


Для рендера страницы надо передать конфиг компонентов и данные:

export function Page() {
return <Render config={config} data={data} />;
}


https://puckeditor.com/
👍15🔥2
Серверные компоненты React: хороший, плохой и уродливый

Обзор серверных компонентов React: что кажется удобным и хорошим, а что не очень.
С помощью серверных компонентов можно получать данные и рендерить UI на сервере:

export default async function Page() {
const stuff = await fetch(/* … */);
return <div>{stuff}</div>;
}

Еще удобно сразу обрабатывать данные формы:

<form
action={async (formData) => {
"use server";
const email = formData.get("email");
await db.emails.insert({ email });
}}
>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" />
<button>Send me spam</button>
</form>

Из-за прогрессивного улучшения эта форма будет работать даже если JS будет выключен. Однако, если потребуется дополнительно показывать состояние загрузки формы или ошибки отправки формы, то код усложнится: придется добавить хуки useFormState и useFormStatus. Пример:

"use client";
const [formState, formAction] = useFormState(saveEmailAction);

<form action={formAction}>
<!-- snip -->
</form>

Такой подход уже не так удобен, т.к. близкий код уже не располагается рядом.

Еще одним минусом серверных компонент стал манки-патчинг fetch API в Next.js:
- по умолчанию все запросы кэшируются.
- нет доступа к объекту Request вне мидлвара.

Также, с серверными компонентами вырос размер JS бандла. В Next.js 12 базовый бандл весил ~70КБ, сейчас Next.js 14 весит 85-90КБ в сжатом виде. Не в сжатом виде, бандл весит 300КБ.

https://www.mayank.co/blog/react-server-components
👍82
Углубленный взгляд на ReactDOM.flushSync и для чего он нужен

flushSync – простое API чтобы заставить React выполнить всю ожидающую работу и синхронно обновить DOM. Ожидающая работа – изменение стейта в компонентах.

В основном, flushSync используют для отмены батчинга изменения стейта в колбеках, пример:

function Demo() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);

function handleClick() {
flushSync(() => {
setCount((c) => c + 1);
});
flushSync(() => {
setFlag((f) => !f);
});
}

{/* --snip-- */}
}


При вызове колбека handleClick, каждое выполнение flushSync заставить React перерендерить компонент и синхронно обновить DOM, вместо одного раза в конце колбека.

Также, flushSync используют, когда после изменения стейта нужно сразу обратиться в DOM, например для измерения DOM узла. Еще пример использования – скролл к элементу сразу после обновления стейта.

https://julesblom.com/writing/flushsync
👍202
Селектор контекста через React.use и React.useMemo

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

Для оптимизации контекста можно использовать библиотеку use-context-selector. Библиотека предоставляет селектор для контекста и ограничивает ререндеры компонента только на изменение выбранных в селекторе значений. Пример:

import { createContext, useContextSelector } from 'use-context-selector';

const context = createContext(null);

const Counter1 = () => {
const count1 = useContextSelector(context, v => v[0].count1);
const setState = useContextSelector(context, v => v[1]);
const increment = () => setState(s => ({
...s,
count1: s.count1 + 1,
}));

{/* --snip-- */}
};

const StateProvider = ({ children }) => (
<context.Provider value={useState({ count1: 0, count2: 0 })}>
{children}
</context.Provider>
);


Если библиотека use-context-selector не подходит, то скоро появится возможность оптимизировать контекст через useMemo и функцию use. Пример:

import { createContext, useMemo, use } from 'react';

const MyContext = createContext({
foo: '',
bar: 0,
});

const useFoo = () => {
return useMemo(() => {
const { foo } = use(MyContext);
return foo;
}, []);
};

На данный момент функция use доступна в canary версии React. Как видно по примеру, в useMemo не надо ничего передавать в массив зависимостей, React сам поймет когда нужно произвести ререндер когда foo изменится.

https://interbolt.org/blog/react-use-selector-optimization/
👍17👎3
Продвинутые фичи Next.js

Подборка менее известных фич в Next.js, о которых полезно знать. Краткий список:

- Автоматическая типизация <Link />:

const nextConfig = {
experimental: {
typedRoutes: true,
},
}

В настройках можно включить типизацию ссылок в компоненте <Link />. Сгенерится d.ts файл, в котором будут типы созданных роутов, и можно будет безопасно использовать href в <Link />.

- Встроенные модули для сторонних скриптов Google. В Next.js есть встроенные компоненты для распространенных скриптов Google в @next/third-parties/google: GoogleTagManager, GoogleAnalytics, GoogleMapsEmbed, YouTubeEmbed.

- Простая генерация sitemap.xml. Нужно создать страницу sitemap.ts и вернуть в нем массив метаданных о страницах:

import { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://acme.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
}
]
}

- Если в src расположить файл instrumentation.ts, то Next.js будет запускать экспортируемую функцию register при запуске сервера. Может пригодиться, если надо запускать внешний код при запуске приложения.

- generateViewport (app router) для генерации мета-тегов в серверных компонентах:

export function generateViewport({ params }) {
return {
themeColor: 'black',
}

/*
<meta name="theme-color" content="black" />
*/
}

https://codedrivendevelopment.com/posts/rarely-known-nextjs-features
👍14👎1
Настройка отчетов о «мягкой» навигации в React

Отчет о «мягкой» навигации – это экспериментальная фича Google Web Vitals. С ее помощью можно измерять Core Web Vitals и другие метрики для динамических URL в SPA. «Мягкая» навигация происходит при изменении URL с помощью JavaScript, что типично для SPA приложений.

Что нужно сделать, чтобы подключить отчеты в приложении:

1) Включить экспериментальную фичу chrome://flags/#enable-experimental-web-platform-features.

2) Установить web-vitals с «мягкой» навигацией npm i web-vitals@3.5.0-soft-navs-10/

3) Создать функцию для отчетов в src/reportWebVitals.js и вызвать ее в src/index.js:


const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ onTTFB, onFCP, onLCP, onCLS, onFID, onINP }) => {
onTTFB(onPerfEntry, {reportSoftNavs: true});
onFCP(onPerfEntry, {reportSoftNavs: true});
onLCP(onPerfEntry, {reportSoftNavs: true});
onCLS(onPerfEntry, {reportSoftNavs: true});
onFID(onPerfEntry, {reportSoftNavs: true});
onINP(onPerfEntry, {reportSoftNavs: true});
});
}
};

export default reportWebVitals;


https://www.debugbear.com/blog/soft-navigations-react-tutorial
👍5🔥1