Заметки про React – Telegram
Заметки про React
3.78K subscribers
34 photos
8 videos
485 links
Короткие заметки про React.js, TypeScript и все что с ним связано
Download Telegram
Частичный пре-рендер с помощью Bun

Bun представил идею макросов в JavaScript. Макрос в Bun – это механизм запуска JS функций во время сборки. Значение, возвращаемое этими функциями, напрямую встраивается в бандл.

Арал Рока в своем блоге предложил идею пре-рендера статических компонентов во время сборки с помощью Bun макросов. Таким образом, на одной странице могут отображаться одновременно статические компоненты, отрендеренные во время сборки, и динамические компоненты, отрендеренные в runtime.

Автор предлагает использовать Bun плагин prerender-macro. Пример кода, который использует макросы:


import StaticComponent from "@/static-component" with { type: "prerender" };
import DynamicComponent from "@/dynamic-component";

return (
<>
<StaticComponent foo="bar" />
<DynamicComponent />
</>
);


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


import { prerender } from "prerender-macro/prerender" with { "type": "macro" };
import DynamicComponent from "@/dynamic-component";

// ...
return (
<>
{prerender({
componentPath: "@/static-component",
componentModuleName: "default",
componentProps: { foo: "bar" },
})}
<DynamicComponent />
</>
);


После чего этот код возвращается в Bun для транспиляции и запуска макросов, которые преобразуют статический компонент в HTML строку во время сборки.

Автор обещает, что при использовании частичного пре-рендера компонентов будет меньше размер бандла и более быстрый runtime рендер страницы.

https://aralroca.com/blog/partial-prerendering
👍17
Headless 2.0

Headless – это библиотека компонентов без стилей, но с хорошей поддержкой доступности. Разрабатывается командой Tailwind CSS.

Вышел релиз мажорной версии Headless 2.0. Что нового:

- Интеграция Floating UI для правильного позиционирования якоря дропдауна.

- Новый компонент checkbox.

- Обертки над нативными элементами формы: Fieldset, Field, Label, Input, Select и другие. Внутри них связываются лейбл и поле через ID, добавляются aria-* атрибуты и т.д. Пример использования:


import { Denoscription, Field, Input, Label } from '@headlessui/react'

function Example({ disabled }) {
return (
<Field disabled={disabled}>
<Label>Name</Label>
<Input name="your_name" />
<Denoscription>Use your real name so people will recognize you.</Denoscription>
</Field>
)
}


- Добавили data атрибуты к элементам для hover, active, focus состояний. Под капотом используются хуки из React Aria. Может пригодиться для стилизации элементов.

- Интеграция TanStack Virtual для виртуализации списка в Combobox.

- Новый сайт и улучшенная документация.

https://tailwindcss.com/blog/headless-ui-v2
👍24
Почему глобальный патчинг вреден

В блоге Артема Захарченко вышла статья про вред глобального патчинга API, при котором меняется его поведение. В качестве примера автор приводит fetch в Next.js и Bun:


// Next.js добавил кэширование и возможность сброса кэша
fetch('https://kettanaito.com', {
next: { revalidate: 10 },
})

// Bun добавил возможность проксировать запрос
fetch('https://redd.one', {
proxy: 'https://kettanaito.com',
})


В обоих случаях меняется стандартное поведение функции fetch глобально. Глобальный патчинг API не только влечет за собой затраты, которые влияют как на пользователей, так и на мейнтенеров, но и вредит другим API и даже самому языку. Как вредит глобальный патчинг:

- Поддержка. Когда вы что-то меняете глобально, вы изменяете то, чем не владеете. Поддержка того, чем ты не владеешь, сложно. Например, вы изменили глобально fetch, чтобы он умел дедуплицировать запросы. Что будет, если вы будете использовать сторонний модуль, в который также встроен механизм дедупликации. Эти два поведения будут конфликтовать, вызывая проблемы, которые сложно отладить.

