Прежде всего, позвольте мне резюмировать мою точку зрения: за исключением очень немногих особых случаев, его не следует использовать.useCallback
.
Если у вас есть разные мнения или сомнения по поводу этого моего вывода, поверьте мне, читайте дальше, следующий контент заставит ваше понимание его перейти на более высокий уровень.
НижеuseCallback
Основное использование:
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
В приведенном выше кодеmemoizedCallback
Он будет сгенерирован один раз в начале, а в дальнейшем процессе только его зависимостиa
илиb
Он будет восстановлен, если он изменится.
ПонялuseCallback
Основное использование, мы используемuseCallback
Обернутая функция и функция, не обернутая ею, объединяются для сравнения:
function App() {
const method1 = () => {
// ...
}
const method2 = useCallback(() => {
// 这是一个和 method1 功能一样的方法
}, [props.a, props.b])
return (
<div>
<div onClick={method1}>button</div>
<div onClick={method2}>button</div>
</div>
)
}
Простите, в отличие от вышесказанного, этоmethod1
производительность хорошая илиmethod2
производительность хорошая?
Я слышал тебя, конечноmethod2
Ах!
нашApp
Функция перевыполняется каждый раз при обновлении, поэтому функции внутри нее также перегенерируются один раз.method1
Каждое поколение заново исполнялось.
а такжеmethod2
Это другое, это возвращаемое значение, обернутое useCallback, если зависимость не изменится, оно не будет перегенерировано, поэтому вы можете подумать, чтоmethod2
Такой способ написания более эффективен.
Но на самом деле мы считаем это несколько неправильным.
Прежде всего, каждый раз, когда функция выполняется, переменные внутри нее перегенерируются, а накладные расходы незначительны.Это официальный сайт.Hooks FAQНаши соответствующие выводы приведены:
Даже если «переменные регенерируются каждый раз, компонент выполнен», не стоит игнорировать, использоватьuseCallback
То же самое каждый раз, когда генерируется новая функция, но место, где она генерируется, очень скрыто, но она генерируется, а не используется. Теперь давайте посмотрим на это поближе.
const method1 = () => { }
const method2 = useCallback(() => {
/* 一个和 method1 一样的方法 */
},
[props.a, props.b]
)
Предполагая, что сейчас он находится в фазе обновления, выполнитеmethod1
, нам просто нужно применить и сохранитьmethod1
Памяти, требуемой функцией, соответствующей этой переменной, вполне достаточно.
но выполнитьmethod2
Шерстяная ткань,
- Во-первых, мы должны дополнительно выполнить
useCallback
функция, - В то же время, мы также должны применить
useCallbck
Память, требуемая функцией, соответствующей первому параметру, стоимость такая же, как иmethod1
Накладные расходы одинаковы, даже если мы будем использовать кеш,useCallback
Также требуются накладные расходы памяти для первого параметра. - Кроме того, чтобы судить
useCallback
Вы хотите обновить результаты, мы должны полагаться на память, чтобы сэкономить время. - и если наш
useCallback
Возвращаемая функция зависит от других значений компонента, и из-за особенностей замыканий в JS они всегда будут существовать и не уничтожаться.
const list = [...]
const method = useCallback(() => {
console.log(list) // list 的引用会一直存在
},
)
Глядя на это таким образом, используйтеuseCallback
, который ничем не лучше оригинала.
мы проходимuseCallback
Исходный код еще раз подтверждает это:
function updateCallback<T>(
callback: T, // useCallback 的第一个参数
deps: Array<mixed> | void | null // useCallback 的第二个参数
): T {
// 取到当前的 useCallback 语句对应的 hook 节点,
const hook = updateWorkInProgressHook();
// 当前的依赖,后面拿来和上一次的依赖进行比较
const nextDeps = deps === undefined ? null : deps;
// 取到上一次缓存的函数
const prevState = hook.memoizedState;
if (prevState !== null) {
// 传了 useCallbck 的第二个参数才走到这里
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 上一次的依赖和这一次的依赖进行比较,
// 相同就直接返回缓存的结果
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
Я считаю, что когда вы увидите это, вы поймете, почему вы не можете легко его использовать.useCallbck
правильно?
Надо сказать, что для него слишком мало правильных сценариев использования.
Есть очень типичныйuseCallbck
Неправильное использование сцены, стыдно сказать, я так написал. если мы последуемэтот документОписание добавляет конфигурацию ESLINT в наш проект, и при написании кода будет сообщено ошибка, аналогичный следующему:
export default function App() {
const [count, setCount] = useState();
const fetchApi = async () => {
await fetch('https://jsonplaceholder.typicode.com/posts/1');
console.log(count);
};
useEffect(() => {
fetchApi();
}, []);
return <div>Hello World</div>;
}
Я не знаю, сколько людей столкнулись с подобными ошибками. Но мы знаем, что мы не можемfetchApi
Эта функция добавлена в зависимость.
Для этой проблемы самое простое и прямое решение — переместить функцию вuseEffect
в.
Выполнение этого заставит некоторых людей чувствовать себя некомфортно, особенно студентов, которые только что пришли из компонента «Класс» (причина темы статьи, мы не будем на этом распространяться). Фактически,useEffect
Сама концепция дизайна рекомендует, чтобы мы поставили его внутрь, мы должны попытаться адаптировать к нему. Если вы привыкли, это будет очень хорошо.
Однако должны быть случаи, когда его нельзя разместить внутри, поэтому можно использовать следующие решения:
Скриншот выше взят из документацииБезопасно ли исключать функции из списка зависимостей?
Пожалуйста, обратите внимание на третий пункт~ Он также говорит, используйтеuseCallback
Этот метод на самом деле является последним средством.После нашего предыдущего анализа вы должны лучше понимать причину, по которой он так сказал.
теперь, когдаuseCallback
Так плохо, когда это будет доступно?
Предположим, у нас естьCounter
Дочерний компонент , потребляет много денег при инициализации рендеринга:
<ExpensiveCounter count={count} onClick={handleClick} />
Если мы не будем проводить оптимизацию, любое обновление родительского компонента будет перерисовываться.Counter
. Чтобы избежать повторного рендеринга дочернего компонента каждый раз, когда рендерится родительский компонент, мы можем использоватьReact.memo
:
const ExpensiveCounter = React.memo(function Counter(props) {
...
})
использоватьReact.memo
После упаковки,Counter
компоненты толькоprops
Он будет перерисовываться только при изменении нашегоCounter
принять дваprops
:Исходное значениеcount
, функцияhandleClick
.
Если родительский компонент из-за изменения других значений и обновления происходит, то родительский компонент будет перерендерен, т.к.handleClick
Является объектом, генерируется каждый рендерингhandleClick
все новые.
Это приводит, хотяCounter
одеялоReact.memo
Оберните слой, но он все равно будет перерендерен.Чтобы решить эту проблему, мы должны написать такhandleClick
Функция:
const handleClick = useCallback(() => {
// 原来的 handleClick...
}, [])
Таким образом, каждый раз, когда мы переходим кCounter
компонентhandleClick
все таки нашCounter
компоненты толькоcount
Он будет отображаться только тогда, когда произойдет изменение, а это именно то, что нам нужно, и это также играет хорошую роль в оптимизации.
Вышеупомянутая сцена может бытьuseCallback
Один из немногих, очень подходящих для сцены. Но какой подкомпонент, с которым вы сталкиваетесь при выполнении задания Спецвыпуска, потребляет больше, чем вы? Во всяком случае, я столкнулся со многим.
На этой неделе планировал обновить статью про интерпретацию планировщика в React, но не знаю зачем, и никак не могу поднять мотивацию, не хочу читать исходники, может быть быть мотивированным на следующей неделе.
Да, кстати, в эти выходные погода очень холодная, и, увидев здесь друзей, я должна не забыть завтра одеться побольше.