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

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

import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';

function shallowEqual(objA: mixed, objB: mixed): boolean {
/**
* Проверка равенства примитивных значений.
*/
if (is(objA, objB)) {
return true;
}

/**
* Проверка на то, что текущие аргументы являются объектами и не равны null.
*/
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}

/**
* На этом шаге мы уверены что работаем с объектами, получаем ключи объекта.
*/
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

/**
* Сравниваем количество ключей объектов.
*/
if (keysA.length !== keysB.length) {
return false;
}

/**
* Сравнение значения объекта для каждого ключа объекта A с объектом B.
* Проверяется наличие ключа объекта A в объекте B.
*/
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}

return true;
}


Выводы:
- Для сравнения объектов используется Object.is вместо строгого равенства.
- В поверхностном сравнение пустой массив и пустой объект равны.
- Объект с индексами в ключах и массив с такими же значениями одинаковы, т.е. { 0: 2, 1: 3 } равен [2, 3].
- Из-за использования Object.is вместо ===, +0 и -0 не равны, а Number.NaN и NaN равны.

https://www.chakshunyu.com/blog/how-does-shallow-comparison-work-in-react/
👍4
useTilg – хук для отладки компонентов

Tiny Logger – небольшой хук для отладки компонентов. Выводит информацию, когда компонент был отрендерен (с какими пропсами), смонтирован и размонтирован. Также хук позволяет отследить как изменились стейты при рендере.

import { useState } from 'react'
import useTilg from 'tilg'

function MyApp() {
const [input, setInput] = useState('')
const [count, setCount] = useState(0)

useTilg()`input = ${input}, count = ${count}`

return (
<>
<input onChange={(e) => setInput(e.target.value)} value={input} />
<button onClick={() => setCount(count + 1)}>{count}</button>
</>
)
}


https://github.com/shuding/tilg
👍10👎2
Почему нельзя вызывать хуки по условию

Например, компонент по определенному условию вернул null. Это невалидный код на React. Но почему так делать нельзя?

const ConditionalComponent = (props) => {
if (!props.visible) return null;

const [value, setValue] = useState();
}


В первую очередь стоит помнить, что babel компилирует код JSX в обычные функции createElement:

const MyComponent = () => {
return <div>Hello world</div>
}

// babel компилирует в
const MyComponent = () => {
return React.createElement("div", null, "Hello world");
};

React рендерит компоненты, вызывает createElement, создает Virtual DOM и выводит пользователю в браузер отрендеренные элементы. Рендер – это просто вызов функции функционального компонента. Функциональный компонент вызывается, возвращается вызов createElement, React сравнивает текущее и новое дерево VDOM и коммитит изменения в DOM.

Важно понимать, что функциональный компонент это функция, которую вызывает React. Это создает проблему: как сохранить данные в стейте при вызовах функции (рендере). Например, следующий код при каждом вызове будет писать в консоль “1”:

const Test = () => {
let a = 1;
console.log(a);
a++;
}

Test(); // 1
Test(); // 1
Test(); // 1


Для того чтобы сохранять состояние стейта между рендерами компонента, React сохраняет состояние во внешнем объекте по ID хука, примерно так:

const state = [];

// Уникальный ID хука, счетчик
let idx = 0;

function useState(init) {
state[idx] = state[idx] ?? { val: init };

return [
state[idx].val,
data => state[idx].val = data
]
}

function Test() {
const [name, setName] = useState('React');
}

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

Более подробно про хуки написано в блоке Дэна Абрамова: https://overreacted.io/why-do-hooks-rely-on-call-order/
👍7
RFC шаблонов в Next.js

Next.js планирует изменить свою систему роутинга. Это поможет адаптировать фичи React 18 и другие новые фичи.

Добавятся новые конвенции именования. Папка ./pages останется, но появится папка ./app, в которой будут работать новые фичи, например, Server Components.

Появится поддержка вложенных роутов/шаблонов с использованием компонента layout.js. Также появится компонент page.js, который будет использоваться для отображения страницы роута.

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