- Предсказуемость. Для того, чтобы CSS одинаково работал во всех браузерах были созданы стандарты стилей. Существуют стандарты Web API для кросс-браузерной совместимости. Также Node.js хорошо поддерживает Web API, предоставляя fetch, ReadableStream, WebSocket и т.д. Таким образом, использование одного API в разных средах предсказуемо. Поэтому изменение поведения fetch в Next.js вредит предсказуемости, потому что это поведение будет работать только в Next.js.

- Обучение. Хорошее API должно обучать. Оно должно быть приближено к существующим стандартам.

Взамен существующему глобальному патчингу в bun, можно использовать существующий API fetch и Request и создать отдельную функцию проксирования:


import { proxy } from 'bun'

const proxiedRequest = proxy(proxyUrl, new Request(url, options))
await fetch(proxiedRequest)


Как можно сделать в Next.js:


import { withCache } from 'next'

const request = new Request('https://kettanaito.com')
fetch(withCache(request, { revalidate: 10 }))


Тем не менее, Next.js идет в сторону отдельной функции для кэширования запросов unstable_cache вместо глобального патчинга fetch.

https://kettanaito.com/blog/why-patching-globals-is-harmful
👍17🔥1
Гайд по SolidJS для React разработчиков

Код на Solid очень похож на React, т.к. оба используют JSX. Однако Solid работает по-другому и важно знать эти отличия. Автор в своей статье написал об отличиях Solid и React:

- Компонент и рендеринг. В React компонент - это функция, которая является частью цикла рендеринга. Компонент возвращает объект, который описывает что должно быть отрендерено. Компонент в Solid - это функция, которая вызывается один раз для создания DOM узла. Можно сказать, что в функция возвращается шаблон.

- Реактивность. В React для реактивности используется useState и контекст, изменение которых вызывает ре-рендер всего компонента. В Solid для реактивности используется createSignal и при изменении значения сигнала ре-рендерится DOM узел, использующий сигнал.

Также автор привел популярные ошибки, с которыми сталкиваются при переходе на Solid. Например, в Solid при деструктуризации пропсов теряется реактивность:


function Message(props) {
const { text } = props;
return <div>{ text }</div>
}


Связано это с тем, что Solid использует Proxy для передачи пропсов. Далее, в тех местах где используется проп через . или [], используется get функция объекта Proxy, в котором возвращается сигнал.

https://www.stashpad.com/blog/react-developer-guide-to-solid-js
👍203
Как работает RSC

В блоге Smashing Magazine автор Лазарь Николов написал подробную статью о том, как работает React Server Components и как это отражается на загрузке страницы. Перед погружением в RSC автор напомнил, как работают CSR, SSR, SSG и ISR, какие у них преимущества и недостатки.

Автор рассказал как теорию, так и показал на практике, как работает RSC. О чем пишет автор:
- жизненный цикл рендеринга RSC в Next.js;
- что такое RSC payload и как его читать;
- как происходит стриминг HTML по чанкам;
- как в HTML вставляется фолбек Suspense и происходит его замена на отрендеренный компонент.

https://www.smashingmagazine.com/2024/05/forensics-react-server-components/
👍15👎1🔥1
Создание бесконечного скролла и E2E тест к нему

Бесконечный скролл позволяет загружать данные на страницу поэтапно, в процессе скролла пользователя. В блоге Wanago автор описал как работает бесконечный скролл с Intersection Observer для постраничной загрузки данных.

Для тестирования функционала используется библиотека Playwright. Узнать количество загруженных записей можно с помощью функции toHaveCount, а проскроллить до элемента-якоря можно с помощью scrollIntoViewIfNeeded:


import { expect, test } from '@playwright/test';

test.describe('The Posts page', () => {
test.describe('when visited', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
// ...
test.describe('and when the user scrolls down', () => {
test('should contain forty posts', async ({ page }) => {
const posts = page.getByTestId('post');

await expect(posts).toHaveCount(20);

await page.getByTestId('loader').scrollIntoViewIfNeeded();

await expect(posts).toHaveCount(40);
});
});
});
});


