Ayub Begimkulov - уроки по JS
3.11K subscribers
29 photos
212 links
По вопросам и деловым предложениям писать на @ayub_begimkulov
Download Telegram
Хочу поделиться небольшим советом, как я обычно пишу forceUpdate на хуках.


const [,forceUpdate] = useReducer(v => v + 1, 0);


Наверное, многие из вас видели уже что-то подобное в моих уроках. Но я думаю, у многих из вас есть вопрос, Айюб, а когда же вообще пригодится forceUpdate?

Вот несколько use case’ов, в которых я его юзаю:

1) Подписка на сторонние сервисы в effect’ах, при изменении которых надо обновить компонент. Что-то похожее делал react-redux в своем прошлом мажоре (до появления useSyncExternalStore).

2) Тестинг того, насколько хорошо мемоизируется компонент. Просто создаешь forceUpdate, пихаешь его в window и смотришь в devtools, какие компоненты ререндарятся без нужды.

3) Бывает полезно при использовании некоторых библиотек, например, для виртуализации. Обычно, они отдают этот метод наружу сами, через ref.

Use case такой, что у компонента есть мемоизация, пропсы не поменялись, но его все равно надо перерендерить, так как мемоизация не учитывает все факторы.

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

Есть ли еще какие-то ситуации, когда вам приходится использовать forceUpdate?

#react #devtips
12👍5🔥2🍓1
Пока компилируется проект, реши написать еще один пост по наболевшей теме на ревью и в целом, во многих проектах.

Это тема касающаяся функционального программирования. Наверное многие из вас знают хайп вокруг функционального программирования, чистых функций и immutable data structures.

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

Приведу пример кода:


const stickyRows = Object.entries(this.props.stickyRows || {})
.map(([start, end]) => [parseInt(start, 10), end])
.filter(([start, end]) => {
return start < lastRow && (!end || end >= lastRow);
})
.map(([index]) => index);


Кажется, все написано, как нужно - нету мутаций, все функции чистые, все по канонам ФП.

Но давайте посмотрим на этот код с другой стороны…


Object.entries - O(n) память, O(n) cpu.
map - O(n) память, O(n) cpu, а тут их еще 2.
filter - тоже самое.


В сумме, у нас выходит 4 итерации и 4 массива, 3 из которых будут собраны сборщиком мусора!

Можно написать тот же самый код в “императивном” стиле, и получить 1-у итерацию и 0 лишних массивов.


const stickyRowsArray: number[] = [];

for (const key in stickyRowsMap) {
if (!hasOwn(stickyRowsMap, key)) {
continue;
}

const start = parseInt(key);
const end = stickyRowsMap[key];

const isInRange = start < lastRow && (!end || end >= lastRow);

if (!isInRange) {
continue;
}

stickyRowsArray.push(start);
}


Можно конечно долго спорить о том, что первый способ лучше читается и тд. Но если адекватно назвать все переменные и не делать вложенные if’ы, то такой код будет не менее читаем, чем функциональный.

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


function flattenOne(arrays) {
return arrays.reduce((result, array) => result.concat(array), []);
}


Если вдруг кто не знает concat - immutable метод, который объединяет 2 массива в один. В таком случае у нас вообще будет кубическое время и память. O((N^2+2MN)/2), если быть точным, где M - размер вложенных массивов, а N - размер arrays.

И к тому же, в данном случае result.push никак не испортил бы иммутабельность функции flattenOne. reduce создает полностью новый массив, переданный в параметр массив тоже arrays никак не мутируется.

В итоге, мутация у нас есть только внутри функции, где код полностью под нашим контролем. В таких НОРМАЛЬНО и даже НУЖНО использовать мутирующие методы.

Основаня идея immutable кода, это то, что не нужно лишний раз мутировать те переменные, которые пришли вам в конфиги/параметры/конструктор, любые внешние переменные, которые не контролируются вашим куском кода.

Вот такие дела.

#devtips #fp #react
19👍10🔥1💯1🍓1
Всем привет!

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

Самый очевидный пример - это useCallback:


function useCallback(cb, deps) {
return useMemo(() => cb, deps);
}


Однако если задуматься по глубже, то хуки, которые реально нужны из коробки - это useState, useEffect, useLayoutEffect, useContext, useDebugValue, ну и некоторые новые хуки, добавленные в React 18.

Давайте в качестве упражнения напишем хуки useReducer, useMemo, useCallback сам.

И так, useCallback мы уже покрыли, давайте следующим шагом покроем useMemo:


function areDepsEqual(prevDeps, deps) {
if (prevDeps.length !== deps.length) {
return false;
}

return prevDeps.every((item, index) => item === deps[index]);
}

function useMemo(factory, deps) {
const prevDepsRef = useRef(null);
const result = useRef(null);

const prevDeps = prevDepsRef.current;

useEffect(() => {
prevDepsRef.current = deps;
});

if (!prevDeps || !areDepsEqual(prevDeps, deps)) {
result.current = factory();
}

return result.current;
}


Тут мы как раз пользуемся тем, что useRef возвращает ссылку на один и тот же объект, поэтому мы можем просто сравнить массив зависимостей и в случае чего обновить значение в объекте.

Хорошо, давайте теперь напишем useReducer:


function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const reducerRef = useRef(reducer);

useLayoutEffect(() => {
reducerRef.current = reducer;
});

const dispatch = useCallback((action) => {
setState((state) => reducerRef.current(state, action));
}, []);

return [state, dispatch];
}


Как мы видим, тут тоже логика получается не такой сложной. Мы просто используем переданный reducer для того, что создавать новый стейт.

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

Однако какие еще хуки мы могли бы написать сами?

