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

Вышла новая версия генератора статических сайтов Docusaurus. Он позволяет создавать сайты на React с фокусом на контент и поддерживает Markdown разметку из под коробки. По словам разработчиков, Docusaurus как create-react-app, но для создания документации, блогов и лендингов.

В новой версии появились новые фичи:
- MDX. Можно добавлять интерактивные React компоненты в Markdown разметку.
- Плагины. У Docusaurus модульная архитектура с системой плагинов. Основные фичи, такие как блог, документация, страницы и поиск можно улучшать плагинами.
- Темизация. Возможность очень гибкой кастомизации стилей, либо создание и использование своей собственной темы.

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

https://docusaurus.io/blog/2022/08/01/announcing-docusaurus-2.0
👍6
Различие между useEffect и useLayoutEffect

Хуки useEffect и useLayoutEffect предназначены для выполнения сайд-эффектов в компоненте и имеют одинаковые сигнатуру. Но цели их использования разные.

useEffect запускается асинхронно после отрисовки браузером DOM изменений.

useLayoutEffect запускается синхронно после DOM изменений, до отрисовки браузера.

Это значит, что если необходимо делать DOM изменения или какую-либо анимацию, то больше подходит useLayoutEffect. В остальных случаях необходимо использовать useEffect. Если выполнять DOM изменения в useEffect, то изменение отобразится в следующем тике браузерного рендера, что приведет к эффекту “мерцания”.

В React 18 было изменено поведение useEffect. Теперь он запускается синхронно, если это результат дискретного ввода. Дискретный ввод — это тип события, при котором результат одного события может повлиять на поведение следующего, например клик. Есть следующий пример:

const App = () => {
const [showToolTip, setShowTooltip] = useState(false);
const buttonRef = useRef();
const tooltipRef = useRef();

useEffect(() => {
if (buttonRef.current == null || tooltipRef.current == null) return;

const { left, top } = buttonRef.current.getBoundingClientRect();
tooltipRef.current.style.left = ${left + 120}px;
tooltipRef.current.style.top = ${top - 20}px;
}, [showToolTip]);

return (
<div>
<button ref={buttonRef}
onClick={() => setShowTooltip(prevState => !prevState)}>
Toggle tooltip
</button>

{showToolTip &&
(<div ref={tooltipRef}>
This is a Tooltip!
</div>)}
</div>
)
};


Если запустить пример выше в React 17, то при клике на кнопку произойдет эффект “мерцания” – сначала тултип отобразится, потом расположится в нужной позиции. Если использоваться useLayoutEffect, то проблема исчезнет. Если запустить пример в React 18, то эффекта “мерцания” не будет и с useEffect, т.к. эффект произошел из-за дискретного ввода – клика по кнопке.

https://blog.saeloun.com/2022/07/28/difference-between-useeffect-and-useeffectlayout-hooks
👍17
Mantine 5.0 – полнофункциональная библиотека компонентов

Mantine – библиотека компонентов для React 18, написанная на TypeScript и Emotion. Включает более 100 готовых компонентов и 40 хуков.

Библиотека разделена на модули, которые можно устанавливать по необходимости. Например, есть модули для работы с формами (аналог react-hook-form) и для работы с датами (компоненты календаря, выбора даты). Также есть модуль для обеспечения серверного рендера компонентов.

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

import { Slider } from '@mantine/core';
import { useHover } from '@mantine/hooks';

export function SliderHover() {
const { hovered, ref } = useHover();

return (
<Slider
defaultValue={40}
min={10}
max={90}
ref={ref}
label={null}
styles={{
thumb: {
transition: 'opacity 150ms ease',
opacity: hovered ? 1 : 0,
},

dragging: {
opacity: 1,
},
}}
/>
);
}


https://mantine.dev/
👍11
Как избежать useEffect с помощью ref колбеков