https://wanago.io/2024/04/29/react-infinite-scrolling-e2e-playwright/
👍13🔥7
React Compiler вышел в open source

Появилась документация по React Compiler, в которой предлагают попробовать экспериментальную версию компилятора. Для того чтобы компилятор оптимизировал компонент, он должен соблюдать условия:

- Валидный JS без ошибок.
- Проверяет nullable/опциональные свойства перед доступом к ним. Пример: if (object.nullableProperty) { object.nullableProperty.foo }. Чтобы это условие выполнялось в TS, надо включить strictNullChecks.
- Соблюдает «Правила React».

Для того чтобы соблюдать «Правила React», появился ESLint плагин eslint-plugin-react-compiler. Этот плагин можно использовать без компилятора.

React Compiler можно добавить в существующий проект на Vite, Next и т.д. через babel-плагин. У плагина есть настройка для фильтрации компилируемых файлов, поэтому можно запустить компилятор только на часть проекта:


const ReactCompilerConfig = {
sources: (filename) => {
return filename.indexOf('src/path/to/dir') !== -1;
},
};


Для того чтобы определить, что компонент оптимизирован компилятором, React Devtools (v5.0+) добавляет бейдж «Memo » рядом с компонентом.

Также команда React создала песочницу, в которой можно увидеть, как компилируется компонент – React Compiler Playground.

https://react.dev/learn/react-compiler
👍24👎51
Предпочитайте несколько композиций

В своем блоге Кайл Шевлин предложил, что, если компонент имеет ограниченный набор вариантов обработки, то будет удобнее иметь четкие отдельные ветки if/else для каждого случая, а не распространять условные выражения повсюду в JSX. Сравните два примера:


function VideoControls() {
const { isPlaying, play, pause } = useVideoControls()

return (
<Button onPress={isPlaying ? pause : play}>
<Icon name={isPlaying ? 'pause' : 'play'} />
<Button.Text>{isPlaying ? 'Pause' : 'Play'}</Button.Text>
</Button>
)
}

function VideoControls() {
const { isPlaying, play, pause } = useVideoControls()

if (isPlaying) {
return (
<Button onPress={pause}>
<Icon name="pause" />
<Button.Text>Pause</Button.Text>
</Button>
)
}

return (
<Button onPress={play}>
<Icon name="play" />
<Button.Text>Play</Button.Text>
</Button>
)
}


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

https://kyleshevlin.com/prefer-multiple-compositions/
👍34👎4
Влияние Styled Components на время ответа SSR

В своем блоге Эндрю Левин написал статью о результатах измерения перфоманса SSR приложения со Styled Components. Styled Components замедляет работу SSR приложения, но насколько – нужно было измерить. Для того чтобы сделать замеры автор использовал неминифицированную копию Styled Components и собрал трассировку CPU во время рендера SSR приложения.

В результате, на рендер дерева компонентов ушло 250мс, из которых 117мс потрачено на генерацию и внедрение Styled Components. Таким образом, ~47% времени SSR было потрачено на Styled Components.

https://blog.levineandrew.com/quantifying-the-impact-of-styled-components-on-server-response-times
👍17
Автоматическая инвалидация запросов в React Query

Reacy Query позволяет инвалидировать запросы в ручном режиме queryClient.invalidateQueries({ queryKey: ['todos'] }). В библиотеке не добавляли встроенные способы автоматической инвалидации по причине того, что это ухудшает гибкость. Ведь в разных ситуациях может потребоваться разная инвалидация: во время onSuccess или onSettled, должна ли мутация ждать инвалидацию. Ожидание инвалидации приводит к тому, что мутация остается в состоянии ожидания pending = true.

Доминик Дорфмайстер в своем блоге показал несколько способов как сделать автоматическую инвалидацию запросов. Инвалидацию можно делать в глобальном обработчике onSuccess кэша. Пример использования поля meta, в котором можно указать список полей для инвалидации:


import { matchQuery } from '@tanstack/react-query'