useEffect`/`useLayoutEffect`/`useInsertionEffect - все требуют вызова в определенный момент жизни компонента, поэтому их тут самому не написать.

useDebugValue`/`useDeferredValue`/`useTransition - тоже все завязаны на внутренности React.

useState должен как-то прикрепляться к компоненту и хранить значение между рендерами, так что тут тоже ничего не сделаешь.

А что по поводу useRef?

Он ведь просто создает мутабельный объект, привязанный к компоненту. Наверное простым решением было бы использование useMemo:


function useRef(value) {
const [ref] = useMemo(() => ({ current: value }), []);

return ref;
}


Но для реализации useMemo мы уже используем useRef...

Каким еще образом мы можем создать наш объект так, чтобы он был привязан к компоненту и не изменялся от рендера к рендеру?

И тут мне пришла странная идея:


function useRef(value) {
const [ref] = useState({ current: value });

return ref;
}


По идее нам ничто не мешает создать наш объект один раз через useState и дальше уже его мутировать. Да и по канонам React стейт мутировать нельзя. Но это связанно с тем, что мутации не поведут за собой обновление компонента, однако другие части приложения будут использовать последнее значение стейта.

Но для реализации useRef тут никаких проблем не должно быть. Наш “рефовый” стейт и так никогда не будет обновляться.

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

#devtips #react
👍4320🔥7💯3🍓1
Сидел тут лазил в исходинках React (хотел уточнить один момент касательно батчинга), и наткнулся на такую вещь, как navigator.scheduling.isInputPending.

Решил загуглить, оказалась, что это АПИ которое позволяет узнать, есть ли какие-то ивенты от пользователя, ожидающие обработку.

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

Подробнее про саму эту фичу можно почитать вот тут — https://developer.chrome.com/articles/isinputpending/

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

UPD: кажется, что есть пропоусал на добавление в стандарты — https://github.com/WICG/is-input-pending

#devtips #js #react
👍162🔥2💯21🍓1
Всем привет!

Сегодня хотел бы поговорить про полную мемоизацию всех колбэков в компонентах и поделиться своим мнением по данному вопросу.

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

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

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

Самый очевидный наверное в том, что есть какой-то стандарт в проекте. Нету лишних комментов на ревью о ненужном useCallback. Также не идут споры о том, стоит ли что-то мемоизировать или нет.

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

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

А в других случаях я бы предпочел все равно использовать мемоизацию только там, где это нужно. Можно придерживаться таких правил:
- Мемоизируем все пропсы для компонентов из внешних библиотек (если точно не знаем, что там внутри).
- Мемоизируем пропсы для компонентов, которые используют memo.
- Если есть пропсы откуда-то с верхнего уровня и они не мемоизированы - можно использовать useEvent/useLatest для того, чтобы избежать лишних обновлений.

Также хотел бы отметить, что если уж и мемоизировать все подряд, то намного проще использовать самописные хуки useEvent и useLatest, так как большинство колбэков вообще никогда не должны обновляться.

#devtips #react #hooks
24👍8🔥6💯2🍓1
Всем привет!

Наверняка многие из вас уже успели посмотреть мое последнее видео про всплывающие окна.

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

Так вот, впервые я увидел данную технику в коде react-redux, и тогда я не совсем понял, для чего это вообще нужно.

Затем через какое-то время наткнулся на issue про stale props и zombie children и на пост приложенный ниже о том, как данная проблема была решена для connect.

В целом, статья очень полезная для понимания redux и многих его концептов.

Да, он уже не настолько актуален после появления react-query и других cache management решений, однако все равно считаю ее очень полезной. Как минимум для общего кругозора.

А если повезет, может как и я извлечете пару полезных идей для своих проектов!

https://kaihao.dev/posts/Stale-props-and-zombie-children-in-Redux

#devtips #react #redux
18👍82💯2👌1🍓1
Всем привет!

Сегодня один из подписчиков скинул мне ссылку на доку из библиотеки с универсальными хуками. Там сказано, что хук useSafeState станет deprecated, так как warning о обновлении стейта в демонтированном компоненте был удален в react 18.

При первом прочтении я удивился, так как об этих изменениях я не слышал, и в доке про 18-ую версию тоже ни слова об этом (даже в секции changelog, где есть ссылки на коммиты с изменениями).

Однако в доке библиотеки была ссылка на discussions, где Ден Абрамов пишет про удаление ворнинга (про причины можете сами почитать по ссылке ниже). Также сам проверил в коде, логов больше никаких нету.

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

https://github.com/reactwg/react-18/discussions/82

#devtips #react
11👍6💯2🏆1🍓1
Сегодня помогал человеку с реализацией draggable canvas (по типу миро, но на html).

Столкнулся с проблемой, когда код внутри setState колбэка вызывался 2 раза.

Код выглядел примерно так:

setPosition(position => {
const deltaX = e.clientX - prevMousePosition.x;
const deltaY = e.clientY - prevMousePosition.y;
prevMousePosition = {
x: e.clientX,
y: e.clientY,

}

return {
x: position.x + deltaX / scale,
y: position.y + deltaY / scale,
}

В итоге при первом вызове стейт вставал в правильное значение, а при втором — все сбрасывалось, так как prevMousePosition был уже таким же, что и clientX/clientY из ивента.

Благо из-за серых логов быстро смог понять, в чем причина. Но все равно выглядит очень странно.

Да, по задумке колбэк должен быть pure function. Но почему нету никакого лога о том, что второй вызов выдал другой результат?

В общем, при обновлении стейта будьте внимательнее.

#devtips #react
👍18🏆21👎1🔥1🍓1