Реф – это обертка, в которой может храниться любое значение, но чаще всего в ней хранят DOM узлы. В каждом HTML элементе есть зарезервированный проп ref, который позволяет получить доступ к DOM узлу после рендера компонента.

const ref = React.useRef(null)

return <input ref={ref} defaultValue="Hello world" />


Одна из типичных задач при работе с рефом – сделать фокус у инпута при инициализации компонента. Один из вариантов, как это можно сделать, через useEffect:

const ref = React.useRef(null)

React.useEffect(() => {
ref.current?.focus()
}, [])

return <input ref={ref} defaultValue="Hello world" />


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

Более оптимальный способ обращения к рефу – через колбек реф. Концептуально реф у React элемента – это функция, которая вызывается после того, как компонент был отрендерен. Поэтому можно передать колбек в реф и получить доступ к DOM узлу только после рендера элемента.

const ref = React.useCallback((node) => {
node?.focus()
}, [])

return <input ref={ref} defaultValue="Hello world" />


Если компонент размонтируется, то колбек будет вызван еще раз с аргументом равным null, поэтому стоит проверять переданный аргумент перед использованием.

https://tkdodo.eu/blog/avoiding-use-effect-with-callback-refs
👍16
Создаем функцию контекста в JavaScript

React популяризировал идею распространения значений через контекст, с помощью которого можно избавиться от проп-дриллинга и синхронизировать состояние компонента в разных частях приложения.
Хотя вариант использования контекста в UI фреймворках очевиден, потребность подобного API существует и не только в UI фреймворках. Создадим свою версию Context API для фреймворка юнит тестирования:

describe('calculator: Add', () => {
it("Should correctly add two numbers", () => {
expect(add(1, 1)).toBe(2);
});
});


После успешного прохождения теста в консоль должна выводиться запись: “calculator: Add > Should correctly add two numbers”.
Для того, чтобы это сообщение вывелось, нам нужно как-то прокинуть описание describe в функцию it. Для этого нам поможет концепция контекста.
Эвятар Алуш в своей статье на Smashing Magazine поделился своей реализацией контекста, которую он использовал в библиотеке для валидации форм Vest.

function createContext() {
let contextValue = undefined;

function Provider(value, callback) {
contextValue = value;
callback();
contextValue = undefined;
}

function Consumer() {
return contextValue;
}

return {
Provider,
Consumer
}
}


https://www.smashingmagazine.com/2022/08/react-context-propagation-javanoscript/
👍6
Мигрируем с Create React App на Vite

Если вы задумывались о миграции приложения с Create React App на Vite, то Катал Мак Доннача в своем блоге собрал инструкцию как это сделать безболезненно.

Единственный минус Vite при работе с проектом на TypeScript – он не проверяет типы при сборке, а сразу транспилирует в JavaScript с помощью esbuild. Поэтому скорость сборки в 20-30 быстрее чем собирать с проверкой типов через tsc. Если это критично, то процесс сборки можно дополнить командой проверки типов tsc --noEmit. В остальном, Vite умеет все то же самое что и CRA, но гораздо быстрее.

https://cathalmacdonnacha.com/migrating-from-create-react-app-cra-to-vite
👍5
Используем React Query вместе с React Router

В React Router 6.4 добавится концепция получения данных – loader и action, как в фреймворке Remix.
Используя проп loader можно объявить, какие данные нужно запросить для отображения роута и получить их в роут через хук useLoaderData. Для изменения данных и их инвалидации используется проп action, а вызывается он при сабмите формы. Однако, React Router не берет на себя обязанность кэширования данных, поэтому имеет смысл использовать библиотеки запросов с кэшированием, например, React Query.

Доминик Дорфмайстер рассказал, как можно объединить React Router и React Query вместе, чтобы использовать преимущества обеих библиотек. React Router вызывает loader получения данных раньше, чем компонент монтируется, а React Query умеет кэшировать данные и дает возможность использовать API запроса, например, refetchOnWindowFocus.