const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (_data, _variables, _context, mutation) => {
queryClient.invalidateQueries({
predicate: (query) =>
// invalidate all matching tags at once
// or everything if no meta is provided
mutation.meta?.invalidates?.some((queryKey) =>
matchQuery({ queryKey }, query)
) ?? true,
})
},
}),
})

// usage:
useMutation({
mutationFn: updateLabel,
meta: {
invalidates: [['issues'], ['labels']],
},
})


https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations
👍92🔥1
useCallback и утечки памяти

В своем блоге Кевин Шинер поделился об опыте поиска утечек памяти при использовании useCallback. Рассмотрим пример:


import { useState, useCallback } from "react";

class BigObject {
public readonly data = new Uint8Array(1024 * 1024 * 10);
}

export const App = () => {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
const bigData = new BigObject(); // 10MB of data

const handleClickA = useCallback(() => {
setCountA(countA + 1);
}, [countA]);

const handleClickB = useCallback(() => {
setCountB(countB + 1);
}, [countB]);

// …
};


Если попеременно вызывать handleClickA и handleClickB, то произойдет бесконечная утечка памяти. Например, при вызове функции handleClickA будет заново создан колбек только для handleClickA, т.к. поменялся countA. Для handleClickB останется старый колбек с текущей областью видимости. Новая область видимости будет хранить ссылку на новый колбек handleClickA и старый колбек handleClickB, внутри которых старая область видимости компонента.

Основная проблема связана с тем, что в одном компоненте разные useCallback могут ссылаться друг на друга и на другие данные через замыкания. Эти замыкания сохраняются в памяти до тех пор, пока useCallback не будет создан заново. Поэтому наличие в компоненте более одного useCallback затрудняет понимание того, что хранится в памяти.

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

- Пишите небольшие компоненты и используйте кастомные хуки.
- Избегайте захвата других замыканий. Это может произойти, если вы вызываете из одного useCallback другой useCallback.
- Избегайте мемоизации, когда в этом нет необходимости.
- Используйте useRef для хранения больших объектов.

https://schiener.io/2024-03-03/react-closures
👍192
Restyle

Новая CSS-in-JS библиотека, совместимая с RSC, которая использует новую фичу поднятия стилей и дедупликацию React 19.

Пример использования через функцию CSS, которая возвращает имена классов и тег <styles>:


import { css } from "restyle";

export default function BasicUsage() {
const [classNames, styles] = css({
padding: "1rem",
backgroundColor: "peachpuff",
});

return (
<>
<div className={classNames}>Hello World</div>
{styles}
</>
);
}


В React 19 тег <styles>, размещенный внутри компонента, в некоторых случаях будет перемещен внутрь тега <head>. Также React дедуплицирует одинаковые стили.

https://www.restyle.dev/
👍18👎131
Использование React Compiler на реальном проекте

В своем блоге Надя Макаревич рассказала об опыте использования React Compiler на нескольких реальных больших проектах.

При тестировании на небольших примерах React Compiler справляется хорошо, может мемоизировать разные сценарии:

- мемоизация компонента без пропсов;
- мемоизирует пропсы для компонента через useCallback, useMemo;
- мемоизирует дочерний компонент через useMemo;


const VerySlowComponentMemo = React.memo(VerySlowComponent);

export const SimpleCase3 = () => {
const [isOpen, setIsOpen] = useState(false);

// мемоизация дочернего компонента через useMemo
const child = useMemo(() => <SomeOtherComponent />, []);

return (
<>
...
<VerySlowComponentMemo>{child}</VerySlowComponentMemo>
</>
);
};


Однако реальные проекты гораздо сложнее чем эти примеры и в них больше разных сценариев. При установке React Compiler на три разных проекта компилятор в каждом из проектов исправил только 2 из 10 проблемных рендера.

Чтобы исправить остальные проблемные рендеры, пришлось вручную исправлять код компонентов. Например:

- деструктуризировать mutate из useMutation и использовать его в коде напрямую;
- выделить часть кода в отдельный компонент;
- в цикле изменить key с index на name.

