React.js. Хук useCallback и зависимости

15.09.2021

Теги: HookJavaScriptReact.jsWeb-разработкаКомпонентТеорияФункция

Смотрю сейчас на Udemy видео-курс по хукам — столкнулся с непониманием, зачем хуку useCallback нужны зависимости. Выяснил у преподавателя и решил записать, чтобы не наступать больше на эти грабли. Итак, есть два компонента — Parent и Child, в Parent есть функция, обернутая в useCallback, чтобы избежать лишних рендеров Child.

import React, { useState, useCallback } from 'react';
import Child from './Child';

export default function Parent(props) {
    const [state, setState] = useState('раз');

    const handleParentClick = () => {
        const newValue = Math.random() < 0.5 ? 'раз' : 'два';
        if (state !== newValue) {
            console.log('Изменение состояния Parent');
            setState(newValue);
        }
    };

    const handleChildClick = useCallback(() => {
        console.log('Обработчик клика Child');
    }, []);

    console.log('Рендер компонента Parent');
    return (
        <div>
            <h1>Родитель, состояние «{state}»</h1>
            <Child name="Ребенок" childClickHandler={handleChildClick} />
            <button onClick={handleParentClick}>Попытка изменить состояние Parent</button>
        </div>
    );
}
import React from 'react';

const Child = React.memo((props) => {
    console.log('Рендер компонента Child');
    return <h2 onClick={props.childClickHandler}>{props.name}</h2>
});

export default Child;

Мы всегда передаем из Parent в Child через пропсы ссылку на одну и ту же функцию — благодаря хуку useCallback. По этой причине мы увидим рендер компонента Child только один раз — в момент монтирования. Когда происходит рендер Parent — это не будет вызывать рендер Child — так работает связка React.memo и useCallback.

Все работает хорошо, пока мы не обратимся внутри handleChildClick к переменной state — она почему-то всегда равна «раз».

const handleChildClick = useCallback(() => {
    console.log('Обработчик клика Child');
    console.log('Значение пременной state', state);
}, []);

При первом рендере компонента Parent, в момент создания функции handleChildClick, она получает ссылку на лексическое окружение, в котором она создается. А хук useCallback сохранит ссылку на функцию handleChildClick, которая имеет замыкание над областью видимости функции Parent. Другими словами, при вызове функции handleChildClick ей будут доступны переменные state, setState, handleParentClick — какими они были в момент создания функции. Тут лучше прочитать у Ильи Кантора — как работает замыкание.

Поскольку useCallback с пустым массивом зависимостей будет всегда возвращать одну и ту же функцию, эта функция будет при вызове всегда выдавать одно и то же значение state. Это то самое значение «раз», какое было при первом рендере — и handleChildClick будет всегда доставать это значение из замыкания.

Чтобы console.log(state) показывал актуальное значение переменной state — нужно передать массив зависимостей при вызове хука.

const handleChildClick = useCallback(() => {
    console.log('Обработчик клика Child');
    console.log('Значение пременной state', state);
}, [state]);

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

При нажатии на кнопку «Попытка изменить состояние» — состояние Parent изменяется с вероятностью 50 процентов. Когда состояние изменяется — это вызывает рендер компонента Child, потому что ссылка, которую возвращает handleChildClick, изменилась. Когда состояние не изменяется — рендера Child не будет, потому что ссылка на функцию тоже остается без изменений.

Поиск: Hook • JavaScript • React.js • Web-разработка • Компонент • Теория • Функция • Хук • useCallback • Замыкание

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.