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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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


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

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


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

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


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

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

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

import {cookies} from "next/headers";

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

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

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


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

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

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

return WrapperComponent
}

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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


https://www.bekk.christmas/post/2023/1/polymorphism-in-react
👍11🔥5
Как JSX преобразуется в HTML

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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


https://puckeditor.com/
👍15🔥2