https://www.developerway.com/posts/i-tried-react-compiler
👍152
Вам все еще нужен Framer Motion?

Мэтт Перри, автор библиотеки Framer Motion, в своем блоге поделился о новых фичах в CSS. Благодаря этим фичам возможно вам больше не понадобится библиотека Framer Motion.
Автор сравнил популярные виды анимаций в Framer Motion и как их можно реализовать через CSS:

- Вводная анимация. Чтобы сделать анимацию при появлении элемента в документе или при загрузке страницы, можно использовать CSS правило @starting-style. Это правило определяет первоначальные стили, из которых элемент будет анимироваться:


#my-element {
opacity: 1;
transition: opacity 0.5s;

@starting-style {
opacity: 0;
}
}


- Независимая трансформация. Появятся свойства translate, scale и rotate для независимой трансформации элемента.


button {
translate: 0px 0px;
transition:
translate 0.2s ease-out,
scale 0.5s ease-in-out;
}

button:hover {
scale: 1.2;
}


- Spring анимация. Для создания spring анимации можно использовать CSS функцию linear(). Функция принимает серию точек и линейно интерполирует между ними. Чтобы не писать серию точек вручную, используйте онлайн генераторы функции linear().

- Анимация по скроллу. В CSS появились две timeline анимации scroll() и view(). Их можно назначить в свойство animation-timeline для управления анимацией посредством скролла, а не времени:


div {
animation-name: fadeAnimation;
animation-timeline: scroll();
}


- Бонус, анимация высоты. В CSS нет возможности анимировать в/из auto, но CSS5 функция calc-size позволит это сделать:


li {
height: 0px;
transition: height 0.3s ease-out;

.open {
height: calc-size(auto);
}
}


https://motion.dev/blog/do-you-still-need-framer-motion
👍27🔥9
This media is not supported in your browser
VIEW IN TELEGRAM
Создание переиспользуемой SubmitButton

В React 19 появился хук useFormStatus для получения статуса формы. С его помощью можно создать переиспользуемую кнопку действия, которая будет сама переходить в состояние прогресса и дизейблиться. Пример компонента кнопки:


export default function SubmitButton({
children,
loading,
disabled,
...otherProps
}: Props & React.HTMLProps<HTMLButtonElement>) {
const { pending } = useFormStatus();
const isSubmitting = pending || loading;

return (
<Button
{...otherProps}
disabled={isSubmitting || disabled}
type="submit"
>
{isSubmitting ? (
// progress
) : (
children
)}
</Button>
);
}


В своей статье автор привел пример использования этой кнопки в React 19 с серверными функциями. Такой пример будет работать, даже когда JavaScript выключен или когда кнопка еще не гидратировала:


export default function Component({ contactId }: { contactId: string }) {
return (
<form action={deleteRecord}>
<input type="hidden" name="contactId" value={contactId} />
<SubmitButton>Delete</SubmitButton>
</form>
);
}


https://aurorascharff.no/posts/creating-a-reusable-submitbutton-with-useformstatus
👍101🔥1
Чеклист безопасности в Next.js

Описание подходов, которые используются в SDK Arcjet для повышения безопасности Next.js. Хорошая безопасность обеспечивается за счет многоуровневого подхода - ничто не может быть защищено на 100%, но есть несколько простых вещей, которые каждый может сделать для снижения риска.

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

- Валидация и санитизация данных. Экранирование и санитизация React помогает предотвратить XSS атаки, но это не покрывает все потребности. Используйте схему для валидации данных, пришедших от пользователя. Например, для создания схемы можно использовать Zod:


export const insertTeamSchema = createInsertSchema(teams, {
name: (schema) =>
schema.name
.min(2, { message: "Must be 2 or more characters." })
.max(100, { message: "Must be 100 or fewer characters." })
.trim(),
});


- Переменные окружения. Next.js автоматически загружает переменные окружения на сервер. Если хотите, чтобы переменная стала доступна на клиенте, то у нее должен быть префикс NEXT_PUBLIC_. Однако будьте осторожны: любое значение, которое вы раскрываете таким образом, становится публичным.

