Селектор контекста через React.use и React.useMemo
Использование контекста в качестве глобального стейта приложения ухудшает производительность приложения, т.к. изменение значения контекста приведет к ререндеру всех компонентов, которые использует данный контекст.
Для оптимизации контекста можно использовать библиотеку use-context-selector. Библиотека предоставляет селектор для контекста и ограничивает ререндеры компонента только на изменение выбранных в селекторе значений. Пример:
Если библиотека use-context-selector не подходит, то скоро появится возможность оптимизировать контекст через useMemo и функцию use. Пример:
На данный момент функция use доступна в canary версии React. Как видно по примеру, в useMemo не надо ничего передавать в массив зависимостей, React сам поймет когда нужно произвести ререндер когда foo изменится.
https://interbolt.org/blog/react-use-selector-optimization/
Использование контекста в качестве глобального стейта приложения ухудшает производительность приложения, т.к. изменение значения контекста приведет к ререндеру всех компонентов, которые использует данный контекст.
Для оптимизации контекста можно использовать библиотеку 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/
InterBolt
The future of React.use and React.useMemo - a powerful alternative to context selectors
use-context-selector is my preferred way to prevent re-renders when using React contexts. But some future React updates provide a better alternative using only React.use and React.useMemo. Follow along as I learn a powerful lesson in my unsuccessfully attempt…
👍17👎3
Продвинутые фичи Next.js
Подборка менее известных фич в Next.js, о которых полезно знать. Краткий список:
- Автоматическая типизация <Link />:
В настройках можно включить типизацию ссылок в компоненте <Link />. Сгенерится d.ts файл, в котором будут типы созданных роутов, и можно будет безопасно использовать href в <Link />.
- Встроенные модули для сторонних скриптов Google. В Next.js есть встроенные компоненты для распространенных скриптов Google в
- Простая генерация sitemap.xml. Нужно создать страницу sitemap.ts и вернуть в нем массив метаданных о страницах:
- Если в src расположить файл instrumentation.ts, то Next.js будет запускать экспортируемую функцию register при запуске сервера. Может пригодиться, если надо запускать внешний код при запуске приложения.
- generateViewport (app router) для генерации мета-тегов в серверных компонентах:
https://codedrivendevelopment.com/posts/rarely-known-nextjs-features
Подборка менее известных фич в 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) Включить экспериментальную фичу
2) Установить web-vitals с «мягкой» навигацией
3) Создать функцию для отчетов в src/reportWebVitals.js и вызвать ее в src/index.js:
https://www.debugbear.com/blog/soft-navigations-react-tutorial
Отчет о «мягкой» навигации – это экспериментальная фича 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
Debugbear
How to Set Up Soft Navigation Reporting in a React App | DebugBear
In this tutorial, we look into how you can report Core Web Vitals for soft navigations from a React application to the Chrome DevTools Console.
👍5🔥1
Релиз Glide Data Grid 6.0
Вышел релиз Glide Data Grid 6.0 – датагрида на канвасе. Основные новые фичи:
- Экспериментальная поддержка кинетического скролла на iOS.
- Улучшение производительности при обновлении большего количества данных за раз.
- Улучшение ячейки UriCell – подчеркивание ссылки при наведении.
- Показ скелетонов в ячейках.
Основное преимущество Glide Data Grid – возможность отрендерить миллион строк, используя ленивый рендеринг. Также для сохранения батареи устройства используется нативный скроллинг.
Т.к. датагрид отображается на канвасе, то есть ограничение в отображаемых данных. Из коробки библиотека поддерживает основные типы ячеек, но если понадобится сделать кастомный тип ячейки, то это будет сложнее, чем для HTML датагридов.
Базовый пример как использовать Glide Data Grid:
https://grid.glideapps.com/
Вышел релиз Glide Data Grid 6.0 – датагрида на канвасе. Основные новые фичи:
- Экспериментальная поддержка кинетического скролла на iOS.
- Улучшение производительности при обновлении большего количества данных за раз.
- Улучшение ячейки UriCell – подчеркивание ссылки при наведении.
- Показ скелетонов в ячейках.
Основное преимущество Glide Data Grid – возможность отрендерить миллион строк, используя ленивый рендеринг. Также для сохранения батареи устройства используется нативный скроллинг.
Т.к. датагрид отображается на канвасе, то есть ограничение в отображаемых данных. Из коробки библиотека поддерживает основные типы ячеек, но если понадобится сделать кастомный тип ячейки, то это будет сложнее, чем для HTML датагридов.
Базовый пример как использовать Glide Data Grid:
const columns: GridColumn[] = [
{ noscript: "First Name", width: 100 },
{ noscript: "Last Name", width: 100 }
];
function getData([col, row]: Item): GridCell {
const person = data[row];
if (col === 0) {
return {
kind: GridCellKind.Text,
data: person.firstName,
allowOverlay: false,
displayData: person.firstName
};
} else if (col === 1) {
return {
kind: GridCellKind.Text,
data: person.lastName,
allowOverlay: false,
displayData: person.lastName
};
} else {
throw new Error();
}
}
export default function App() {
return (
<DataEditor columns={columns} getCellContent={getData} rows={data.length} />
);
}
https://grid.glideapps.com/
Glideapps
glide-data-grid
A no-compromise, outrageously fast react data grid, with rich rendering and TypeScript support.
👍9👎1
Гайд по композиции серверных компонентов
Композиция позволяет очень гибко переиспользовать компоненты и настраивать их внешний вид. Пример декомпозированного компонента карточки, которая может менять вид в зависимости от дочерних компонентов:
При композиции серверных и клиентских компонентов есть несколько особенностей, которые стоит учитывать:
- Возможна передача только отрендеренных компонентов при передаче серверных компонентов в клиентский:
- Нельзя передавать пропсы через React.cloneElement в клиентских компонентах, которые принимают серверные. Связано это с тем, что серверные компоненты рендерятся перед клиентскими, поэтому передача пропсов через React.cloneElement не сработает.
- Не рекомендуется использовать серверные компоненты в render props. Пример того, как делать не надо:
Каждый ререндер ClientCard вызовет render prop renderFooter, который спровоцирует сетевой запрос на сервер за обновленным состоянием серверного компонента <ServerFooter />.
https://frontendatscale.com/blog/donut-components/
Композиция позволяет очень гибко переиспользовать компоненты и настраивать их внешний вид. Пример декомпозированного компонента карточки, которая может менять вид в зависимости от дочерних компонентов:
function Page() {
return (
<Card>
<Details city={...} />
<Tags tags={...} />
<Footer>
<AddToFavorites />
<Button />
</Footer>
</Card>
);
}
При композиции серверных и клиентских компонентов есть несколько особенностей, которые стоит учитывать:
- Возможна передача только отрендеренных компонентов при передаче серверных компонентов в клиентский:
// Так можно
<ClientCard footer={<ServerFooter />} />
// Так нельзя
<ClientCard Footer={ServerFooter} />
- Нельзя передавать пропсы через React.cloneElement в клиентских компонентах, которые принимают серверные. Связано это с тем, что серверные компоненты рендерятся перед клиентскими, поэтому передача пропсов через React.cloneElement не сработает.
- Не рекомендуется использовать серверные компоненты в render props. Пример того, как делать не надо:
<ClientCard
renderFooter={async (clientProps) => {
"use server";
return <ServerFooter {...clientProps} />;
}}
/>
function ClientCard({ renderFooter }) {
const [count, setCount] = useState(0);
return (
<div>
...
<Suspense>{renderFooter({ count })}</Suspense>
</div>
);
}
Каждый ререндер ClientCard вызовет render prop renderFooter, который спровоцирует сетевой запрос на сервер за обновленным состоянием серверного компонента <ServerFooter />.
https://frontendatscale.com/blog/donut-components/
Frontendatscale
Delicious Donut Components | Frontend at Scale
An interactive guide to component composition with React Server Components
👍8❤1
Запуск локального React приложения в Android и iOS приложения
Гайд по запуску локального React приложения на мобильном телефоне, без доступа к интернету и запросов на сервер. В туториале по шагам описано, как забандлить React приложение на Vite в Anrdoid/iOS приложение и отобразить его через WebView.
Android: https://ditto.live/blog/run-react-locally-in-android
iOS: https://ditto.live/blog/run-react-locally-in-ios-app
Гайд по запуску локального React приложения на мобильном телефоне, без доступа к интернету и запросов на сервер. В туториале по шагам описано, как забандлить React приложение на Vite в Anrdoid/iOS приложение и отобразить его через WebView.
Android: https://ditto.live/blog/run-react-locally-in-android
iOS: https://ditto.live/blog/run-react-locally-in-ios-app
👍9❤3
Новые хуки в React 19
Помимо развития серверных компонентов в React добавляются новые хуки для получения данных и для работы с формой. Кратко о них:
- use(Promise). Хук для получения данных. Теперь при использовании fetch не надо дополнительно использовать useEffect. Достаточно хука use, пример:
Вместе с хуком use можно использовать Suspense для показа состояния загрузки. Также в отличие от других хуков, хук use можно использовать внутри циклов и условных операторов.
- use(Context). Хук для чтения контекста. Также можно использовать внутри циклов и условных операторов. Можно оптимизировать получение значения контекста, используя хук useMemo:
- action в форме принимает асинхронную функцию:
- useFormState – хук для помощи при работе с action у формы. Принимает action и дефолтный стейт формы, возвращает текущий стейт формы и action для передачи в форму:
- useFormStatus – хук, который возвращает текущее состояние формы. Используется в дочерних компонентах внутри формы:
https://marmelab.com/blog/2024/01/23/react-19-new-hooks.html
Помимо развития серверных компонентов в React добавляются новые хуки для получения данных и для работы с формой. Кратко о них:
- use(Promise). Хук для получения данных. Теперь при использовании fetch не надо дополнительно использовать useEffect. Достаточно хука use, пример:
import { use } from 'react';
function MessageComponent() {
const message = use(fetch('…'));
// ...
}
Вместе с хуком use можно использовать Suspense для показа состояния загрузки. Также в отличие от других хуков, хук use можно использовать внутри циклов и условных операторов.
- use(Context). Хук для чтения контекста. Также можно использовать внутри циклов и условных операторов. Можно оптимизировать получение значения контекста, используя хук useMemo:
import { use } from 'react';
function HorizontalRule({ show }) {
if (show) {
const theme = useMemo(() => use(MyContext).theme, []);
return <hr className={theme} />;
}
return false;
}
- action в форме принимает асинхронную функцию:
const AddToCartForm = () => {
const formAction = async (formData) => {
await addToCart(formData, noscript);
};
return (
<form action={formAction}>
{/* --snip-- */}
</form>
);
};
- useFormState – хук для помощи при работе с action у формы. Принимает action и дефолтный стейт формы, возвращает текущий стейт формы и action для передачи в форму:
import { useFormState } from 'react-dom';
import { action } from './action';
function MyComponent() {
const [state, formAction] = useFormState(action, null);
// ...
return <form action={formAction}>{/* ... */}</form>;
}
- useFormStatus – хук, который возвращает текущее состояние формы. Используется в дочерних компонентах внутри формы:
const { pending, data, method, action } = useFormStatus();
https://marmelab.com/blog/2024/01/23/react-19-new-hooks.html
Marmelab
New client-side hooks coming to React 19
Data fetching and form handling are about to get easier in React, and not just in SSR apps.
👍22👎10❤6🔥3
Тип кортежей
В своем блоге Кайл Шевлин описал проблему типизации кортежей, возвращаемых из функции, пример:
Если посмотреть на тип result, то можно увидеть, что это массив, состоящий из объединения boolean и типа объекта handlers. По сути, TypeScript не знает индекс наших значений в массиве.
Чтобы решить эту проблему, можно указать возвращаемый тип у функции useBool, но более правильно будет добавить as const к возвращаемому кортежу:
Это сообщит TypeScript, что возвращаемый массив будет readonly и никогда не изменится. Поэтому TypeScript корректно определит типы state и handlers в местах использования.
https://kyleshevlin.com/wrangling-tuple-types/
В своем блоге Кайл Шевлин описал проблему типизации кортежей, возвращаемых из функции, пример:
function useBool(initialValue = false) {
const [state, setState] = React.useState(initialValue)
const handlers = React.useMemo(
() => ({
/* --snip-- */
}),
[initialValue],
)
return [state, handlers]
}
/**
const result: (
| boolean
| { /* --snip-- */ }
)[]
*/
const result = useBool()
Если посмотреть на тип result, то можно увидеть, что это массив, состоящий из объединения boolean и типа объекта handlers. По сути, TypeScript не знает индекс наших значений в массиве.
Чтобы решить эту проблему, можно указать возвращаемый тип у функции useBool, но более правильно будет добавить as const к возвращаемому кортежу:
function useBool(initialValue = false) {
/* --snip-- */
return [state, handlers] as const
}
Это сообщит TypeScript, что возвращаемый массив будет readonly и никогда не изменится. Поэтому TypeScript корректно определит типы state и handlers в местах использования.
https://kyleshevlin.com/wrangling-tuple-types/
Kyle Shevlin's Blog
Wrangling Tuple Types | Kyle Shevlin
TypeScript doesn't know whether you're returning an array or a tuple from a function, so let's learn a couple ways we can fix that problem.
👍18❤1👎1
Как начинать проект на React в 2024 году
В своем блоге Робин Вирух сделал ежегодный обзор как начинать новый проект на React в 2024 году. Вот его предложения:
- React и Vite. Если вы раньше использовали Create React App, то сейчас лучше перейти на Vite. Он быстрее, более активно поддерживается и схож с CRA. В нем также нет какого-то фреймворка, на основе которого строится приложение.
- Next.js или Remix. Если нужен готовый фреймворк с SSR/SSG. Под капотом много готовых решений для облегчения разработки, и можно легко добиться высоких показателей производительности, если все правильно делать.
- Astro. Фреймворк для создания MPA сайтов. Этот случай подходит, если делаете сайт с упором на контент: блог, портфолио, лендинги. Ориентирован на рендер на сервере и по умолчанию не поставляет JS в браузер.
https://www.robinwieruch.de/react-starter/
В своем блоге Робин Вирух сделал ежегодный обзор как начинать новый проект на React в 2024 году. Вот его предложения:
- React и Vite. Если вы раньше использовали Create React App, то сейчас лучше перейти на Vite. Он быстрее, более активно поддерживается и схож с CRA. В нем также нет какого-то фреймворка, на основе которого строится приложение.
- Next.js или Remix. Если нужен готовый фреймворк с SSR/SSG. Под капотом много готовых решений для облегчения разработки, и можно легко добиться высоких показателей производительности, если все правильно делать.
- Astro. Фреймворк для создания MPA сайтов. Этот случай подходит, если делаете сайт с упором на контент: блог, портфолио, лендинги. Ориентирован на рендер на сервере и по умолчанию не поставляет JS в браузер.
https://www.robinwieruch.de/react-starter/
www.robinwieruch.de
How to start a React Project [2025]
React starter kits (Vite, Next, Astro, Remix, React Router) which help React developers to start a React project in 2025 ...
👍12👎4❤1
Мысли про Remix
Возможности React растут, а технические подходы к созданию UI стали более сложными, появляются новые «мета-фреймворки», которые позволяют быстрее создавать более производительные и масштабируемые приложения, прилагая меньшие усилия к разработке. На данный момент есть два популярных «мета-фреймворка» – Next.js и Remix.
Соломон Хоук поделился своими мыслями про Remix. Что нравится в Remix:
- Хорошая документация, в которой легко ориентироваться, есть гайды и темам и справочник API.
- Изучение Remix связано с пониманием роутинга, загрузкой данных и изучением соглашений, принятых в Remix: наименование файла, название экспортируемых функций и констант.
- В Remix уделяется внимание стандартам веб API, например используется Response и FormData.
- Для большинства рабочих кейсов не надо устанавливать стейт-менеджер и библиотеку для получения данных (типа react-query). Хватает встроенного API, например loader для получения данных.
Также автор описал вещи, которые не нравятся в Remix:
- Remix использует esbuild, но не дает доступа к его конфигурации.
- Получение данных связано с роутом через loader. Это может стать проблемой, если хотите получать данные на уровне компонента. Как вариант, использовать react-query.
- Проблема с ESM модулями. Remix бандлит сервер в формате CommonJS и по умолчанию не бандлит зависимые модули.
https://www.viget.com/articles/thoughts-on-remix/
Возможности React растут, а технические подходы к созданию UI стали более сложными, появляются новые «мета-фреймворки», которые позволяют быстрее создавать более производительные и масштабируемые приложения, прилагая меньшие усилия к разработке. На данный момент есть два популярных «мета-фреймворка» – Next.js и Remix.
Соломон Хоук поделился своими мыслями про Remix. Что нравится в Remix:
- Хорошая документация, в которой легко ориентироваться, есть гайды и темам и справочник API.
- Изучение Remix связано с пониманием роутинга, загрузкой данных и изучением соглашений, принятых в Remix: наименование файла, название экспортируемых функций и констант.
- В Remix уделяется внимание стандартам веб API, например используется Response и FormData.
- Для большинства рабочих кейсов не надо устанавливать стейт-менеджер и библиотеку для получения данных (типа react-query). Хватает встроенного API, например loader для получения данных.
Также автор описал вещи, которые не нравятся в Remix:
- Remix использует esbuild, но не дает доступа к его конфигурации.
- Получение данных связано с роутом через loader. Это может стать проблемой, если хотите получать данные на уровне компонента. Как вариант, использовать react-query.
- Проблема с ESM модулями. Remix бандлит сервер в формате CommonJS и по умолчанию не бандлит зависимые модули.
https://www.viget.com/articles/thoughts-on-remix/
https://www.viget.com
Thoughts on Remix | Viget
Reflections on Remix by seasoned React developers. TL;DR: it good.
👍9
Улучшаем типизацию роутинга в Next.js
В Next.js есть экспериментальная фича для генерации типов роутов:
Однако, этот способ не всегда применим, он позволяет только проверить правильность написания пути роута в href у Link:
У typedRoutes есть несколько ограничений:
- нет валидации типа query params и проверок в runtime.
- нет подсказок для динамических роутов.
- нет валидации типа при передаче href как объекта <Link href={{href: '/about', query: {ref: 'hello'}}} />
- нет валидации типа для useParams() и useSearchParams()
Чтобы обеспечить валидацию типа и проверок в runtime, автор статьи предлагает использовать Zod и создал утилитарную функцию makeRoute. Пример как используется makeRoute:
Динамические роуты:
useParams с типизацией:
Query параметры:
https://www.flightcontrol.dev/blog/fix-nextjs-routing-to-have-full-type-safety
В Next.js есть экспериментальная фича для генерации типов роутов:
const nextConfig = {
experimental: {
typedRoutes: true,
},
}
Однако, этот способ не всегда применим, он позволяет только проверить правильность написания пути роута в href у Link:
// Валидно
<Link href="/about" />
// Ошибка
<Link href="/aboot" />
У typedRoutes есть несколько ограничений:
- нет валидации типа query params и проверок в runtime.
- нет подсказок для динамических роутов.
- нет валидации типа при передаче href как объекта <Link href={{href: '/about', query: {ref: 'hello'}}} />
- нет валидации типа для useParams() и useSearchParams()
Чтобы обеспечить валидацию типа и проверок в runtime, автор статьи предлагает использовать Zod и создал утилитарную функцию makeRoute. Пример как используется makeRoute:
Динамические роуты:
export const OrgParams = z.object({orgId: z.string()})
export const Routes = {
home: makeRoute(({orgId}) => `/org/${orgId}`, OrgParams)
}
…
<Link href={Routes.home({orgId: 'g4eion3e3'})} />
useParams с типизацией:
// type = {orgId: string}
const params = Routes.home.useParams()
Query параметры:
export const SignupSearchParams = z.object({
invitationId: z.string().optional().nullable(),
})
export const Routes = {
signup: makeRoute(() => "/signup", z.object({}), SignupSearchParams),
}
…
<Link href={Routes.signup({}, {search: {invitationId: '8haf3dx'}})} />
https://www.flightcontrol.dev/blog/fix-nextjs-routing-to-have-full-type-safety
Flightcontrol
Fix Next.js routing to have full type-safety
Next.js has poor built in routing type safety, but here's how to fix it once and for all.
👍9❤1
Использование Intersection Observer в React
react-intersection-observer – библиотека для использования Intersection Observer в React. Можно использовать через хук useInView и через компонент InView:
Поддерживаются параметры Intersection Observer v2: trackVisibility и delay.
В репозитории есть рецепты готовых сценариев использования: ленивая загрузка изображений, триггер анимации и отслеживание показов.
https://github.com/thebuilder/react-intersection-observer
react-intersection-observer – библиотека для использования Intersection Observer в React. Можно использовать через хук useInView и через компонент InView:
const Component = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
});
return (
<div ref={ref}>
<h2>{`Header inside viewport ${inView}.`}</h2>
</div>
);
};
Поддерживаются параметры Intersection Observer v2: trackVisibility и delay.
В репозитории есть рецепты готовых сценариев использования: ленивая загрузка изображений, триггер анимации и отслеживание показов.
https://github.com/thebuilder/react-intersection-observer
GitHub
GitHub - thebuilder/react-intersection-observer: React implementation of the Intersection Observer API to tell you when an element…
React implementation of the Intersection Observer API to tell you when an element enters or leaves the viewport. - thebuilder/react-intersection-observer
👍18
Релиз Storybook 8 Beta
Вышел релиз Storybook 8 Beta. Какие он принес изменения:
- Значительное улучшение производительности. Поменяли компилятор, вместо Babel теперь SWC. Для проектов на React автоматически генерируются контролы с помощью react-docgen, это ускорило время запуска на 20%.
- Обновили UI на мобильных устройствах. Теперь на мобильных устройствах боковая панель будет появляться снизу.
- Улучшили утилиты для тестирования. Объединили
- Поддержка React Server Components. Пока что в историях можно использовать RSC только в Next.js проектах.
- Обновили версии основных зависимостей. Для Storybook 8 потребуется Node.js 18+, Vite 5 и Lit 3 версии.
https://storybook.js.org/blog/storybook-8-beta/
Вышел релиз Storybook 8 Beta. Какие он принес изменения:
- Значительное улучшение производительности. Поменяли компилятор, вместо Babel теперь SWC. Для проектов на React автоматически генерируются контролы с помощью react-docgen, это ускорило время запуска на 20%.
- Обновили UI на мобильных устройствах. Теперь на мобильных устройствах боковая панель будет появляться снизу.
- Улучшили утилиты для тестирования. Объединили
@storybook/jest и @storybook/testing-library в один модуль @storybook/test, который основан на Vitest. - Поддержка React Server Components. Пока что в историях можно использовать RSC только в Next.js проектах.
- Обновили версии основных зависимостей. Для Storybook 8 потребуется Node.js 18+, Vite 5 и Lit 3 версии.
https://storybook.js.org/blog/storybook-8-beta/
Storybook Blog
Storybook 8 Beta
Major compatibility & performance improvements
👍15👎1
LLRT – среда выполнения от AWS
LLRT – облегченная среда выполнения JS от AWS. LLRT обеспечивает в 10 раз более быстрый запуск приложения и в 2 раза более низкую общую стоимость по сравнению с другими средами, работающими в AWS Lambda. По времени запуска LLRT быстрее Node.js, Bun или Deno.
LLRT написан на Rust и использует QuickJS в качестве JS движка. Хоть LLRT и не позиционирует себя в качестве замены Node.js, его API частично совместимо с API Node.js.
На данный момент вместе с LLRT есть 14 известных мне сред выполнения JS:
- Node.js (V8)
- Deno (V8)
- Bun (JavaScript core - WebKit)
- Workerd (Cloudflare, V8)
- LLRT (AWS, V8)
- WinterJS (Wasmer, Spider Money)
- Edge Runtime (Vercel, V8)
- Lagon (Vercel Acquired, V8)
- Compute @edge (Fastly, Spider Monkey)
- Edge Compute (Alibaba V8)
- Hermes (Facebook)
- Bloomberg (v8, Internal)
- Bytedance (v8, Internal)
- txiki (QuickJS)
https://github.com/awslabs/llrt
LLRT – облегченная среда выполнения JS от AWS. LLRT обеспечивает в 10 раз более быстрый запуск приложения и в 2 раза более низкую общую стоимость по сравнению с другими средами, работающими в AWS Lambda. По времени запуска LLRT быстрее Node.js, Bun или Deno.
LLRT написан на Rust и использует QuickJS в качестве JS движка. Хоть LLRT и не позиционирует себя в качестве замены Node.js, его API частично совместимо с API Node.js.
На данный момент вместе с LLRT есть 14 известных мне сред выполнения JS:
- Node.js (V8)
- Deno (V8)
- Bun (JavaScript core - WebKit)
- Workerd (Cloudflare, V8)
- LLRT (AWS, V8)
- WinterJS (Wasmer, Spider Money)
- Edge Runtime (Vercel, V8)
- Lagon (Vercel Acquired, V8)
- Compute @edge (Fastly, Spider Monkey)
- Edge Compute (Alibaba V8)
- Hermes (Facebook)
- Bloomberg (v8, Internal)
- Bytedance (v8, Internal)
- txiki (QuickJS)
https://github.com/awslabs/llrt
GitHub
GitHub - awslabs/llrt: LLRT (Low Latency Runtime) is an experimental, lightweight JavaScript runtime designed to address the growing…
LLRT (Low Latency Runtime) is an experimental, lightweight JavaScript runtime designed to address the growing demand for fast and efficient Serverless applications. - awslabs/llrt
👍11🔥2❤1
Релиз Million.js 3
Вышел релиз Million.js 3. Подробнее что это написано здесь.
Какие изменения в новой версии:
- Улучшенная производительность. Для SSR приложений основной проблемой производительности была гидратация, ее сложность была O(n), где n – количество DOM узлов. В Million.js 3 уменьшили сложность гидратации до O(d), где d – количество динамических DOM узлов, d ≤ n:
- Стабильность. Переписали компилятор для лучшей поддержки TypeScript, множественных возвратов, условных операторов и для обработки вложенных React компонентов.
https://million.dev/blog/million-3
Вышел релиз Million.js 3. Подробнее что это написано здесь.
Какие изменения в новой версии:
- Улучшенная производительность. Для SSR приложений основной проблемой производительности была гидратация, ее сложность была O(n), где n – количество DOM узлов. В Million.js 3 уменьшили сложность гидратации до O(d), где d – количество динамических DOM узлов, d ≤ n:
// Million.js 3 гидратирует только handleClick и count
<div>
<h1>Hello, world!</h1>
<button onClick={handleClick}>{count}</button>
</div>
- Стабильность. Переписали компилятор для лучшей поддержки TypeScript, множественных возвратов, условных операторов и для обработки вложенных React компонентов.
https://million.dev/blog/million-3
Telegram
Заметки про React
Million.js: замена VDOM для React
Million.js – это замена VDOM для React, которая увеличивает скорость работы приложения в несколько раз. Библиотека использует подход Block Virtual DOM, который по другому определяет различия и его работу можно разбить на…
Million.js – это замена VDOM для React, которая увеличивает скорость работы приложения в несколько раз. Библиотека использует подход Block Virtual DOM, который по другому определяет различия и его работу можно разбить на…
👍7👎2
Над чем работают в React Labs
В блоге React вышел новый пост о статусе работы над текущими проектами. Кратко, о чем написали:
- React Compiler готовится к релизу и уже успешно используется в проде на некоторых проектах. Компилятор будет автоматически оптимизировать код приложения, чтобы изменение стейта внутри компонента не повлекло за собой лишние ре-рендеры дочерних компонентов.
Компилятор будет оптимизировать только те компоненты, которые подходят под определенные условия. Одно из таких условий – идемпотентность компонента, т.е. всегда возвращает одинаковый результат при одном и том же наборе пропсов.
- Actions. Раньше предполагалось делать Actions, которые выполняют код на сервере – Server Actions. Сейчас API расширили и он стал называться просто Actions, поддерживая выполнение действия и на клиенте:
Функцию в action можно будет определить как на сервере, используя директиву 'use server', так и на клиенте. При работе с action будут доступны хуки отслеживания состояния формы useFormStatus и useFormState.
- Директивы "use client" и "use server" будут определять «точки разделения» между окружениями. Директива "use client" будет сообщать бандлеру о необходимости генерации <noscript> (как в Astro Islands), а "use server" сообщит бандлеру о необходимости генерации POST endpoint (как в tRPC Mutations).
- Document Metadata. Появится встроенная поддержка для рендера <noscript>, <meta>, <link> в любом месте дерева компонентов, как в React Helmet.
- Загрузка ресурсов. Интеграция Suspense с жизненным циклом загрузки ресурсов, таких как стили, шрифты и скрипты.
https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024
В блоге React вышел новый пост о статусе работы над текущими проектами. Кратко, о чем написали:
- React Compiler готовится к релизу и уже успешно используется в проде на некоторых проектах. Компилятор будет автоматически оптимизировать код приложения, чтобы изменение стейта внутри компонента не повлекло за собой лишние ре-рендеры дочерних компонентов.
Компилятор будет оптимизировать только те компоненты, которые подходят под определенные условия. Одно из таких условий – идемпотентность компонента, т.е. всегда возвращает одинаковый результат при одном и том же наборе пропсов.
- Actions. Раньше предполагалось делать Actions, которые выполняют код на сервере – Server Actions. Сейчас API расширили и он стал называться просто Actions, поддерживая выполнение действия и на клиенте:
<form action={search}>
<input name="query" />
<button type="submit">Search</button>
</form>
Функцию в action можно будет определить как на сервере, используя директиву 'use server', так и на клиенте. При работе с action будут доступны хуки отслеживания состояния формы useFormStatus и useFormState.
- Директивы "use client" и "use server" будут определять «точки разделения» между окружениями. Директива "use client" будет сообщать бандлеру о необходимости генерации <noscript> (как в Astro Islands), а "use server" сообщит бандлеру о необходимости генерации POST endpoint (как в tRPC Mutations).
- Document Metadata. Появится встроенная поддержка для рендера <noscript>, <meta>, <link> в любом месте дерева компонентов, как в React Helmet.
- Загрузка ресурсов. Интеграция Suspense с жизненным циклом загрузки ресурсов, таких как стили, шрифты и скрипты.
https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024
react.dev
React Labs: What We've Been Working On – February 2024 – React
The library for web and native user interfaces
👍17❤1👎1
Анимация с учетом направления в Framer Motion
При добавлении анимации на сайт бывает нужно чтобы она двигалась в разных направлениях, в зависимости от контекста. Например, при навигации между экранами надо чтобы текущий экран уезжал влево, а новый экран выезжал справа. Когда возвращаешься назад, то анимация должна быть обратной.
Чтобы сделать такую анимацию можно использовать Framer Motion и создать переиспользуемый компонент, который можно использовать для разных ситуаций. Пример использования:
https://sinja.io/blog/direction-aware-animations-in-framer-motion
При добавлении анимации на сайт бывает нужно чтобы она двигалась в разных направлениях, в зависимости от контекста. Например, при навигации между экранами надо чтобы текущий экран уезжал влево, а новый экран выезжал справа. Когда возвращаешься назад, то анимация должна быть обратной.
Чтобы сделать такую анимацию можно использовать Framer Motion и создать переиспользуемый компонент, который можно использовать для разных ситуаций. Пример использования:
export const DirectionAwareAnimationsReusableSolution = () => {
const [product, setProduct] = useState<Product | null>(null);
const direction: Direction = product ? 'forward' : 'back';
return (<div>
<MotionConfig transition={{ type: 'spring', duration: 0.55 }}>
<AnimatePresenceWithDirection initial={false} mode="sync" direction={direction}>
{product
? <ProductDetails product={product} onBack={() => setProduct(null)} key="details" />
: <ProductsList openProduct={setProduct} key="list" />
}
</AnimatePresenceWithDirection>
</MotionConfig>
</div>);
};
export const ProductDetails = () => {
const animationProps = useDirectionAnimation();
return <motion.div {...animationProps}>
{/* ... */}
</motion.div>
});
https://sinja.io/blog/direction-aware-animations-in-framer-motion
sinja.io
Direction-aware animations in Framer Motion · OlegWock
Carousels, multistep forms and navigation between screens – they all profit from nice direction-aware animation. In this recipe, you'll learn how you can implement some.
👍5
Как использовать forwardRef с generic компонентами
Одно из ограничений forwardRef в том, что он отключает выведение типа для generic компонентов. Например:
Чтобы исправить выведение типа для generic компонентов, можно создать утилитарную функцию для вызова forwardRef с правильными типами:
https://www.totaltypenoscript.com/forwardref-with-generic-components
Одно из ограничений forwardRef в том, что он отключает выведение типа для generic компонентов. Например:
const Table = <T,>(
props: {
data: T[];
renderRow: (row: T) => React.ReactNode;
},
ref: React.ForwardedRef<HTMLTableElement>
) => {
/** --snip-- */
};
const ForwardReffedTable = React.forwardRef(Table);
<Table
data={["a", "b"]}
renderRow={(row) => { // Тип выводится: row: string
return <tr>{row}</tr>;
}}
/>;
<ForwardReffedTable
data={["a", "b"]}
renderRow={(row) => { // Тип не выводится: row: unknown
return <tr>{row}</tr>;
}}
/>;
Чтобы исправить выведение типа для generic компонентов, можно создать утилитарную функцию для вызова forwardRef с правильными типами:
function fixedForwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactNode
): (props: P & React.RefAttributes<T>) => React.ReactNode {
return React.forwardRef(render) as any;
}
// Вызов forwardRef с правильным выведением типа:
const ForwardReffedTable = fixedForwardRef(Table);
https://www.totaltypenoscript.com/forwardref-with-generic-components
Total TypeScript
How To Use forwardRef With Generic Components
Learn about the limitations of React's `forwardRef` TypeScript and discover a solution to enable inference on generic components.
👍8🔥3
React будет компилируемым
В блоге React рассказали, что React 19 будет компилируемым. В React Training объяснили, что это значит.
При использовании функциональных компонентов и хуков нужно самостоятельно мемоизировать компоненты, колбеки и переменные для предотвращения ненужных ререндеров. При использовании useCallback или useMemo нужно указывать массив зависимостей в deps, чтобы React пересоздавал функцию при изменении зависимостей.
Если использовать линтер для определения зависимостей exhaustive-deps, то он решает большинство проблем. Однако, зависимости, которые кладутся в массив, должны быть стабильными. Стабильная переменная – это переменная, которая не изменяется, если вы этого не захотите. Для стабилизации массивов, объектов и функций используется useMemo.
Для того чтобы избежать проблем с мемоизацией и определением массива зависимостей, в React 19 появится авто-мемоизация с помощью компилятора. Этот компилятор избавит разработчиков от ручной мемоизации, которая была основной проблемой при работе с функциональными компонентами и хуками.
https://reacttraining.com/blog/react-19-will-be-compiled
В блоге React рассказали, что React 19 будет компилируемым. В React Training объяснили, что это значит.
При использовании функциональных компонентов и хуков нужно самостоятельно мемоизировать компоненты, колбеки и переменные для предотвращения ненужных ререндеров. При использовании useCallback или useMemo нужно указывать массив зависимостей в deps, чтобы React пересоздавал функцию при изменении зависимостей.
Если использовать линтер для определения зависимостей exhaustive-deps, то он решает большинство проблем. Однако, зависимости, которые кладутся в массив, должны быть стабильными. Стабильная переменная – это переменная, которая не изменяется, если вы этого не захотите. Для стабилизации массивов, объектов и функций используется useMemo.
Для того чтобы избежать проблем с мемоизацией и определением массива зависимостей, в React 19 появится авто-мемоизация с помощью компилятора. Этот компилятор избавит разработчиков от ручной мемоизации, которая была основной проблемой при работе с функциональными компонентами и хуками.
https://reacttraining.com/blog/react-19-will-be-compiled
ReactTraining.com
React Will Be Compiled
React Corporate Workshops, Training, and Consulting
👍40👎3
Чему мы научились при переходе на Next.js 14 с серверными компонентами
Medusa.js – open source проект для создания e-commerce приложений. В своем блоге они рассказали, чему научились при переходе стартового шаблона на Next.js 14 с серверными компонентами.
Одна из новых особенностей, которая требует привыкания – механизм получения данных и кэширования. Кэширование работает автоматически для fetch запросов и каждый запрос кэшируется и хранится до ревалидации. Это избавляет от проп-дриллинга и передачи значений через контекст, данные можно сразу получить через fetch из кэша.
Серверные действия – асинхронные функции, которые вызываются на клиенте и выполняются на сервере. Под капотом происходит POST запрос на сервер. Используя серверные действия, не раскрываются API ключи и не надо беспокоиться о CORS.
Серверные компоненты позволяют создать UI, который полностью рендерится на сервере. Серверные компоненты имеют доступ к БД и API, что позволяет сократить количество запросов между клиентом и сервером. Например, по умолчанию в Medusa.js на странице карточки товара все компоненты серверные, за исключением кнопки добавления корзины.
С серверными компонентами возникает проблема при получении параметров URL. Т.к. в серверных компонентах не поддерживаются хуки, то единственный вариант получения параметров URL - в page.tsx, и уже дальше передать параметры через проп-дриллинг дочерним компонентам.
https://medusajs.com/blog/client-server-transition-learnings-nextjs-14-server-components/
Medusa.js – open source проект для создания e-commerce приложений. В своем блоге они рассказали, чему научились при переходе стартового шаблона на Next.js 14 с серверными компонентами.
Одна из новых особенностей, которая требует привыкания – механизм получения данных и кэширования. Кэширование работает автоматически для fetch запросов и каждый запрос кэшируется и хранится до ревалидации. Это избавляет от проп-дриллинга и передачи значений через контекст, данные можно сразу получить через fetch из кэша.
Серверные действия – асинхронные функции, которые вызываются на клиенте и выполняются на сервере. Под капотом происходит POST запрос на сервер. Используя серверные действия, не раскрываются API ключи и не надо беспокоиться о CORS.
Серверные компоненты позволяют создать UI, который полностью рендерится на сервере. Серверные компоненты имеют доступ к БД и API, что позволяет сократить количество запросов между клиентом и сервером. Например, по умолчанию в Medusa.js на странице карточки товара все компоненты серверные, за исключением кнопки добавления корзины.
С серверными компонентами возникает проблема при получении параметров URL. Т.к. в серверных компонентах не поддерживаются хуки, то единственный вариант получения параметров URL - в page.tsx, и уже дальше передать параметры через проп-дриллинг дочерним компонентам.
https://medusajs.com/blog/client-server-transition-learnings-nextjs-14-server-components/
Medusa
Medusa - What we've learned from the transition to Next.js 14 with Server Components
The most popular ecommerce project on GitHub. Medusa provides open-source ecommerce backend infrastructure in NodeJS.
👍10👎5🔥2
Избегаем ошибки гидратации с помощью useSyncExternalStore
При разработке SSR приложений может возникать ошибка:
Она связана с несоответствием контента между клиентом и сервером. Например, при рендере даты на сервере используется одна локаль 21/02/2024, а на клиенте другая локаль – пользовательская 2/21/2024.
Самый простой способ избавиться от ошибки - это использовать проп suppressHydrationWarning. Но как следует из документации React им нельзя злоупотреблять.
Другой способ – использовать useEffect для предотвращения рендера на сервере:
Т.к. на сервере useEffect не запускается, то это рабочий способ, но не самый оптимальный. При дальнейшей навигации по сайту компонент LastUpdated будет всегда рендериться дважды, хотя в этом не будет никакой нужды. Мы уже находимся на клиенте, но LastUpdated об этом не знает.
Еще один способ – использовать useSyncExternalStore. Хоть он и предназначен для подписки на внешний стор, у него есть вторая особенность: он позволяет различать clientSnapshot и serverSnapshot. При навигации по сайту он будет сразу брать clientSnapshot, а на сервере использовать serverSnapshot. Вместо подписки на внешний стор можно передать заглушку – пустую функцию. Пример использования:
https://tkdodo.eu/blog/avoiding-hydration-mismatches-with-use-sync-external-store
При разработке SSR приложений может возникать ошибка:
Uncaught Error: Text content does not match server-rendered HTML.Она связана с несоответствием контента между клиентом и сервером. Например, при рендере даты на сервере используется одна локаль 21/02/2024, а на клиенте другая локаль – пользовательская 2/21/2024.
Самый простой способ избавиться от ошибки - это использовать проп suppressHydrationWarning. Но как следует из документации React им нельзя злоупотреблять.
Другой способ – использовать useEffect для предотвращения рендера на сервере:
function LastUpdated() {
const [isClient, setIsClient] = React.useState(false)
React.useEffect(() => {
setIsClient(true)
}, [])
if (!isClient) {
return null
}
/** --snip-- */
}
Т.к. на сервере useEffect не запускается, то это рабочий способ, но не самый оптимальный. При дальнейшей навигации по сайту компонент LastUpdated будет всегда рендериться дважды, хотя в этом не будет никакой нужды. Мы уже находимся на клиенте, но LastUpdated об этом не знает.
Еще один способ – использовать useSyncExternalStore. Хоть он и предназначен для подписки на внешний стор, у него есть вторая особенность: он позволяет различать clientSnapshot и serverSnapshot. При навигации по сайту он будет сразу брать clientSnapshot, а на сервере использовать serverSnapshot. Вместо подписки на внешний стор можно передать заглушку – пустую функцию. Пример использования:
const emptySubscribe = () => () => {}
function LastUpdated() {
const date = React.useSyncExternalStore(
emptySubscribe,
() => lastUpdated.toLocaleDateString(),
() => lastUpdated.toLocaleDateString('en-US')
)
return <span>Last updated at: {date}</span>
}
https://tkdodo.eu/blog/avoiding-hydration-mismatches-with-use-sync-external-store
tkdodo.eu
Avoiding Hydration Mismatches with useSyncExternalStore
Avoiding hydration mismatches can usually be done in two ways - suppressing the warning or spawning an effect. But is there a third option ... ?
👍17