https://nextjs.org/blog/layouts-rfc
👍4🔥3
Мигрируем с Jest на Vitest

Vitest – это фреймворк тестирования, использующий в качестве сборщика Vite.js. Vitest использует совместимый с Jest API и поддерживает из под коробки основные фичи для тестирования (моки, снапшоты, покрытие). Также фреймворк поддерживает многопоточность, используя библиотеку tinypool.

Vitest особенно актуален, если вы используете в качестве сборщика приложения Vite.js. В этом случае для запуска тестов и сборки приложения будет использоваться один и тот же конфиг приложения (vite.config.js).

Если вы используете Jest, то совсем не сложно перейти на Vitest, учитывая что их API похоже. Катал Мак Доннача описал шаги для миграции с Jest на Vitest:

https://cathalmacdonnacha.com/migrating-from-jest-to-vitest
👍3👎1
Обзор библиотек управления состоянием

Библиотеки управления состоянием определяют, как приложение будет делиться своим состоянием. Оно может быть глобальным или локальным для определенного участка приложения.

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

Альберт Гао в своем блоге составил обзор популярных библиотек управления состоянием. Библиотеки разбиты на категории: по структуре сторов (глобальный, несколько или атомарный), способу чтения записей из стора (селектор или геттер) и способу обновления стора (React-подобный API, экшен объект или инкапсулированный метод стора).

https://www.albertgao.xyz/2022/02/19/react-state-management-libraries-2022/
👍51
Обновление React Router v6.4.0-pre

В новую минорную версию React Router v6.4 было добавлено много новых абстракций из Remix: action, loader.
Если для страницы будут нужны данные, то их можно вернуть в функции loader, а получить в самом компоненте через хук useLoaderData. Функция action используется для мутаций. Мутации выполняются через отправку формы, используя компонент Form, или через хук useSubmit.


export async function loader({ params }) {
const note = await getNote(params.noteId);
if (!note) throw new Response("", { status: 404 });
return note;
}

export async function action({ params }) {
await deleteNote(params.noteId);
return redirect("/new");
}

export default function Note() {
const note = useLoaderData();
return (
<div>
<h2>{note.noscript}</h2>
<div>{note.content}</div>
<Form method="post">
<button type="submit">Delete</button>
</Form>
</div>
);
}

createRoot(document.getElementById("root")).render(
<DataBrowserRouter>
<Route path="/" element={<Root />} loader={rootLoader}>
<Route
path="note/:noteId"
element={<Note />}
loader={noteLoader}
action={noteAction}
errorElement={<div>Note not found</div>}
/>
</Route>
</DataBrowserRouter>
);


https://beta.reactrouter.com/en/remixing/getting-started/data
2🔥2👎1
HolyJS 2022 Spring пройдет уже через неделю. 8–10 июня, на 80% online, а 23 июня в Санкт-Петербурге будет offline-день. 

Вас ждут 24 доклада от разработчиков из VK, Яндекса, QIWI, Тинькофф и других компаний. Поговорим о фреймворках, перформансе, техническом долге, архитектуре, работе с Node.js, serverless-подходе, графике, TypeScript и wasm.

Среди докладов:
«Безопасность данных: построение архитектуры при интеграции алгоритмов шифрования в приложении для финансового учета»;
«Wasm GC»;
«TypeScript: компромиссы проектирования языка»;
«Поймать дзен, работая над дизайн-системой Яндекс.Дзена»;
«Serverless — это не страшно!»;
«Нейрофизиология сложности кода».

Подробности и готовая программа — на сайте конференции.
Покупая билет на конференцию из вкладки «для частных лиц» используйте промокод reactnotes2022JRGpc для скидки.
Создание мини-версии Next.js

Джонас Гальвес в своем блоге рассказал. как создать свою мини-версию фреймворка Next.js. В своем эксперименте автор сосредоточился на двух основных фичах:
- роутинг на основе файлов в папке /pages;
- возможность изоморфного получения данных, используя функцию getServerSideProps();