- Избегайте раскрытия кода. Чтобы избежать передачи серверного кода на клиент, используйте библиотеку server-only. Импортируйте ее в начале файла серверного компонента. Если клиентский компонент попытается использовать серверный компонент, то сборка упадет.

- Заголовки безопасности. При отдаче страницы сервером выставляйте заголовки безопасности. Они могут предотвратить распространенные веб-атаки. Основные заголовки, которые следует использовать: Content Security Policy (CSP), Strict-Transport-Security (HSTS), X-Content-Type-Options, Permissions-Policy, Referrer-Policy.

https://blog.arcjet.com/next-js-security-checklist/
👍17👎2
Suspense и React 19 RC

В React 19 RC появилось спорное изменение в работе Suspense.

В 18 версии внутри Suspense рендерелись все компоненты и отображался fallback, пока внутри компонентов происходила загрузка данных. После окончания загрузки данных во всех компонентах вместо fallback отображались сами компоненты.


export default function App() {
return (
<Suspense fallback={<p>...</p>}>
<Header />
<Navbar />
<main>
<Content />
</main>
<Footer />
</Suspense>
)
}


В React 19 RC изменилось поведение Suspense. Если компонент приостановится, то его соседние компоненты не будут отрендерены до тех пор, пока компонент не закончит загрузку данных. Это привело к тому, что вместо параллельной загрузки данных во всех компонентах, он начал делать последовательную загрузку данных, создавая «водопад» запросов.

У этого решения есть и положительный эффект: есть возможность сразу показывать fallback, избегая лишнего рендера.

Для решения проблемы «водопада» запросов нужно делать предварительные запросы на уровне роута:


export const Route = createFileRoute('/')({
loader: ({ context: { queryClient } }) => {
queryClient.ensureQueryData(repoOptions('tanstack/query'))
queryClient.ensureQueryData(repoOptions('tanstack/table'))
},
component: () => (
<Suspense fallback={<p>...</p>}>
<RepoData name="tanstack/query" />
<RepoData name="tanstack/table" />
</Suspense>
),
})


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

Скорее всего поведение Suspense еще изменится перед релизом React 19, т.к. не всех устраивает текущее поведение. Например, библиотека react-three-fiber основана на асинхронной работе и полагается на то, как работает Suspense в React 18.

https://tkdodo.eu/blog/react-19-and-suspense-a-drama-in-3-acts
👍11👎5
Как работает React Compiler

Автор объясняет, как работает компилятор React начиная с самых основ (транспилятор, AST, мемоизация), а затем анализирует результат компилятора на конкретном примере:


function List(t0) {
const $ = _c(6);

const { items } = t0;

/* --snip-- */

let t2;

if ($[1] !== items) {
const pItems = processItems(items);
let t3;

if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = (item) => <li>{item}</li>;

$[3] = t3;
} else {
t3 = $[3];
}

t2 = pItems.map(t3);
$[1] = items;
$[2] = t2;
} else {
t2 = $[2];
}

/* --snip-- */
}


Функция _c (сокращенно от cache) создает массив длинной 6. Компилятор React проанализировал функцию Link и понял, что для максимизации производительности надо хранить 6 элементов. Когда функция Link вызовется впервые, то результат каждой операции сохранится в одном из шести элементов массива $.

У компилятора есть недостаток: он затрудняет дебаг нашего кода, добавляя новый уровень абстракции. Поэтому стоит научиться понимать, как работает React Compiler, чтобы лучше дебажить компилированный им код.

https://tonyalicea.dev/blog/understanding-react-compiler/
👎15👍6🔥21
Релиз TypeScript 5.5

Вышел релиз новой версии TypeScript. Список основных изменений:

- Предикаты выводимых типов (Inferred Type Predicates). Одно из главных изменений, которое многим упростит написание кода. Теперь TS сам выводит типы для предикатов-функций, сужая (narrowing) возвращаемый тип. Пример:


