Лови "анализ кучи", запущен код из примера автора. Без дополнительных мемоизаций. Только дебаг лог в конструкторе биг-даты.
Накликано 35 обьектов биг-дата - 5 обьектов в куче
Накликано 110, в куче 3
В памяти лежит максимум 5 объектов биг даты (даже 6 в этот раз поймать не смог) Под сборку мусора они попадают в итоге.
Уте́чка па́мяти (англ. memory leak) — процесс неконтролируемого уменьшения объёма свободной оперативной или виртуальной памяти компьютера, связанный с ошибками в работающих программах, вовремя не освобождающих память от ненужных данных, или с ошибками системных служб контроля памяти.
Где утечка памяти если, если память освобождется? Ну если с ростом числа созданных объектов был бы, пусть и не линейный, но рост количества объектов в куче - тогда да. Хоть 20 хоть 500 объектов накликайте - в куче будет максимум 5. Ну или 6 если получится "удачно" сделать снапшот памяти. Откуда они там берутся и почему не "удаляются" сразу? Кто захочет - разберется. И вот про это я бы статью прочитал. А так нет ни коварности, ни утечки памяти.
Ну на Хабре нет возможности загрузить гифку на 30 мегабайт. Я изначально записал видео включая анализ кучи. (Потому и указал что максимум видел 6 объектов в памяти, что кстати не так уж и много.) Но как по мне тут и консоль лога достаточно что-бы понять что объект создан 1 раз. А раз был создан 1 экземпляр, то и в куче он будет внезапно 1.
В конце концов возьми и сам "проанализируй" (как по мне там и анализировать то нечего). И приведи пример кода который действительно докажет существование указанной проблемы. А пока-что статья даже близко не оправдывает заголовок.
На приведённом вами скриншоте я не вижу указания bigData в списке зависимостей useCallback.
Смотри код из статьи, там есть третий обработчик клика (без мемоизации, но он зависит от биг-даты), на мой скрин он не влез и по понятным причинам я делал акцент на своих вставках (статью читали?).
Полный код
import { useState, useCallback, useMemo } from "react";
class BigObject {
constructor() {
console.debug("new BigObject created");
}
public readonly data = new Uint8Array(1024 * 1024 * 10);
}
export const HabrExample = () => {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
// only 1 instance of BigObject will be created
const bigData = useMemo(() => new BigObject(), []);
// instance created on each render
// 5-6 objects may be stored in memory
// const bigData = new BigObject(); // 10 МБ данных
const handleClickA = useCallback(() => {
setCountA(countA + 1);
}, [countA]);
const handleClickB = useCallback(() => {
setCountB(countB + 1);
}, [countB]);
// Этот код демонстрирует проблему
const handleClickBoth = () => {
handleClickA();
handleClickB();
console.log(bigData.data.length);
};
return (
<div>
<button onClick={handleClickA}>Increment A</button>
<button onClick={handleClickB}>Increment B</button>
<button onClick={handleClickBoth}>Increment Both</button>
<p>
A: {countA}, B: {countB}
</p>
</div>
);
};
Ещё раз. Где??? Если правильно написать код - лишние объекты не создаются. И никаких 100500 экземпляров биг даты в памяти. (делал ~20 снапшотов памяти, видел максимум 6 экземпляров биг даты). Ниже скрин в котором биг-дата создаётся не более 1 раза.
"цепочка замыканий" решается правильным использованием всех мемоизационных хуков (включая useMemo) В тексте статьи то ли не хватает адекватного примера, то ли проблемы описаной в заголовке не существует. Лично я склоняюсь ко второму варианту.
А если уж надо при каких-то обстоятельствах пересоздавать биг-дату. Мемоизацию зависимостей биг-даты (всё тем жеuseMemo) и/или стейтменеджер никто не отменял.
P.S. Реакт делает компилятор который будет делать мемоизацию за разработчика.
In order to optimize applications, React Compiler automatically memoizes your code. You may be familiar today with memoization through APIs such as useMemo, useCallback, and React.memo. With these APIs you can tell React that certain parts of your application don’t need to recompute if their inputs haven’t changed, reducing work on updates. While powerful, it’s easy to forget to apply memoization or apply them incorrectly. This can lead to inefficient updates as React has to check parts of your UI that don’t have any meaningful changes.
Какая растущая цепочка замыканий. Куча бреда в статье и в коментариях.
Создание 10мб объекта происходит при каждом рендере компонента. Именно его надо мемоизировать при помощи useMemo в первую очередь. Тогда и с useCallback не будет проблем. Мемоизационные хуки (внезапно) держат в памяти не только последний результат, а и несколько предыдущих (возможно и без ограничений вообще) и очищаются при удалении компонента из дерева. И это не про реакт, в так впринцыпе работает мемоизация.
Это
const bigData = new BigObject()
Должно выглядеть так
const bigData = useMemo(() => new BigObject(), []);
Или вынесено из компонента (например в стейт-менеджер).
Автор я понимаю что ты пришел из .Net, но тут проблема не в понимании реакта, а в понимании мемоизации. Посмотри как работает например код функции memoize из библиотеки lodash. Ты увидишь что происходит мемоизация (сохранения в памяти) нескольких результатов вызова с привязкой каждого к аргументам (зависимостям). Считай что это кеш, который конечно можно переполнить. И в твоём примере ты постоянно создаёшь новый экземпляр bigData и передаешь его как зависимость в useCallback в итоге твоя мемоизация вообще не работает и забивает память. В случае с мемоизированым bigData - если у нехо нет зависимости от пропсов (или чего-то ещё) то он будет создан один раз для экземпляра компонента. И да, того же можно добиться с useRef, но там дело не в больших объектах, а в мутабельности. При мутации объекта который хранится в useRef - реакт вообще не будет ничего рендерить. Есть задачи которые можно решить и тем и тем, но по принципу работы это разные вещи, и далеко не всегда взаимозаменяемые.
По поводу разделения Html, js, css, мне всё равно импонирует подход vue, то что весь компонент, конкретно его стили, и внутрений скриптинг, типо замороченых анимаций по клику, пишутся в одном файле, и если я захочу переписать полностью один вид кнопок, не затрагивая остальные, мне нужно ходить в общий css, я это могу сделать в одном файле, а в глобальный css вынести те самые переменные (css custom properties) цветов, шрифтов, отступов, теней и всего остального. Я пока только изучаю vue, но насколько я понимаю проблем с тем что бы забрать значения этих переменных из глобального css не будет. Именно по этой причине я начал учить Vue вместо реакта или господи помилуй Angular
Лови "анализ кучи", запущен код из примера автора. Без дополнительных мемоизаций. Только дебаг лог в конструкторе биг-даты.
В памяти лежит максимум 5 объектов биг даты (даже 6 в этот раз поймать не смог) Под сборку мусора они попадают в итоге.
Где утечка памяти если, если память освобождется? Ну если с ростом числа созданных объектов был бы, пусть и не линейный, но рост количества объектов в куче - тогда да. Хоть 20 хоть 500 объектов накликайте - в куче будет максимум 5. Ну или 6 если получится "удачно" сделать снапшот памяти. Откуда они там берутся и почему не "удаляются" сразу? Кто захочет - разберется. И вот про это я бы статью прочитал. А так нет ни коварности, ни утечки памяти.
Ну да, обработчик с коментов автора статьи: "
// Этот код демонстрирует проблему
" - ни на что не влияет.... (Facepalm)Ну на Хабре нет возможности загрузить гифку на 30 мегабайт. Я изначально записал видео включая анализ кучи. (Потому и указал что максимум видел 6 объектов в памяти, что кстати не так уж и много.) Но как по мне тут и консоль лога достаточно что-бы понять что объект создан 1 раз. А раз был создан 1 экземпляр, то и в куче он будет внезапно 1.
В конце концов возьми и сам "проанализируй" (как по мне там и анализировать то нечего). И приведи пример кода который действительно докажет существование указанной проблемы. А пока-что статья даже близко не оправдывает заголовок.
Смотри код из статьи, там есть третий обработчик клика (без мемоизации, но он зависит от биг-даты), на мой скрин он не влез и по понятным причинам я делал акцент на своих вставках (статью читали?).
Полный код
Ещё раз. Где??? Если правильно написать код - лишние объекты не создаются. И никаких 100500 экземпляров биг даты в памяти. (делал ~20 снапшотов памяти, видел максимум 6 экземпляров биг даты). Ниже скрин в котором биг-дата создаётся не более 1 раза.
"цепочка замыканий" решается правильным использованием всех мемоизационных хуков (включая
useMemo
)В тексте статьи то ли не хватает адекватного примера, то ли проблемы описаной в заголовке не существует. Лично я склоняюсь ко второму варианту.
А если уж надо при каких-то обстоятельствах пересоздавать биг-дату. Мемоизацию зависимостей биг-даты (всё тем же
useMemo)
и/или стейтменеджер никто не отменял.P.S. Реакт делает компилятор который будет делать мемоизацию за разработчика.
Источник: https://react.dev/learn/react-compiler
Какая растущая цепочка замыканий. Куча бреда в статье и в коментариях.
Создание 10мб объекта происходит при каждом рендере компонента. Именно его надо мемоизировать при помощи
useMemo
в первую очередь. Тогда и сuseCallback
не будет проблем. Мемоизационные хуки (внезапно) держат в памяти не только последний результат, а и несколько предыдущих (возможно и без ограничений вообще) и очищаются при удалении компонента из дерева. И это не про реакт, в так впринцыпе работает мемоизация.Это
const bigData = new BigObject()
Должно выглядеть так
const bigData = useMemo(() => new BigObject(), []);
Или вынесено из компонента (например в стейт-менеджер).
Автор я понимаю что ты пришел из .Net, но тут проблема не в понимании реакта, а в понимании мемоизации. Посмотри как работает например код функции
memoize
из библиотекиlodash
. Ты увидишь что происходит мемоизация (сохранения в памяти) нескольких результатов вызова с привязкой каждого к аргументам (зависимостям). Считай что это кеш, который конечно можно переполнить. И в твоём примере ты постоянно создаёшь новый экземплярbigData
и передаешь его как зависимость вuseCallback
в итоге твоя мемоизация вообще не работает и забивает память. В случае с мемоизированымbigData
- если у нехо нет зависимости от пропсов (или чего-то ещё) то он будет создан один раз для экземпляра компонента. И да, того же можно добиться сuseRef
, но там дело не в больших объектах, а в мутабельности. При мутации объекта который хранится вuseRef
- реакт вообще не будет ничего рендерить. Есть задачи которые можно решить и тем и тем, но по принципу работы это разные вещи, и далеко не всегда взаимозаменяемые.Ну акцент на "можно реализовать" а во vue это есть из коробки, это не делает реакт хуже, и не означает что я не стану его учить в будущем)
По поводу разделения Html, js, css, мне всё равно импонирует подход vue, то что весь компонент, конкретно его стили, и внутрений скриптинг, типо замороченых анимаций по клику, пишутся в одном файле, и если я захочу переписать полностью один вид кнопок, не затрагивая остальные, мне нужно ходить в общий css, я это могу сделать в одном файле, а в глобальный css вынести те самые переменные (css custom properties) цветов, шрифтов, отступов, теней и всего остального. Я пока только изучаю vue, но насколько я понимаю проблем с тем что бы забрать значения этих переменных из глобального css не будет. Именно по этой причине я начал учить Vue вместо реакта или господи помилуй
Angular