предисловие
Для прочтения этой статьи требуетсяReact hooksсерединаuseStateиuseEffectиметь базовое понимание. Эта моя статья имеет общее введениеПолное использование хуков в проектах React.
useCallback
Роль использования обратного вызова
Официальная документация:
Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
Проще говоря, он возвращает функцию, которая будет обновляться (возвращая новую функцию) только при изменении зависимостей.
Применение обратного вызова
Онлайн код:Code Sandbox
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1);
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
return (
<div>
<div>
<Button onClickButton={handleClickButton1}>Button1</Button>
</div>
<div>
<Button onClickButton={handleClickButton2}>Button2</Button>
</div>
<div>
<Button
onClickButton={() => {
setCount3(count3 + 1);
}}
>
Button3
</Button>
</div>
</div>
);
}
// Button.jsx
import React from 'react';
const Button = ({ onClickButton, children }) => {
return (
<>
<button onClick={onClickButton}>{children}</button>
<span>{Math.random()}</span>
</>
);
};
export default React.memo(Button);
В случае, если вы можете нажать несколько кнопок в демоверсии, чтобы просмотреть эффект:
- При нажатии Button1 будет обновлено только содержимое за Button1 и Button3;
- Нажатие Button2 обновит содержимое трех кнопок;
- Щелчок по Button3 также обновляет только содержимое за Button1 и Button3.
Если внимательно рассмотреть вышеуказанные эффекты, то можно обнаружить, что только послеuseCallback
Оптимизированная Button2 будет изменяться только при нажатии на нее самой, а две другие будут изменяться до тех пор, пока обновляется родительский компонент (здесь Button1 и Button3 фактически одинаковы, но функция написана в другом месте). Давайте подробнее рассмотрим конкретную логику оптимизации.
Здесь вы можете заметить, что компонент Button
React.memo
Этот метод выполняет поверхностное сравнение реквизитов внутри этого метода, и если реквизиты не изменились, компонент не будет перерендерен.
const a = () => {};
const b = () => {};
a === b; // false
В приведенном выше коде видно, что наши две одинаковые функции не равны (это ерунда, я думаю, все, кто может это видеть, знают, поэтому я не буду это объяснять).
const [count1, setCount1] = useState(0);
// ...
const handleClickButton1 = () => {
setCount1(count1 + 1);
};
// ...
return <Button onClickButton={handleClickButton1}>Button1</Button>
Оглянитесь назад на вышеButton
компоненты требуютonClickButton
props , хотя и полезный внутри компонентаReact.memo
оптимизировать, но мы заявляемhandleClickButton1
Метод определяется напрямую, что означает, что пока родительский компонент повторно отображается (обновление состояния или свойств), здесь будет объявлен новый метод.Хотя новый метод и старый метод одинаковы по длине, они еще два разных объекта,React.memo
После сравнения обнаруживается, что реквизит объекта изменился, и он перерисовывается.
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
В приведенном выше коде наш метод оборачивает слой с помощью useCallback, а затем передает[count2]
переменная, где useCallback будет основываться наcount2
Есть ли изменение, таким образом решая, следует ли возвращать новую функцию, функциявнутренний объемТакже обновлено.
Поскольку наш метод зависит только отcount2
эта переменная иcount2
только вОн не будет обновляться, пока не будет нажата кнопка Button2.handleClickButton2
, поэтому мы нажимаем кнопку Button1 без повторного рендеринга содержимого Button2.
Tips
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count2, setCount2] = useState(0);
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, []);
return (
<Button
count={count2}
onClickButton={handleClickButton2}
>Button2</Button>
);
}
Мы подправили код и изменили второй параметр зависимости useCallback напустой массив, а это значит, что этот метод не имеет зависимых значений и не будет обновляться. И из-за статической области видимости JS эта функцияcount2
навсегда0
.
Вы можете щелкнуть Button2 несколько раз, чтобы просмотреть изменения, и вы обнаружите, что значение за Button2 изменится только один раз. потому что вышеуказанная функцияcount2
Всегда будет0
, а это значит, что каждый раз0 + 1
, принято Баттономcount
реквизит, и только из0
стать1
и всегда будет1
,иhandleClickButton2
Кроме того, поскольку нет никаких зависимостей и новые методы не будут возвращены, компонент Button будет толькоcount
Изменить и обновить один раз.
Вышеупомянутые проблемы вызваны отсутствием обновлений, а затем мы рассмотрим проблемы, вызванные частыми обновлениями.
const [text, setText] = useState('');
const handleSubmit = useCallback(() => {
// ...
}, [text]);
return (
<form>
<input value={text} onChange={(e) => setText(e.target.value)} />
<OtherForm onSubmit={handleSubmit} />
</form>
);
В приведенном выше примере мы видим, что нашhandleSubmit
будет зависеть отtext
обновление для обновления, вinput
в использованииtext
изменения должны быть довольно частыми, если в это время нашиOtherForm
Это большой компонент, который должен быть оптимизирован и может быть использован в настоящее время.useRef
помогать.
const textRef = useRef('');
const [text, setText] = useState('');
const handleSubmit = useCallback(() => {
console.log(textRef.current);
// ...
}, [textRef]);
return (
<form>
<input value={text} onChange={(e) => {
const { value } = e.target;
setText(value)
textRef.current = value;
}} />
<OtherForm onSubmit={handleSubmit} />
</form>
);
использоватьuseRef
Переменная может быть сгенерирована так, чтобы быть доступной в течение каждого срока службы компонента, иhandleSubmit
не поэтомуtext
обновить, не пускаетOtherForm
Рендерить несколько раз.
В комментариях друг упомянул, нужно ли обернуть все методы с помощью useCallback, Это также должно быть вопросом для многих друзей, которые только что узнали о useCallback. Первый ответ:Не заключайте все методы в useCallback, подробно описано ниже.
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1)
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1)
}, [count2]);
return (
<>
<button onClick={handleClickButton1}>button1</button>
<button onClick={handleClickButton2}>button2</button>
</>
)
Приведенный выше способ написания объявит новый при повторном отображении текущего компонента.handleClickButton1
функция, нижеuseCallback
Функция внутри также объявляет новую функцию, которая передается вuseCallback
, хотя эта функция может бытьinputs
Никаких изменений не произошло и не будет возвращеноhandleClickButton2
на переменную.
Тогда в нашем случае он возвращает и новую функцию, и старую, потому что следующее<button>
уже будет отображаться, но вместо этого используйтеuseCallback
После каждого выполнения необходимо сравнивать внутренниеinputs
Независимо от того, изменилось оно или нет, и сохранить предыдущую функцию, потребление еще больше.
useCallback должен сотрудничать с дочерними компонентами
shouldComponentUpdate
илиReact.memo
используются вместе, в противном случае это обратная оптимизация.
useMemo
Роль useMemo
Официальная документация:
Передайте функцию «создать» и массив зависимостей. useMemo будет пересчитывать запомненное значение только тогда, когда одна из зависимостей изменилась.
Проще говоря, это передача функции создания и зависимостей.Функция создания должна будет вернуть значение.Только при изменении зависимостей эта функция будет вызываться снова и возвращать новое значение.
Применение useMemo
useMemo очень похож на useCallback.На основании приведенного выше useCallback уже можно предположить, что useMemo также может оптимизировать кеш для значений, передаваемых в дочерние компоненты.
// ...
const [count, setCount] = useState(0);
const userInfo = {
// ...
age: count,
name: 'Jace'
}
return <UserCard userInfo={userInfo}>
// ...
const [count, setCount] = useState(0);
const userInfo = useMemo(() => {
return {
// ...
name: "Jace",
age: count
};
}, [count]);
return <UserCard userInfo={userInfo}>
Очевидно, что userInfo выше будет каждый раз новым объектом, независимо от того, чтоcount
Независимо от того, есть ли изменение или нет, это приведет к повторному рендерингу UserCard, и следующее будет вcount
Новый объект возвращается только после изменения.
Вышеупомянутое использование связано с оптимизацией производительности, вызванной передачей значений между родительским и дочерним компонентами.больше чем это, согласно официальной документации:
This optimization helps to avoid expensive calculations on every render.
Вы можете поместить в useMemo некоторую дорогостоящую вычислительную логику и обновлять ее только при изменении значения зависимости.
const num = useMemo(() => {
let num = 0;
// 这里使用 count 针对 num 做一些很复杂的计算,当 count 没改变的时候,组件重新渲染就会直接返回之前缓存的值。
return num;
}, [count]);
return <div>{num}</div>
На самом деле, useMemo гораздо шире, чем useCallback.Мы можем определить возвращаемое значение useMemo как возвращающую функцию, чтобы использовать useCallback гибко. В процессе разработки, когда некоторые переменные изменяются, что повлияет на обновление нескольких мест, мы можем вернуть объект или массив и добиться одновременного кэширования нескольких данных путем деструктурирования присваивания.
const [age, followUser] = useMemo(() => {
return [
new Date().getFullYear() - userInfo.birth, // 根据生日计算年龄
async () => { // 关注用户
await request('/follow', { uid: userInfo.id });
// ...
}
];
}, [userInfo]);
return (
<div>
<span>name: {userInfo.name}</span>
<span>age: {age}</span>
<Card followUser={followUser}/>
{
useMemo(() => (
// 如果 Card1 组件内部没有使用 React.memo 函数,那还可以通过这种方式在父组件减少子组件的渲染
<Card1 followUser={followUser}/>
), [followUser])
}
</div>
)
Эпилог
Простое понимание, useCallback и useMemo, один кеш — это функция, а другой — возвращаемое значение функции. useCallback предназначен для оптимизации дочерних компонентов и предотвращения повторного рендеринга дочерних компонентов. useMemo может оптимизировать текущий компонент, а также подкомпоненты.Оптимизация текущего компонента в основном кэширует некоторую сложную вычислительную логику через memoize. Конечно, нет необходимости использовать useMemo, если вы выполняете только некоторые простые вычисления.Здесь вы можете учитывать потребление производительности некоторых вычислений и потребление производительности при сравнении входных данных для достижения компромисса (если есть логика, подобная эта логика сравнения, нет необходимости использовать useMemo, и вы все равно можете меньше изнашивать клавиатуру 😅).
Если у вас есть какие-либо вопросы или предложения, пожалуйста, оставьте сообщение.
Заканчивать.