// function isBirdReal(bird: Bird | undefined): bird is Bird
function isBirdReal(bird: Bird | undefined) {
return bird !== undefined;
}


Это будет работать для метода массива filter. Если раньше TS не определял тип у элемента массива после фильтрации, то теперь будет. Пример:


function makeBirdCalls(countries: string[]) {
// birds: Bird[]
const birds = countries
.map(country => nationalBirds.get(country))
.filter(bird => bird !== undefined);

for (const bird of birds) {
bird.sing(); // ok!
}
}


- Сужение типа для констант. TS теперь может сужать типы для выражений obj[key], где obj и key константы. Пример:


function f1(obj: Record<string, unknown>, key: string) {
if (typeof obj[key] === "string") {
// Сейчас работает, ранее была ошибка
obj[key].toUpperCase();
}
}


- Поддержка новых ECMAScript методов для Set. Появилась поддержка новых методов для Set: union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, isDisjointFrom. Пример:


let fruits = new Set(["apples", "bananas", "pears", "oranges"]);
let oranges = new Set(["oranges"]);

// Set(4) {'apples', 'bananas', 'pears', 'oranges'}
console.log(fruits.union(oranges));


https://devblogs.microsoft.com/typenoscript/announcing-typenoscript-5-5/
👍37👎1
Ленивое обнаружение роутов в React Router

В новой минорной версии React Router 6.24 появилась возможность ленивого обнаружения роутов. С помощью метода unstable_patchRoutesOnMiss можно загружать дополнительные роуты и реализовывать разделение кода для ваших роутов вместо того, чтобы хранить их все в одном монолитном модуле. Эта фича пригодится для приложений с тысячами роутов, которые теперь не надо будет загружать заранее, задерживая гидратацию.


const router = createBrowserRouter(
[
{
id: "root",
path: "/",
Component: RootComponent,
},
],
{
async unstable_patchRoutesOnMiss({ path, patch }) {
if (path === "/a") {
// Загрузка `a` роута (`{ path: 'a', Component: A }`)
let route = await getARoute();
// Установка `a` роута как новый дочерний роут у `root`
patch("root", [route]);
}
},
}
);


https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v6240
👍13👎2
5 заблуждений о React Server Components

В React 19 представили большое архитектурное изменение: разделение на серверные и клиентские компоненты. Серверные компоненты выполняются только на сервере, и их код не попадает в клиентский бандл. Клиентские компоненты – обычные компоненты React, к которым уже все привыкли. Эта новая архитектура направлена на использование сильных сторон как на сервере, так и на клиенте.

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

- Предпочитайте только серверные компоненты, а клиентские используйте редко. Это не так, потому что серверные компоненты не подходят для всех сценариев, особенно которые требуют интерактивности. Серверные компоненты – это новый тип компонентов, которые выполняются только на сервере, получают данные и рендерят их на сервере для повышения производительности.


async function UserProfile({ userId }) {
const response = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
const user = response.user;

return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}


- use client помечает клиентские компоненты, а use server помечает серверные компоненты. На самом деле директива use client помечает клиентские компоненты. Но директива use server не нужна, все компоненты по умолчанию являются серверными.

- Каждый клиентский компонент должен иметь директиву use client. На самом деле достаточно объявить директиву use client один раз в корневом файле клиентского компонента. Все компоненты, определенные в этом файле, автоматически станут клиентскими. Сюда входят вложенные компоненты, утилиты и функции внутри файла.

- Серверные компоненты не могут быть вложены в клиентские компоненты. Напрямую импортировать серверный компонент в клиентский нельзя. Но использовать серверный компонент внутри клиентского можно через проп, например, children:


// ServerComponent.js
function ServerComponent() {
return <h1>Hello from the server</h1>;
}

// ClientComponent.js
'use client'
function ClientComponent({ children }) {
return (
<div>
<p>This is a client component</p>
{children}
</div>
);
}

// Usage
function App() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
);
}


https://www.builder.io/blog/nextjs-react-server-components
👍11👎2