предисловие
До появления хуков, если компонент содержал внутреннийstate
, мы основываемся наclass
формы для создания компонентов.
В реакции точка оптимизации производительности:
- передача
setState
, это вызовет повторный рендеринг компонента, независимо от того, до или послеstate
Это то же самое - При обновлении родительского компонента дочерний компонент также будет автоматически обновлен.
Основываясь на двух вышеупомянутых пунктах, наше обычное решение: используйтеimmutable
Сравните, позвоните, когда они не равныsetState
, существуетshouldComponentUpdate
до и после судаprops
а такжеstate
, если нет изменений, вернутьсяfalse
чтобы предотвратить обновление.
существуетhooks
После выхода в компоненте нет функцииshouldComponentUpdate
В жизненном цикле мы не можем решить, следует ли обновляться, оценивая состояние до и после.useEffect
больше не различатьmount
update
Два состояния, а это значит, что при каждом вызове функционального компонента будет выполняться вся его внутренняя логика, что принесет большую потерю производительности.
В сравнении
Давайте кратко рассмотрим сигнатуры вызовов useMemo и useCallback:
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T; function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
useCallback
а такжеuseMemo
параметры сuseEffect
Последовательно, самая большая разница между нимиuseEffect
будет использоваться для обработки побочных эффектов, а первые два хука - нет.
useCallback
а такжеuseMemo
будет выполняться при первом рендеринге компонента, а затем выполняться снова, когда переменные зависят от изменения; и оба хука возвращают кешированное значение,useMemo
возврат в кэшеПеременная,useCallback
возврат в кэшефункция
React.memo()
В эпоху классовых компонентов для оптимизации производительности мы часто используемPureComponent
, каждый раз неглубокое сравнение пропсов, конечно кроме PureComponent можно ещеshouldComponentUpdate
для более глубокого контроля.
В функциональном компоненте React тесно предоставляетReact.memo
Этот HOC (компонент более высокого порядка) подобен PureComponent, но он специально предусмотрен для функционального компонента и не применим к классовому компоненту.
Но по сравнению с PureComponent,React.memo()
может поддерживать указание参数
, что может быть эквивалентноshouldComponentUpdate
Поэтому React.memo() удобнее в использовании, чем PureComponent.
(Конечно, если вы сами инкапсулируете HOC и реализуете комбинацию PureComponent + shouldComponentUpdate внутри, все должно быть в порядке. В предыдущих проектах было довольно много способов ее использования.)
Сначала посмотрите, как используется React.memo():
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
Использование очень простое, вне функционального компонента объявитеareEqual
метод судить дваждыprops
Какая разница, если не передать второй параметр, по умолчанию будет делаться только props浅比较
Последний экспортируемый компонент — это компонент, обернутый React.memo().
Пример:
-
index.js: родительский компонент
-
Child.js: дочерний компонент
-
ChildMemo.js: дочерний компонент, завернутый в React.memo.
index.js
import React, { useState, } from 'react';
import Child from './Child';
import ChildMemo from './Child-memo';
export default (props = {}) => {
const [step, setStep] = useState(0);
const [count, setCount] = useState(0);
const [number, setNumber] = useState(0);
const handleSetStep = () => {
setStep(step + 1);
}
const handleSetCount = () => {
setCount(count + 1);
}
const handleCalNumber = () => {
setNumber(count + step);
}
return (
<div>
<button onClick={handleSetStep}>step is : {step} </button>
<button onClick={handleSetCount}>count is : {count} </button>
<button onClick={handleCalNumber}>numberis : {number} </button>
<hr />
<Child step={step} count={count} number={number} /> <hr />
<ChildMemo step={step} count={count} number={number} />
</div>
);
}
child.js
Этот дочерний компонент сам по себе не имеет логики и упаковки, он просто отображает то, что передается от родительского компонента.props.number
Следует отметить, что подкомпоненты не используютсяprops.step
а такжеprops.count
, но однаждыprops.step
Изменение вызовет повторный рендеринг.
import React from 'react';
export default (props = {}) => {
console.log(`--- re-render ---`);
return (
<div>
{/* <p>step is : {props.step}</p> */}
{/* <p>count is : {props.count}</p> */}
<p>number is : {props.number}</p>
</div>
);
};
childMemo.js
Этот подкомпонент обернут React.memo и переданisEqual
Метод оценивается только тогда, когда реквизит дваждыnumber
перезапустит рендеринг, иначеconsole.log
не будет выполняться.
import React, { memo, } from 'react';
const isEqual = (prevProps, nextProps) => {
if (prevProps.number !== nextProps.number) {
return false;
}
return true;
}
export default memo((props = {}) => {
console.log(`--- memo re-render ---`);
return (
<div>
{/* <p>step is : {props.step}</p> */}
{/* <p>count is : {props.count}</p> */}
<p>number is : {props.number}</p>
</div>
);
}, isEqual);
Сравнение эффектов
Как видно из рисунка выше, при нажатии step и count изменились как props.step, так и props.count, поэтомуChild.js
Этот дочерний компонент перерисовывается каждый раз (----re-render----
), даже если эти два реквизита не используются.
В этом случае,ChildMemo.js
Повторный рендеринг повторно выполняться не будет.
Только при изменении props.number,ChildMemo.js
а такжеChild.js
Производительность стабильна.
Как видно из вышеизложенного,Второй метод React.memo() должен существовать для конкретного требования.Потому что в экспериментальном сценарии мы видим, что даже если я используюReact.memo
Обертывание Child.js всегда вызывает повторный рендеринг, потому что поверхностное сравнение свойств должно было измениться.
Детальная оптимизация производительности React.useMemo()
Из приведенного выше использования React.memo() мы можем обнаружить, что, в конце концов, весь компонент обернут в самый внешний слой, и нам нужно вручную написать метод для сравнения этих конкретных реквизитов перед повторным рендерингом.
В некоторых сценариях мы просто хотим, чтобы часть компонента не перерисовывалась, а не весь компонент не перерисовывался, то есть для достижения局部 Pure
Функция.
useMemo()
Основное использование заключается в следующем:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo() возвращает запомненное значение, которое пересчитывается только при изменении зависимостей (таких как a и b выше)
Если запомненное значение остается неизменным, логика рендеринга не будет перезапущена.
Говоря о логике рендеринга, следует помнить, что useMemo() выполняется во время рендеринга, поэтому некоторые дополнительные побочные операции, такие как сетевые запросы, выполняться не могут.
Если массив зависимостей не указан ([a,b] выше), то мемоизированное значение будет пересчитываться каждый раз, что также будет перекрашиваться.
Добавьте новый к приведенному выше кодуChild-useMemo.js
Подкомпоненты следующие:
import React, { useMemo } from 'react';
export default (props = {}) => {
console.log(`--- component re-render ---`);
return useMemo(() => {
console.log(`--- useMemo re-render ---`);
return <div>
{/* <p>step is : {props.step}</p> */}
{/* <p>count is : {props.count}</p> */}
<p>number is : {props.number}</p>
</div>
}, [props.number]);
}
Единственное отличие от вышеописанного в том, что useMemo() используется для обёртывания логики возвращаемой части рендеринга, а объявление зависит от props.number, а остальные не изменились.
Эффект контраста:
На приведенном выше рисунке мы видим, что каждый раз, когда родительский компонент обновляет шаг/число, он запускает повторную визуализацию дочернего компонента, инкапсулированного с помощью useMemo, но число не меняется, указывая на то, что повторная визуализация HTML часть не была перезапущена
Только когда зависимый props.number изменится, повторный рендеринг внутри пакета useMemo() будет запущен повторно.
React.useCallback()
После разговора о useMemo следует следующее:useCallback
. useCallback похож на useMemo, но возвращает кэшированную функцию. Рассмотрим простейшее использование:
const fnA = useCallback(fnB, [a])
Пример:
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 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.jsx
import React from 'react';
const Button = ({ onClickButton, children }) => {
return (
<>
<button onClick={onClickButton}>{children}</button>
<span>{Math.random()}</span>
</>
);
};
export default React.memo(Button);
Вы можете заметить здесь
React.memo
Этот метод, этот метод будет внутриprops
Сделайте поверхностное сравнение если еслиprops
Если ничего не изменилось, этот компонент не будет перерисовываться.
надButton
Все компоненты требуют реквизитов onClickButton, хотя они полезны внутри компонентов.React.memo
оптимизировать, но мы заявляемhandleClickButton1
Метод определяется напрямую, что означает, что пока родительский компонент повторно отображается (обновление состояния или свойств), здесь будет объявлен новый метод.Хотя новый метод и старый метод одинаковы по длине, они еще два разных объекта,React.memo
После сравнения обнаруживается, что реквизит объекта изменился, и он перерисовывается.
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
В приведенном выше коде наш метод оборачивает слой с помощью useCallback, а затем передает[count2]
переменная, здесь useCallback решит, следует ли возвращать новую функцию в зависимости от того, изменился ли count2, и соответственно будет обновлена внутренняя область действия функции.
Так как наш метод зависит только от переменной count2, а count2 будет обновляться только после нажатия Button2handleClickButton2
, поэтому мы нажимаем Button1 без повторного рендеринга содержимого Button2.
Суммировать
-
В случае, если дочернему компоненту не нужны значения и функции родительского компонента, нужно использовать только
memo
Функция оборачивает дочерний компонент. -
Если есть функция, переданная дочернему компоненту, используйте
useCallback
-
Если есть значение для передачи дочернему компоненту, используйте
useMemo
-
useEffect
,useMemo
,useCallback
обесобственное закрытиеиз. То есть каждый раз, когда компонент рендерится, он фиксирует состояние в контексте текущей функции компонента (state
,props
), поэтому каждый раз выполнение этих трех хуков отражаетсяТекущее состояние, вы не можете использовать их для захвата последнего состояния. Для этого случая мы должны использоватьref
посетить.