🖥 Проблема устаревших замыканий и способы ее решения в React. Часть 1



Замыкания — пожалуй, одна из самых пугающих концепций JavaScript. Даже всезнающий ChatGPT скажет вам это. Во всяком случае, это одна из самых скрытых особенностей языка.



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



Попробуем проникнуть в очередную тайну JavaScript-кода, чтобы выяснить:



🟢что такое замыкания, как они появляются и зачем нужны;

🟢что такое “устаревшее” замыкание и почему оно возникает.



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



✏️Задача



Представьте, что вы реализуете форму с несколькими полями ввода. Одно из полей представляет собой очень тяжелый компонент из какой-то внешней библиотеки. У вас нет доступа к его внутренним элементам, поэтому вы не можете устранить проблемы с его производительностью. Но этот компонент очень нужен в форме, поэтому вы решили обернуть его в React.memo, чтобы минимизировать повторные рендеринги при изменении состояния формы. Как-то так:

const HeavyComponentMemo = React.memo(HeavyComponent);



const Form = () => {

const [value, setValue] = useState();



return (

<>

<input

type="text"

value={value}

onChange={(e) => setValue(e.target.value)}

/>

<HeavyComponentMemo />

</>

);

};




Пока все хорошо. Тяжелый компонент принимает только одно строковое свойство, например title, и коллбэк onClick. Это происходит при нажатии кнопки “done” (“готово”) внутри компонента. Отправить данные формы также довольно просто: достаточно передать title и onClick.



const HeavyComponentMemo = React.memo(HeavyComponent);



const Form = () => {

const [value, setValue] = useState();

const onClick = () => {

// сюда передаем данные формы

console.log(value);

};



return (

<>

<input

type="text"

value={value}

onChange={(e) => setValue(e.target.value)}

/>

<HeavyComponentMemo

title="Welcome to the form"

onClick={onClick}

/>

</>

);

};




А вот теперь перед вами встает дилемма. Как известно, каждое свойство компонента, обернутое в React.memo, должно быть либо примитивным значением, либо постоянным между повторными рендерами. В противном случае мемоизация не сработает. Поэтому технически нужно обернуть onClick в useCallback:



const onClick = useCallback(() => {

// здесь передаем данные

}, []);




Но, как известно, хук useCallback должен иметь все зависимости, объявленные в его массиве зависимостей. Поэтому, чтобы отправить данные формы в компонент, нужно объявить эти данные как зависимость:



const onClick = useCallback(() => {

// сюда подаются данные

console.log(value);



// добавление значения к зависимости

}, [value]);




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



👆 Далее



👆 Часть 2



@react_js