#reactjs #use-ref #usecallback
#reactjs #использовать-ref #обратный вызов
Вопрос:
Выполняя обзор кода, я наткнулся на этот пользовательский хук:
import { useRef, useEffect, useCallback } from 'react'
export default function useLastVersion (func) {
const ref = useRef()
useEffect(() => {
ref.current = func
}, [func])
return useCallback((...args) => {
return ref.current(...args)
}, [])
}
Этот хук используется следующим образом:
const f = useLastVersion(() => { // do stuff and depends on props })
По сути, по сравнению с const f = useCallBack(() => { // do stuff }, [dep1, dep2])
этим избегается объявление списка зависимостей и f
никогда не изменяется, даже если изменяется одна из зависимостей.
Я не знаю, что думать об этом коде. Я не понимаю, в чем недостатки использования useLastVersion
по сравнению с useCallback
.
Комментарии:
1. Вы можете просто вернуть
ref.current
это настолько избыточно, этоuseCallback
бесполезно, можете ли вы спросить, почему это там?2. @DennisVash возвращает
ref.current
другое: здесь мы возвращаем функцию, которая никогда не меняется, тогдаref.current
как изменится.3. Мой вопрос в том, почему при наличии функции сам объект ref (return ref) имеет тот же срок службы, что и функция, зачем иметь другую оболочку?
4. Простое использование useCallback, похоже, делает то же самое, что вы предложили
5. @DennisVash нет, это не так. При непосредственном использовании функции при каждом рендеринге создается новая функция, и если она передается как реквизит, дочерний компонент также будет повторно отображаться. При использовании
useCallback
ссылка не изменится, если массив зависимостей не изменится, поэтому мы избегаем потенциальных повторных отправлений для дочерних компонентов. Используя этотuseLastVersion
хук, мы продвигаем его еще дальше, потому что мы мутируем, чтобы избежать любого повторного использования.
Ответ №1:
На этот вопрос на самом деле уже более или менее дан ответ в документации: https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback
Интересная часть:
Также обратите внимание, что этот шаблон может вызвать проблемы в параллельном режиме. В будущем мы планируем предоставить более эргономичные альтернативы, но самое безопасное решение прямо сейчас — всегда аннулировать обратный вызов, если какое-то значение зависит от изменений.
Также интересно прочитать: https://github.com/facebook/react/issues/14099 и https://github.com/reactjs/rfcs/issues/83
Текущая рекомендация — использовать поставщика, чтобы избежать передачи обратных вызовов в props, если мы обеспокоены тем, что это может привести к слишком большому количеству повторных вызовов.
Ответ №2:
Моя точка зрения, как указано в комментариях, заключается в том, что этот хук избыточен с точки зрения «сколько рендеров вы получаете», когда происходят слишком частые изменения зависимостей (в useEffect
useCallback
массивах / dep), использование обычной функции является лучшим вариантом (без накладных расходов).
Этот хук скрывает рендеринг компонента, использующего его, но рендеринг происходит из useEffect
его родительского элемента.
Если мы суммируем количество рендеринга, мы получаем:
- Ссылка обратный вызов (перехват): рендеринг в
Component
(из-заvalue
) Рендеринг в hook (useEffect
), всего 2. - Только обратный вызов: рендеринг в
Component
(из-заvalue
) рендеринг вCounter
(изменение в дуэте ссылок на функции наvalue
изменение), всего 2. - обычная функция: рендеринг в
Component
рендеринг вCounter
: новая функция при каждом рендеринге, всего 2.
Но вы получаете дополнительные накладные расходы для неглубокого сравнения в useEffect
or useCallback
.
Практический пример:
function App() {
const [value, setValue] = useState("");
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
type="text"
/>
<Component value={value} />
</div>
);
}
function useLastVersion(func) {
const ref = useRef();
useEffect(() => {
ref.current = func;
console.log("useEffect called in ref callback");
}, [func]);
return useCallback((...args) => {
return ref.current(...args);
}, []);
}
function Component({ value }) {
const f1 = useLastVersion(() => {
alert(value.length);
});
const f2 = useCallback(() => {
alert(value.length);
}, [value]);
const f3 = () => {
alert(value.length);
};
return (
<div>
Ref and useCallback:{" "}
<MemoCounter callBack={f1} msg="ref and useCallback" />
Callback only: <MemoCounter callBack={f2} msg="callback only" />
Normal: <MemoCounter callBack={f3} msg="normal" />
</div>
);
}
function Counter({ callBack, msg }) {
console.log(msg);
return <button onClick={callBack}>Click Me</button>;
}
const MemoCounter = React.memo(Counter);
В качестве примечания, если целью является только нахождение длины input
с минимальным отображением, inputRef.current.value
решением будет чтение.
Комментарии:
1. Конечно, для этого примера мы, вероятно, могли бы сделать лучше, это было предназначено только для примера. Как вы можете видеть, используя
useLastVersion
вместо повторногоCounter
рендеринга (представьте, что это тяжелый компонент для рендеринга), мы выполняем только очень легкий эффект.