Для сборки проекта используется Vite. Для получения списка файлов и его экспортов используется метод import.meta.globEager('/pages/**/*.jsx’), который возвращает массив объектов страниц с экспортами. Роуты формируются автоматически с использованием react-router-dom.

https://hire.jonasgalvez.com.br/2022/may/18/building-a-mini-next-js/
👍73
Работа с 3D моделями в Three.js

Вводный гайд по работе с 3D моделями с использованием библиотеки react-three-fiber. Разбирается как подключать glTF модели и отображать их на канвасе. Для работы со светом есть компоненты <ambientLight> (заполняет светом всю сцену) и <pointLight> (свет по определенным точкам, как лампа). Для ручного управления камерой мышью используется компонент <OrbitControls>.

https://betterprogramming.pub/working-with-3d-model-in-three-js-e228621141af
👍3
Как работает хук useInsertionEffect в React 18

CSS-in-JS библиотеки генерируют стили на лету и вставляют их в документ. Для этих библиотек важно знать, в какой момент можно вставлять теги <style> в документ, т.к. это может повлиять на производительность.

При добавлении или удалении CSS правил браузер пересчитывает стили у всех элементов. Для того чтобы избежать лишнего пересчета стилей для элементов на странице при изменении CSS правил, необходимо изменять их одновременно с другим изменением DOM, например, когда React мутирует DOM, перед чтением макета (например clientWidth) и до отрисовки в браузере.

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

Внутри хука можно изменять глобальные DOM элементы, такие как <style> или <defs>. Он запускается перед хуком useLayoutEffect. Основное предназначение данного хука – изменение стилей в CSS библиотеках.

function useCSS(rule) {
useInsertionEffect(() => {
if (!isInserted.has(rule)) {
isInserted.add(rule);
document.head.appendChild(getStyleForRule(rule));
}
});
return rule;
}
function App() {
let className = useCSS(rule);
return <div className={className} />;
}


https://blog.saeloun.com/2022/06/02/react-18-useinsertioneffect
👍7
Бесплатный день HolyJS 2022 Spring!

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

Community Day на HolyJS 2022 Spring — 10 июня доступ свободный

В билет бесплатного дня входят:
4 доклада: например, про server-driven UI в вебе и преобразование TypeScript в Java.
Воркшоп: спикеры покажут, как написать приложение для конвертации криптовалюты с микросервисной архитектурой с помощью gRPC на платформе Node.js.
Дискуссии после докладов.
Возможность поучаствовать в играх, квизах, конкурсах и других активностях от партнеров конференции — там можно не только круто провести время, но и получить ценные призы.
Чаты, где сидят сотни ваших коллег со всего мира.

Для участия в Community Day нужно только зарегистрироваться — для этого переходите по ссылке.
Listener API в Redux Toolkit

В RTK с самого начала используется redux-thunk в качестве дефолтного мидлвара для написания синхронной и асинхронной логики вне компонента. Однако, thunk не позволяет вызывать side effect функцию в ответ на dispatch экшена. Для таких целей обычно используется redux-saga.

В redux-toolkit v1.8.0 появилось новое listener API, которое позволяет создавать мидлвар для подписки на экшен или изменении стейта. Концептуально новое API похоже на useEffect, которое запускается в ответ на изменении стора.

Подписка может быть создана как в процессе создания стора через listenerMiddleware.startListening(), либо динамически в рантайме через dispatch(addListener()).


// Create the middleware instance and methods
const listenerMiddleware = createListenerMiddleware()

// Add one or more listener entries that look for specific actions.
// They may contain any sync or async logic, similar to thunks.
listenerMiddleware.startListening({
actionCreator: todoAdded,
effect: async (action, listenerApi) => {
// Run whatever additional side-effect-y logic you want here
console.log('Todo added: ', action.payload.text)

// Can cancel other running instances
listenerApi.cancelActiveListeners()

// Run async logic
const data = await fetchData()

// Pause until action dispatched or state changed
if (await listenerApi.condition(matchSomeAction)) {
// Use the listener API methods to dispatch, get state,
// unsubscribe the listener, start child tasks, and more
listenerApi.dispatch(todoAdded('Buy pet food'))
listenerApi.unsubscribe()
}
},
})


Обзор и сравнение с redux-saga:
https://blog.logrocket.com/redux-toolkits-new-listener-middleware-vs-redux-saga/
👍7
TypeScript типы для React

Использование TypeScript при написании компонентов в React может существенно облегчить разработку и поддержку кода. Небольшая подборка утилитарных типов для работы с React:

- React.ComponentProps. Иногда нет доступа к типу пропсов компонента: например, внешняя библиотека, которая дает доступ только к самому компоненту. Чтобы узнать тип пропсов компонента можно использовать React.ComponentProps.

import { ComponentProps } from "react";
import { ExternalComponent } from "external-lib";

type InternalComponentProps = ComponentProps<typeof ExternalComponent> & {
outerClassName: string;
};


- React.MouseEventHandler. Используется для типизации колбека события мыши.

import { MouseEventHandler, } from "react";

type ComponentProps = {
caption: string;
onClick: MouseEventHandler<HTMLButtonElement>;
};

const Component = (props: ComponentProps) => (
<div>
<button onClick={props.onClick}>{props.caption}</button>
</div>
);


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

type ComponentProps = Pick<Something, "propA" | "propB" | "children"> & {
wrapperClassName?: string;
}

export const Component = (props: ComponentProps) => (

);


https://www.chakshunyu.com/blog/7-typenoscript-utility-types-for-react-developers/
🔥7👍1
Как использовать useMemo и useCallback

Скорее всего вы знакомы с хуками useMemo и useCallback. Эти хуки используются практически в каждом проекте, и чем больше они используются, тем больше усложняют код компонента. Многие используют эти хуки, не до конца понимая зачем они нужны, и пишут их, потому что все так делают, так проще.

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

Основная задача хуков useMemo и useCallback- это мемоизация между ре-рендерами компонента. Если какое-то непримитивное значение или функция объявлены в теле компонента, то каждый раз при рендере будет создаваться новая ссылка на значение. При мемоизации значение будет закэшировано во время первого рендера и при последующих рендерах будет возвращаться ссылка на закэшированное значение. Это удобно при использовании с useEffect:

const Component = () => {
// сохранение ссылк между ре-рендерами
const a = useMemo(() => ({ test: 1 }), []);

useEffect(() => {
// будет вызван когда значение “a” изменится
}, [a]);
};


Использование хуков useMemo и useCallback полезно только при ре-рендерах. При первом рендере React кэширует значения, поэтому рендерится компонент немного медленнее.
Один из популярных вариантов использования хуков мемоизации – это предотвращение ререндера компонента путем мемоизации пропсов. Например:

​​const Item = ({ item, onClick }) => <button onClick={onClick}>{item.name}</button>;

const Component = ({ data }) => {
const value = useMemo(() => ({ a: someStateValue }), [someStateValue]);
const onClick = useCallback(() => {
console.log(value);
}, [value]);

return (
<>
{data.map((d) => (
<Item item={d} onClick={onClick} />
))}
</>
);
};


В примере выше использование хуков нецелесообразно. Компонент ререндерится, если родительский компонент ререндерится. Чтобы предотвратить ререндер компонента, нужно использовать функцию React.memo. Компонент обернутый в React.memo будет ререндерится, только если изменятся пропсы. Поэтому все пропсы для этого компоненты должны быть мемоизированны.

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

https://www.developerway.com/posts/how-to-use-memo-use-callback
👍5
Приглашаем всех JavaScript-разработчиков 23 июня в Питер на офлайн-день конференции HolyJS 2022 Spring!

Вас ждут:
5 докладов.
Дискуссии после каждого доклада.
Lightning talks: мини-доклады от участников конференции.
Живое общение, нетворкинг и обмен знаниями.
Стенды партнеров с разными активностями, мини-докладами и мерчем.

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

Подробности, полную программу и билеты вы найдете по ссылке.

Если покупаете билет за свой счет (а не за счет компании), то воспользуйтесь промокодом для получения скидки: reactnotes2022JRGpc
👍31
Пример использования startTransition и 3D-рендеринга

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

Цель переходных обновлений состоит в том, чтобы создать более отзывчивый интерфейс.

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

Конкурентный рендеринг может выполнять следующие вещи:
- Yielding: React периодически ставит работу рендера на паузу, чтобы дать браузеру сделать другую работу.
- Прерывание: при появлении срочных задач React ставит рендер несрочной задачи на паузу и начинает рендерить срочную задачу, чтобы UI оставался отзывчивым.
- Отбрасывание старых результатов: если данные стейта для перехода устарели, то React отбрасывает старый рендер и начинает рендер по новой для свежего стейта.

Доу-Чи Лиу в своем блоге показал, как использовать API конкурентного рендеринга startTransition для рендера тяжелого компонента с 3D моделью с визуализацией связей между пользователями в Github.

Приложение содержит два компонента:
- поисковая строка пользователя Github;
- сцена с 3D визуализацией связей на three.js и react-three-fiber;

https://dawchihliou.github.io/articles/stress-testing-concurrent-features-in-react-18
👍4
Plasmo – React фрейморвк для создания браузерных расширений

Plasmo – новый фреймворк на React и TypeScript для создания браузерных расширений. Особенности:
- live-reloading при дев разработке;
- файлы окружений;
- автоматический деплой;
- автоматическая генерация manifest.json;
- поддержка всех основных браузеров;

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

https://docs.plasmo.com/
👍6
This media is not supported in your browser
VIEW IN TELEGRAM
Jest Preview для дебага тестов

При написании тестов с использованием Testing Library приходится писать много запросов в DOM, держа в голове структуру страницы, которую рендерит React для теста.

Для того чтобы видеть результат рендера во время теста, можно использовать библиотеку Jest Preview. После установки библиотеки и настройки конфигов, необходимо во время запуска тестов запускать команду jest-preview, которая будет стартовать сервер для превью тестов. В самих тестах, для отображения в браузере текущего DOM компонента, используется функция preview.debug().

Стоит учитывать, что в текущий момент библиотека активно развивается и может работать нестабильно.

https://www.jest-preview.com/
👍7
React Labs: над чем мы работали – июнь 2022

React Labs — это новая серия статей: команда React хочет чаще делиться своим прогрессом. Команда работает над несколькими проектами и делится информацией о текущем состоянии. Это не дорожная карта, а исследовательские проекты, которые могут попасть в релиз.

- Серверные компоненты. Вместо использования внешней библиотеки для I/O будет использоваться async/await для лучшей совместимости. Откажутся от расширения у серверных файлов .server.js в пользу аннотаций внутри файла. Работают над унификацией для поддержки сборщиков Webpack/Vite.

- Asset Loading. Интеграция загрузки изображений, шрифтов, стилей и Suspense. Загрузка ассетов должна блокировать отображение, но не конкурентный рендеринг и стриминг.

- React Optimizing Compiler. Представили компилятор React Forget на React Conf 2021. Работа идет, недавно переписали компилятор на новую архитектуру.

- Offscreen. Предлагает новые возможности, например, мгновенный переход – рендер компонента в фоне.

https://reactjs.org/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.html
👍6
Инверсия управления и внедрение JSX через Context API

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

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

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


const TableColumnActions = props => {
const { row, column } = props;
const [openActions, setOpenActions] = useState(false);
useOnClickOutside(popupRef, () => setOpenActions(false));
const renderMenu = useTableActionsContext();

const menuOptions = renderMenu({
row,
column,
styles,
});

return (
<div className="relative">
{openActions ? (
<ul className={styles.actionsMenu}>{menuOptions}</ul>
) : null}
</div>
);
};


https://theroadtoenterprise.com/blog/react-inversion-of-control-and-jsx-injection-via-context-api
👍6