// src/routes/contacts.jsx
import { useQuery } from '@tanstack/react-query'
import { getContact } from '../contacts'

// ⬇️ define your query
const contactDetailQuery = (id) => ({
queryKey: ['contacts', 'detail', id],
queryFn: async () => getContact(id),
})

// ⬇️ needs access to queryClient
export const loader =
(queryClient) =>
async ({ params }) => {
const query = contactDetailQuery(params.contactId)
// ⬇️ return data or fetch it
return (
queryClient.getQueryData(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
}

export default function Contact() {
const params = useParams()
// ⬇️ useQuery as per usual
const { data: contact } = useQuery(contactDetailQuery(params.contactId))
// render some jsx
}


Компонент, где объявляется роут:

const queryClient = new QueryClient()
export default function Routes() {
return (
<Route
path="contacts/:contactId"
element={<Contact />}
// ⬇️ pass the queryClient to the route
loader={contactLoader(queryClient)}
/>
);
}


Обратите внимание на то, что loader - не хук, поэтому не может использовать хук useQueryClient для получения инстанса клиента, и его нужно прокидывать в loader вручную.
На данный момент React Router 6.4 еще находится в бете, но уже можно посмотреть на сайте документации, как будут работать новые концепции получения данных.

https://tkdodo.eu/blog/react-query-meets-react-router
👍11👎3
Почему React ре-рендерит

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

Основная причина для рендера компонента – изменение стейта. При изменении стейта ре-рендерятся дочерние компоненты. Чтобы избежать лишнего ре-рендера компонента при рендере родительского, используйте мемоизацию – memo.

React оптимизирует процесс рендера, поэтому сам по себе ре-рендер не имеет большого значения. Однако в некоторых случаях ре-рендер “тяжелого” компонента может занять много времени и повлиять на производительность приложения. В случаях, когда нужно оптимизировать количество ре-рендеров, используют хуки useMemo и useCallback.

useMemo используется для кэширования вычислений между ре-рендерами. Также useMemo используется для сохранения ссылки на одно и то же значение между ре-рендерами для передачи в пропсы мемоизированного компонента. useCallback делает то же самое, что и useMemo, но только для функций.

https://www.joshwcomeau.com/react/why-react-re-renders/
https://www.joshwcomeau.com/react/usememo-and-usecallback/
👍7
React Awesome Reveal

Библиотека для React, которая добавляет анимацию раскрытия элемента используя Intersection Observer API. Библиотека использует Emotion для объявления стилей анимаций.
Также библиотека поддерживает Tree-shaking и серверный рендер. Поддерживается большинство стандартных анимаций из библиотеки Animate.css. Есть возможность каскадного применения анимации к дочерним элементам.


<Fade cascade>
<p>I enter first...</p>
<p>...then comes my turn...</p>
<p>...and finally you see me!</p>
</Fade>


https://github.com/morellodev/react-awesome-reveal
👍5
Клиентский рендер vs серверный рендер

Альмог Габай провел подробное исследование CSR, в котором изучается потенциал приложений, которые рендерятся на клиенте, по сравнению с приложениями с серверным рендером.
Основной целью работы была возможность достичь хорошей производительности приложения и поддержки SEO в CSR приложениях.

В гайде написано про то, как можно настроить конфиг webpack для оптимальной работы приложения: создание vendor файла и preload чанков приложения. Для поддержки SEO необходима генерация sitemap.xml, которую можно создавать при сборке приложения.

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

https://github.com/theninthsky/client-side-rendering
👍5
Миграция на React Router v6: подводные камни и альтернативы

Отличный гайд по миграции на React Router v6, в котором рассказывают про возможные проблемы, с которыми могут столкнуться разработчики при смене версии React Router в приложении.

Если приложение большое, то облегчить процесс миграции поможет библиотека react-router-dom-v5-compat. С помощью нее можно поэтапно мигрировать на новую версию, сохраняя работоспособность приложения.

https://habr.com/ru/company/alfa/blog/686954/
🔥4👍1
Использование хука useSyncExternalStore

В React 18 появился новый хук useSyncExternalStore, который используется для чтения и подписки на внешние источники данных и совместим с конкурентным рендерингом. В основном он используется в библиотеках управления состояния, например, Redux для реализации селекторов. Также хук useSyncExternalStore может использоваться и в коде приложения для предотвращения лишних ререндеров компонента.

Для примера рассмотрим хук useLocation из библиотеки react-router. Он возвращает объект с несколькими атрибутами.

function CurrentHash() {
const { hash } = useLocation();
return <div>{hash}</div>;
}



Возможно для работы компонента не нужны все атрибуты, однако при изменении любого из них произойдет ререндер компонента, даже если он не используется. Это может негативно повлиять на производительность приложения, особенно если этот хук используется в главном компоненте приложения App.js.

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

- subscribe: функция, которая вызывает колбек при изменении значений.
- getSnapshot: функция, которая возвращает текущее значение стора.
- getServerSnapshot: функция, которая возвращает значение стора во время серверного рендеринга.

Чтобы реализовать через useSyncExternalStore селектор location, необходимо установить библиотеку history. Перенесем API history на аргументы useSyncExternalStore:

- history.listen: подписки на изменения.
- history: текущее значение.

Таким образом, реализация кастомного хука будет довольна проста:

function useHistorySelector(selector) {
return useSyncExternalStore(history.listen, () =>
selector(history)
);
}

function CurrentPathname() {
const pathname = useHistorySelector(
(history) => history.location.pathname
);
return <div>{pathname}</div>;
}

function CurrentHash() {
const hash = useHistorySelector(
(history) => history.location.hash
);
return <div>{hash}</div>;
}
👍13🔥2
React и композиция функций

При создании приложения на React бывает необходимо делать сразу несколько вещей на каждой странице:
- проверять статус аутентификации пользователя;
- проверять фича-тоглы для отображения фич;
- логирование работы компонента;
- рендер лэйаута (навигация, боковое меню и т.д.)

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

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

const MyPage = ({ user = {}, signIn, features = [], log }) => {
return (
<>
<AuthStatusProvider>
<FeatureProvider>
<LogProvider>
<StandardLayout>
<div className="content">{/* our actual page content... */}</div>
</StandardLayout>
</LogProvider>
</FeatureProvider>
</AuthStatusProvider>
</>
);
};


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

const compose = (...fns) => (x) => fns.reduceRight((y, f) => f(y), x);
const withProviders = compose(
withUser,
withFeatures,
withLogger,
withLayout
);
export default withProviders;


Теперь решения сквозных проблем собраны и изменяются в одном месте. Для использования в компоненте нужно импортировать HOC withProviders:

const MyPage = ({ user = {}, signIn, features = [], log }) => {
return <>{/* our actual page content... */}</>;
};
const MyPageWithProviders = withProviders(MyPage);


https://medium.com/javanoscript-scene/why-every-react-developer-should-learn-function-composition-23f41d4db3b1
👍9
Обработка ошибок в React

В приложении могут возникать самые разные ошибки: от ошибки сети до неправильной работы хука в сторонней библиотеке. Если ваше приложение не будет обрабатывать такие ошибки, то оно может сломаться, отображая пользователю белый экран. В React для обработки ошибок используется Error Boundaries – механизм, когда ошибка в дереве компонентов всплывает до компонента обработчика ошибки и как-то обрабатывает его, например, отображая компонент-заглушку. Компонент обработки ошибок становится Error Boundaries, если он классовый и реализованы методы static getDerivedStateFromError или componentDidCatch.

У Error Boundaries есть недостаток, он не обрабатывает ошибки, которые возникли в следующих случаях:
- обработка событий;
- асинхронный код (setTimeout или requestAnimationFrame);
- во время SSR;
- возникли в Error Boundaries;

Также нет механизма повторного рендера дочернего компонента у Error Boundaries для восстановления приложения.

Чтобы обойти эти ограничения, можно использовать библиотеку react-error-boundary. Ее преимущество в том, что она позволяет восстановить работу приложения после ошибки. Также есть хук для обработки ошибок в асинхронном коде. Пример работы библиотеки:

import {ErrorBoundary} from 'react-error-boundary'

function ErrorFallback({error, resetErrorBoundary}) {
return (
<div role="alert">
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}

const ui = (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// reset the state of your app so the error doesn't happen again
}}
>
<ComponentThatMayError />
</ErrorBoundary>
)


https://github.com/bvaughn/react-error-boundary
👍15🔥2
Под капотом у Mobx. Пишем свою реактивную библиотеку с нуля

Когда начинаете использовать Mobx, то кажется, что внутри него используется какая-то магия. Но если посмотреть исходники, то окажется, что все написано на JS и используются стандартные объекты.

При создании Observable объекта и вывода его в консоль можно увидеть, что Observable оборачивает значение в Proxy. Внутри Proxy объекта находятся реакции, а внутри реакций находятся наблюдаемые значения и получается рекурсивная зависимость.

Григорий Гаврилов в статье на Хабре объяснил, как работает Mobx под капотом, реализовав основное API библиотеки.

https://habr.com/ru/post/689374/
👍10
Будущее рендеринга в React

В текущий момент есть два подхода для рендеринга приложений на React:

- Client-Side Rendering. Сервер отдает базовый HTML и JS бандл и уже в браузере происходит рендеринг и получение данных по API.

- Server-side rendering. Получение данных и рендеринг приложения происходит на стороне сервера с помощью renderToString, который возвращает HTML для передачи клиенту. После получения HTML и JS на клиенте происходит гидратация.

У обоих из этих подходов есть свои плюсы и минусы в терминах Web Vitals, о которых пишет автор статьи. Новые подходы к рендеру приложения на React предназначены для решения основных минусов текущих подходов.

Streaming SSR. Новое API renderToPipeableStream позволяет в связке с Suspense разделить приложение на сегменты, каждый из которых будет рендериться независимо от остальных. При успешном рендере сегмента, HTML будет передаваться на клиент и вставляться в свое место в DOM.

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

Server components еще находятся в альфа версии и недоступны для использования. Streaming SSR также еще официально не зарелизина. Команда React планирует, что разработчики не будут напрямую использовать новые подходы в своих проектах, т.к. они будут сложными в интеграции. Вместо этого, новые подходы рендера должны появиться в фреймворках, которые предоставят более простой интерфейс для работы. Например, новая версия Next.js Layouts RFC должна будет поддержать новые подходы рендера React.

https://prateeksurana.me/blog/future-of-rendering-in-react/
👍8
Как получать данные в React

Надя Макаревич в своем блоге рассказала об оптимизации запросов на получение данных при инициализации вашего SPA приложения на React. В гайде не рассказывается о какой-то конкретной библиотеке для получения данных, а приводятся примеры паттернов получения данных и их подводные камни.

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

const commentsPromise = fetch('/get-comments');

const Comments = () => {
useEffect(() => {
const dataFetch = async () => {
// just await the variable here
const data = await (await commentsPromise).json();

setState(data);
};

dataFetch();
}, [url]);
}


https://www.developerway.com/posts/how-to-fetch-data-in-react
👍6👎1
RFC: поддержка async компонентов

От Эндрю Кларка вышло RFC о поддержке асинхронных компонентов на сервере и промисов на клиенте через хук use. Эти два больших нововведения могут существенно облегчить работу с API и базами данных в React. Компоненты, которые используют эти возможности, должны будут обернуты в Suspense.

Async/await для серверных компонентов:

async function Note({id}) {
const note = await db.posts.get(id);
return (
<div>
<h1>{note.noscript}</h1>
</div>
);
}


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

Использование хука use для браузерных компонентов:

function Note({id, shouldIncludeAuthor}) {
const note = use(fetchNote(id));

let byline = null;
if (shouldIncludeAuthor) {
const author = use(fetchNoteAuthor(note.authorId));
byline = <h2>{author.displayName}</h2>;
}

return (
<div>
<h1>{note.noscript}</h1>
{byline}
</div>
);
}


Как видно из примера, хук use, в отличие от других хуков, можно вызывать внутри условий, также его можно вызывать внутри циклов. Браузерные компоненты не должны быть определены как async. Чтобы получить данные из промиса, достаточно использовать только хук use.

Это еще не финальный RFC. Остается открытым вопрос с кэшированием данных. Как обещает автор, в будущем будет предложен отдельный RFC по кэшированию, а релизится API запросов будет вместе с API кэширования.

RFC https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
Обсуждение https://github.com/reactjs/rfcs/pull/229
🔥10
Tremor – библиотека для создания дашбордов

Новая библиотека компонентов, которая подойдет для проектов-дашбордов. Она включает в себя разные виды графиков, основные UI компоненты, а также компоненты для создания шаблона – колонки и блоки.

Под капотом используется Tailwind CSS, и есть возможность изменения цвета компонента. Также на сайте уже есть примеры работы библиотеки с готовыми блоками.

https://www.tremor.so/
👍4
React, я люблю тебя, но ты сводишь меня с ума

На хабре вышел перевод статьи Франсуа Занинотто с критикой React.

Автор подсвечивает несколько проблем, которые возникают при разработке React приложений:

- Работа с формами. Для ручного получения данных с формы есть управляемые и неуправляемые инпуты. Чтобы обработать поле инпута, нужно создать отдельный стейт и функцию обработчик. Если захотите использовать библиотеку для работы с формами, то будьте готовы, что в них тоже есть недостатки: некоторые из них уже не поддерживаются, а другие медленно работают или в них слишком запутанная документация.

- Контекст. В useContext отсуствует критически важная вещь – возможность реагировать на изменение только определенных полей объекта, как в Redux useSelector. Это приводит к тому, что приходится разделять контекст на более мелкие части.

- useEffect и useCallback. Требует ручного управления зависимостями, заставляя записывать все используемые переменные в список зависимостей. В ​​Solid.js таких проблем нет.

- Строгость хуков. Хуки нельзя вызывать условно. Хоть и известно, что если компонент с хуком нужно показывать по условию, то его нужно вкладывать в промежуточный компонент для предотвращения рендеринга, но все равно, непонятно зачем это нужно делать. Это просто один из примеров сложности хуков.

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

https://habr.com/ru/company/timeweb/blog/693072/
👍8👎5
Полностью типизированные веб-приложения

Кент С. Доддс рассказывает о сквозном покрытии типов в веб-приложениях, от БД до UI через разные границы: localStorage, сеть, БД.

Например, при получении данных из localStorage нельзя точно определить, какие будут получены данные, т.к. со временем они могут измениться. Если кастовать тип для данных из localStorage, то может возникнуть runtime ошибка, например, из-за того, что нет нужного поля в объекте.

Кент С. Доддс рекомендует валидировать данные после получения их из какого-либо внешнего источника. Один из вариантов – библиотека zod, которая позволяет описать схему данных и получить ее тип. Если окажется, что объект не совпадает со схемой, то будет выкинута ошибка. В ином случае вы будете уверены, что работаете с правильными данными.

import { z } from "zod"

const Ticket = z.object({
workshopId: z.string(),
attendeeId: z.string(),
discountCode: z.string().optional()
})
type Ticket = z.infer<typeof Ticket>

const rawTicket = JSON.parse(localStorage.get('ticket'))
const result = Ticket.safeParse(rawTicket);
if (result.success) {
const ticket = result.data
// ^? Ticket
} else {
// result.error will have a handy informative error
}


https://www.epicweb.dev/fully-typed-web-apps
🔥10