Эта статья была включена вGithub
: GitHub.com/Умышленно миром/…, добро пожаловать Звезда!
Общая проблема
-
🐤Восстановите исходное значение useReducer, почему его нельзя восстановить?
-
🐤Как useEffect имитирует жизненный цикл componentDidMount, componentUpdate, componentWillUnmount?
-
🐤Как правильно настроить прослушиватели событий для DOM в useEffect?
-
🐤Почему в useEffect, useCallback и useMemo получаются старые значения в state и props?
-
🐤Как оптимизировать, когда useCallback будет часто срабатывать?
-
🐤В чем разница между сценариями использования useCallback и useMemo?
-
🐤Как вызвать состояние или метод дочернего компонента в родительском компоненте?
Поверьте, прочитав эту статью, вы сможете получить нужные вам ответы.
Во-первых, процесс рендеринга функционального компонента
Давайте посмотрим, как работает функциональный компонент:
Counter.js
function Counter() {
const [count, setCount] = useState(0);
return <p onClick={() => setCount(count + 1)}>count: {count}</p>;
}
за кликp
Этикетка,count
оба +1,setCount
вызовет рендеринг функционального компонента. Повторный рендеринг функционального компонента на самом деле является повторным выполнением текущей функции.
При каждом рендеринге функционального компонента внутреннийstate
, функция и переданныйprops
являются независимыми.
Например:
// 第一次渲染
function Counter() {
// 第一次渲染,count = 0
const [count, setCount] = useState(0);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
// 点击 p 标签触发第二次渲染
function Counter() {
// 第二次渲染,count = 1
const [count, setCount] = useState(0);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
// 点击 p 标签触发第三次渲染
function Counter() {
// 第三次渲染,count = 2
const [count, setCount] = useState(0);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
// ...
Методы, объявленные в функциональных компонентах, аналогичны. Таким образом, каждый кадр, отображаемый в функциональном компоненте, соответствует своему независимому
state
,function
,props
.
2. useState/useReducer
useState
VS setState
-
useState
Работает только с функциональными компонентами,setState
Работает только с компонентами класса -
useState
Вы можете объявить более одного в компоненте функции, а значения состояния в компоненте класса должны быть объявлены вthis
изstate
в объекте -
В основном,
state
При изменении:-
useState
Исправлятьstate
когда то же самоеuseState
Заявленная стоимость будетПереопределить обработку, несколькоuseState
Объявленное значение сработаетвизуализировать несколько раз -
setState
Исправлятьstate
время, много разsetState
объекты будутОбработка слияния
-
-
useState
Исправлятьstate
При установке одного и того же значения функциональный компонент не будет перерисовываться, а будет унаследованComponent
компоненты класса, даже еслиsetState
Это же значение также активирует рендеринг
useState
VS useReducer
Первоначальный значение
-
useState
При установке начального значения, если начальное значение является значением, вы можете установить его напрямую.Если это значение, возвращаемое функцией, рекомендуется использовать функцию обратного вызова для его установки.
const initCount = c => {
console.log('initCount 执行');
return c * 2;
};
function Counter() {
const [count, setCount] = useState(initCount(0));
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
найдет дажеCounter
Когда компонент повторно редит не болееcount
переназначить начальное значение, ноinitCount
функция будет выполняться повторно
Изменено на функцию обратного вызова:
const initCount = c => {
console.log('initCount 执行');
return c * 2;
};
function Counter() {
const [count, setCount] = useState(() => initCount(0));
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
В настоящее время,initCount
функция будет толькоCounter
Выполняется при инициализации компонента, а затем независимо от того, как компонент отрисовывается,initCount
функция больше не будет выполняться
-
useReducer
Когда начальное значение установлено, начальное значение может быть значением только может быть только значением, и способ функции обратного вызова нельзя использовать.- Если это возвращаемое значение функции выполнения, то при повторном рендеринге компонента функция выполнения все равно будет выполняться.
Изменить статус
-
useState
При изменении состояния то же самоеuseState
Заявленное состояние будет переопределено
function Counter() {
const [count, setCount] = useState(0);
return (
<p
onClick={() => {
setCount(count + 1);
setCount(count + 2);
}}
>
clicked {count} times
</p>
);
}
в текущем интерфейсе
count
изstep
да 2
-
useReducer
При изменении состояния несколько разdispatch
Будет выполняться по порядку, отрисовывая компоненты по очереди
function Counter() {
const [count, dispatch] = useReducer((x, payload) => x + payload, 0);
return (
<p
onClick={() => {
dispatch(1);
dispatch(2);
}}
>
clicked {count} times
</p>
);
}
в текущем интерфейсе
count
изstep
да 3
снижениеuseReducer
Начальное значение , почему его нельзя восстановить
Например следующий пример:
const initPerson = { name: '小明' };
const reducer = function (state, action) {
switch (action.type) {
case 'CHANGE':
state.name = action.payload;
return { ...state };
case 'RESET':
return initPerson;
default:
return state;
}
};
function Counter() {
const [person, dispatch] = useReducer(reducer, initPerson);
const [value, setValue] = useState('小红');
const handleChange = useCallback(e => setValue(e.target.value), []);
const handleChangeClick = useCallback(() => dispatch({ type: 'CHANGE', payload: value }), [value]);
const handleResetClick = useCallback(() => dispatch({ type: 'RESET' }), []);
return (
<>
<p>name: {person.name}</p>
<input type="text" value={value} onChange={handleChange} />
<br />
<br />
<button onClick={handleChangeClick}>修改</button> |{' '}
<button onClick={handleResetClick}>重置</button>
</>
);
}
Нажмите кнопку «Изменить», чтобы изменить объект.name
Измените его на Little Red, нажмите кнопку сброса и восстановите исходный объект. Но давайте посмотрим на эффект:
можно увидетьname
После изменения Xiaohong, как бы вы ни нажимали кнопку сброса, его нельзя восстановить.
Это потому, что вinitPerson
когда мы изменилисьstate
свойство, которое приводит к начальному значениюinitPerson
изменилось, поэтому послеRESET
, даже если вернетсяinitPerson``,但是name
Значение по-прежнему маленькое красное.
Поэтому, когда мы изменяем данные, мы должны обращать внимание не на выполнение атрибутивных операций с исходными данными, а на воссоздание новых объектов для работы. Например, внесите следующие изменения:
// ...
const reducer = function (state, action) {
switch (action.type) {
case 'CHANGE':
// !修改后的代码
const newState = { ...state, name: action.payload }
return newState;
case 'RESET':
return initPerson;
default:
return state;
}
};
// ...
Взгляните на измененный эффект, вы можете сбросить его как обычно:
3. использоватьЭффект
useEffect
Основное использование:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count: ', count);
});
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
за кликp
Этикетка,Counter
Компоненты будут перерендерены, и вы увидите в консоли, что естьlog
Распечатать.
использоватьuseEffect
моделированиеcomponentDidMount
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count: ', count);
// 设置依赖为一个空数组
}, []);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
БудуuseEffect
Для зависимостей задан пустой массив, как видите, консоль выводит вывод только при первом рендеринге компонента. после этого неважноcount
Как обновить, больше не будет печатать.
использоватьuseEffect
моделированиеcomponentDidUpdate
- Используйте условие, чтобы определить, является ли зависимость исходным значением, если нет, перейдите к логике обновления.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count !== 0) {
console.log('count: ', count);
}
}, [count]);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
Однако у этого подхода есть недостаток: при наличии нескольких зависимостей требуется несколько сравнений, поэтому вы можете использовать следующий метод.
- использовать
useRef
Установите начальное значение для сравнения
function Counter() {
const [count, setCount] = useState(0);
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
} else {
console.log('count: ', count);
}
}, [count]);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
использоватьuseEffect
моделированиеcomponentWillUnmount
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count: ', count);
return () => {
console.log('component will unmount')
}
}, []);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
useEffect
Функция, возвращаемая в обернутой функции, будет выполняться при повторном отображении функционального компонента и очистке предыдущего кадра данных. Таким образом, эта функция может сделать некоторую очистку.
еслиuseEffect
Данные зависимости представляют собой пустой массив, тогда при выполнении функции возврата это означает, что компонент фактически выгружен.
Дать
useEffect
настраиватьзависимости - пустой массив,а такжевернуть функцию, то возвращаемая функция эквивалентнаcomponentWillUnmount
Обратите внимание, что зависимости должны быть установлены в пустой массив. Если это не пустой массив, то эта функция срабатывает не при размонтировании компонента, а при повторном рендеринге компонента для очистки данных предыдущего кадра.
существуетuseEffect
правильно дляDOM
Настройка прослушивателей событий
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const handleClick = function() {
console.log('count: ', count);
}
window.addEventListener('click', handleClick, false)
return () => {
window.removeEventListener('click', handleClick, false)
};
}, [count]);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
существуетuseEffect
Настройте прослушиватели событий вreturn
В функции убраны побочные эффекты, а события прослушивания отменены
существуетuseEffect、useCallback、useMemo
получен изstate、props
Почему старое значение
Как мы только что сказали, каждый кадр функционального компонента будет иметь свой собственный независимыйstate、function、props
. а такжеuseEffect、useCallback、useMemo
Имеет функцию кэширования.
Поэтому мы берем переменную под текущую соответствующую область видимости функции. Если зависимости установлены неправильно, тоuseEffect、useCallback、useMemo
Он не будет выполняться повторно, а используемые в нем переменные по-прежнему имеют прежние значения.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const handleClick = function() {
console.log('count: ', count);
}
window.addEventListener('click', handleClick, false)
return () => {
window.removeEventListener('click', handleClick, false)
};
}, []);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
Или предыдущий пример, если в это время дано
useEffect
установите пустой массив в качестве зависимости, тогда что угодноcount
Сколько раз менять, нажмитеwindow
, распечатанныйcount
еще 0
useEffect
Почему бесконечное выполнение происходит в
- не для
useEffect
установить зависимости, а вuseEffect
обновление вstate
, что приведет к бесконечной перерисовке интерфейса
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
Эта ситуация приведет к бесконечному повторному рендерингу интерфейса, потому что никакие зависимости не установлены.Если мы хотим предоставить интерфейс при первом рендеринге интерфейсаcount
Чтобы установить новое значение, просто установите пустой массив для зависимостей.
После модификации: будет установлено только при инициализацииcount
стоимость
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
Приведенный выше пример - когда зависимости отсутствуют, будут проблемы, тогда будут проблемы, когда зависимости установлены нормально.
- На данный момент есть требование: каждый раз
count
При добавлении нам нужно перевернуть страницу (page
+1), посмотрите, как написать:
Поскольку мы зависим отcount
, чтобы включить в зависимостиcount
, при измененииpage
нужно зависеть отpage
, поэтому зависимости также должны включатьpage
function Counter() {
const [count, setCount] = useState(0);
const [page, setPage] = useState(0);
useEffect(() => {
setPage(page + 1);
}, [count, page]);
return (
<>
<p onClick={() => setCount(count + 1)}>clicked {count} times</p>
<p>page: {page}</p>
</>
);
}
В это время это также приведет к тому, что интерфейс будет бесконечно повторяться, поэтому измените его в это время.page
измените его на функцию и удалите из зависимостиpage
Только что
После модификации: можно добиться эффекта и избежать повторного рендеринга
function Counter() {
const [count, setCount] = useState(0);
const [page, setPage] = useState(0);
useEffect(() => {
setPage(p => p + 1);
}, [count]);
return (
<>
<p onClick={() => setCount(count + 1)}>clicked {count} times</p>
<p>page: {page}</p>
</>
);
}
4. Конкуренция
Выполнение раньше, но возврат позже приведет к неправильной перезаписи значения состояния.
существуетuseEffect
, могут быть сценарии, в которых делаются сетевые запросы, и мы будем проходить в соответствии с родительским компонентомid
, чтобы инициировать сетевой запрос,id
При внесении изменений запрос делается снова.
function App() {
const [id, setId] = useState(0);
useEffect(() => {
setId(10);
}, []);
// 传递 id 属性
return <Counter id={id} />;
}
// 模拟网络请求
const fetchData = id =>
new Promise(resolve => {
setTimeout(() => {
const result = `id 为${id} 的请求结果`;
resolve(result);
}, Math.random() * 1000 + 1000);
});
function Counter({ id }) {
const [data, setData] = useState('请求中。。。');
useEffect(() => {
// 发送网络请求,修改界面展示信息
const getData = async () => {
const result = await fetchData(id);
setData(result);
};
getData();
}, [id]);
return <p>result: {data}</p>;
}
Показать результаты:
В приведенном выше примере, после нескольких обновлений страницы, вы можете увидеть, что конечный результат иногда отображаетсяid 为 0 的请求结果
, иногдаid 为 10 的结果
.
Правильным результатом должен быть «результат запроса с идентификатором 10». Это проблема конкуренции.
Решение:
- Отменить асинхронную операцию
// 存储网络请求的 Map
const fetchMap = new Map();
// 模拟网络请求
const fetchData = id =>
new Promise(resolve => {
const timer = setTimeout(() => {
const result = `id 为${id} 的请求结果`;
// 请求结束移除对应的 id
fetchMap.delete(id);
resolve(result);
}, Math.random() * 1000 + 1000);
// 设置 id 到 fetchMap
fetchMap.set(id, timer);
});
// 取消 id 对应网络请求
const removeFetch = (id) => {
clearTimeout(fetchMap.get(id));
}
function Counter({ id }) {
const [data, setData] = useState('请求中。。。');
useEffect(() => {
const getData = async () => {
const result = await fetchData(id);
setData(result);
};
getData();
return () => {
// 取消对应网络请求
removeFetch(id)
}
}, [id]);
return <p>result: {data}</p>;
}
Показать результаты:
На этом этапе, независимо от того, как вы обновляете страницу, она будет отображать толькоid 为 10 的请求结果
.
- Установите логическую переменную для отслеживания
// 模拟网络请求
const fetchData = id =>
new Promise(resolve => {
setTimeout(() => {
const result = `id 为${id} 的请求结果`;
resolve(result);
}, Math.random() * 1000 + 1000);
});
function Counter({ id }) {
const [data, setData] = useState('请求中。。。');
useEffect(() => {
let didCancel = false;
const getData = async () => {
const result = await fetchData(id);
if (!didCancel) {
setData(result);
}
};
getData();
return () => {
didCancel = true;
};
}, [id]);
return <p>result: {data}</p>;
}
Можно обнаружить, что независимо от того, как страница обновляется в это время, она будет отображать толькоid 为 10 的请求结果
.
5. Как сохранить ненужное в функциональных компонентахstate
,props
значение
функциональный компонент неthis
указал, поэтому, чтобы сохранитьэкземпляр компонентасвойства, вы можете использоватьuseRef
работать
функциональный компонентref
иметь возможностьзакрытие проникновенияСпособность. Путем преобразования значения обычного типа в значение сcurrent
Ссылка на объект свойства для обеспечения актуальности значения свойства при каждом доступе к нему.
Гарантированный доступ в каждом кадре функционального компонентаstate
значение такое же
- Посмотрите, не используете ли вы
useRef
В случае каждого кадраstate
как печатается значение
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const handleClick = function() {
console.log('count: ', count);
}
window.addEventListener('click', handleClick, false)
});
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
нажмите сначалаp
Tab 5 раз, затем нажмитеwindow
объект, вы можете увидеть результат печати:
- использовать
useRef
После этого в каждом кадреref
как печатается значение
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
// 将最新 state 设置给 countRef.current
countRef.current = count;
const handleClick = function () {
console.log('count: ', countRef.current);
};
window.addEventListener('click', handleClick, false);
});
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
Та же операция, что и раньше, сначала нажмитеp
Tab 5 раз, затем нажмитеwindow
интерфейс, вы можете увидеть результат печати
использовать
useRef
То есть можно гарантировать, что функции, к которым осуществляется доступ в каждом кадре функционального компонентаstate
Ценности те же.
Как сохранить свойства экземпляра функционального компонента
Функциональные компоненты не имеют экземпляров, поэтому свойства не могут быть смонтированы вthis
начальство. Тогда, если мы хотим создать не-state
,props
Переменные можно создавать и уничтожать с помощью функциональных компонентов, как это сделать?
Так же можно пройтиuseRef
,useRef
не только можетDOM
, вы также можете преобразовать обычные переменные вcurrent
объект недвижимости
Например, мы хотим установитьModel
Экземпляр , когда компонент создается, генерируетсяmodel
Например, после того, как компонент будет уничтожен и воссоздан, будет автоматически сгенерирован новый.model
пример
class Model {
constructor() {
console.log('创建 Model');
this.data = [];
}
}
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(new Model());
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
Согласно этому методу записи, когда функциональный компонент создается, сгенерированныйModel
экземпляр, смонтированный наcountRef
изcurrent
характеристики. При повторном рендеринге больше не выдаетcountRef
переназначить.
Это означает, что один и тот же используется до тех пор, пока компонент не будет удален.Model
например, после удаления текущийmodel
Экземпляр также будет уничтожен.
Внимательно наблюдайте за выводом консоли, вы обнаружите, что хотя
countRef
не переназначается, но при повторном рендеринге компонентаModel
Конструктор по-прежнему выполняется несколько раз
Итак, на данный момент мы можем заимствоватьuseState
характеристики, перепишите его.
class Model {
constructor() {
console.log('创建 Model');
this.data = [];
}
}
function Counter() {
const [count, setCount] = useState(0);
const [model] = useState(() => new Model());
const countRef = useRef(model);
return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}
При таком использовании его можно использовать без модификацииstate
случае, использоватьmodel
Некоторые свойства экземпляра могут сделатьflag
, который может быть источником данных или дажеMobx
изstore
использовать.
Шесть, используйте обратный вызов
Как и в вопросе, как избежать частого изменения зависимостейuseCallback
Как часто?
function Counter() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return <p onClick={handleClick}>clicked {count} times</p>;
}
Здесь мы положилиclick
Событие извлечено, используяuseCallback
пакет, но на самом деле он не очень хорошо работал.
потому чтоCounter
Повторный рендеринг компонента в настоящее время зависит только отcount
меняется, так что вотuseCallback
Нет никакой разницы между использованием и не использованием.
использоватьuseReducer
заменятьuseState
можно использоватьuseReducer
заменить.
function Counter() {
const [count, dispatch] = useReducer(x => x + 1, 0);
const handleClick = useCallback(() => {
dispatch();
}, []);
return <p onClick={handleClick}>clicked {count} times</p>;
}
useReducer
вернутьdispatch
функция поставляется сmemoize
, не меняется при нескольких рендерингах. Таким образом, вuseCallback
не обязательноdispatch
как зависимость.
КsetState
передаточная функция в
function Counter() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <p onClick={handleClick}>clicked {count} times</p>;
}
существуетsetCount
При использовании функции в качестве параметра в , полученное значение является последнимstate
значение, поэтому операции могут выполняться с этим значением.
пройти черезuseRef
Выполнение закрытия закрытия
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
const handleClick = useCallback(() => {
setCount(countRef.current + 1);
}, []);
return <p onClick={handleClick}>clicked {count} times</p>;
}
Такого же эффекта можно добиться и таким образом. Но это не рекомендуется, так как вам не только придется писать больше кода, но и могут возникнуть неожиданные проблемы.
Семь, используйте памятку
описано вышеuseCallback
некоторые проблемы и решения. Посмотрите нижеuseMemo
.
useMemo
а такжеReact.memo
разные:
-
useMemo
Это оптимизация и кеширование некоторых данных внутри компонента и ленивая их обработка. -
React.memo
Он обертывает функциональный компонент и обертывает внутренние компоненты компонента.state
,props
Производится поверхностное сравнение, чтобы определить, требуется ли рендеринг.
useMemo
а такжеuseCallback
разница
-
useMemo
Возвращаемое значение является значением, может быть свойством, может быть функцией (включая компоненты) -
useCallback
Возвращаемое значение может быть только функцией
следовательно,useMemo
в какой-то мере заменитьuseCallback
, эквивалентное условие:useCallback(fn, deps) => useMemo(() => fn, deps)
Поэтому сказанное выше оuseCallback
Некоторые моменты оптимизации также применимы кuseMemo
.
8. Следует ли часто использовать useCallback и useMemo?
Вот мое скромное мнение:Не рекомендуется частое использование
Дамы и господа, не начинайте распылять, позвольте высказать свое мнение
причина:
- useCallback и useMemo на самом деле вызываются как функции в функциональных компонентах, поэтому первый параметр — это функция обратного вызова, которую мы передаем.Независимо от того, используются ли useCallback и useMemo, эта функция обратного вызова будет создана, поэтому она не снижает стоимость создания функции.
- Мало того, что это не может снизить стоимость создания, но после использования useCallback и useMemo зависимость второго параметра также должна выполнять поверхностное сравнение каждый раз при рендеринге, что фактически увеличивает стоимость сравнения данных.
- Таким образом, использование useCallback и useMemo может не только снизить нагрузку, но и увеличить стоимость сравнения, поэтому не рекомендуется использовать его часто.
Причина уже давно объяснена.Являются ли useCallback и useMemo бессмысленными?Конечно нет.Если эффекта вообще нет, то зачем React его предоставлять.
Он все еще используется, но нам нужно судить в зависимости от ситуации и когда его использовать.
Вот несколько сценариев, в которых применимы useCallback и useMemo.
Сценарии использования useCallback
-
Сценарий 1: Необходимо оптимизировать производительность подкомпонентов
В этом примере приложение передаст свойство функции onClick дочернему компоненту Foo.
Код перед оптимизацией с использованием useCallback
App.js
import React, { useState } from 'react'; import Foo from './Foo'; function App() { const [count, setCount] = useState(0); const fooClick = () => { console.log('点击了 Foo 组件的按钮'); }; return ( <div style={{ padding: 50 }}> <Foo onClick={fooClick} /> <p>{count}</p> <button onClick={() => setCount(count + 1)}>count increment</button> </div> ); } export default App;
Foo.js
import React from 'react'; const Foo = ({ onClick }) => { console.log('Foo 组件: render'); return <button onClick={onClick}>Foo 组件中的 button</button>; }; export default Foo;
Нажмите кнопку в присоединении подсчета приложений, вы можете увидеть каждый раз, когда Foo повторно видно, но фактически, когда подсчет изменяется, родительский компонент для повторного визуализации, и подбаллии не нуждаются в рекретере, ток Ситуация не будет проблем.
Но если компонент Foo очень сложный и огромный компонент, то необходимо оптимизировать компонент Foo, и useCallback может пригодиться.
Код оптимизирован с помощью useCallback
App.js
Оберните свойство функции, переданное дочернему компоненту, с помощью useCallback вimport React, { useCallback, useState } from 'react'; import Foo from './Foo'; function App() { const [count, setCount] = useState(0); const fooClick = useCallback(() => { console.log('点击了 Foo 组件的按钮'); }, []); return ( <div style={{ padding: 50 }}> <Foo onClick={fooClick} /> <p>{count}</p> <button onClick={() => setCount(count + 1)}>count increment</button> </div> ); } export default App;
Foo.js
Используйте React.memo для переноса компонентов (компоненты класса наследуют PureComponent Тот же эффект)import React from 'react'; const Foo = ({ onClick }) => { console.log('Foo 组件: render'); return <button onClick={onClick}>Foo 组件中的 button</button>; }; export default React.memo(Foo);
Нажмите еще раз
count increment
кнопка, вы можете видеть, что родительский компонент обновляется, но дочерний компонент не будет сброшенrender
-
Сценарий 2: Нужен как другой
hooks
зависимости, здесь используйте толькоuseEffect
дать демонстрациюВ этом примере по состоянию
page
изменения для повторного запроса сетевых данных, когдаpage
изменения, мы надеемся вызватьuseEffect
вызовите сетевой запрос, в то время какuseEffect
называется вgetDetail
функцию, чтобы использовать последнююpage
, так что вuseEffect
нужно зависеть отgetDetail
функция для вызова последнейgetDetail
использовать
useCallback
код перед обработкойApp.js
import React, { useEffect, useState } from 'react'; const request = (p) => new Promise(resolve => setTimeout(() => resolve({ content: `第 ${p} 页数据` }), 300)); function App() { const [page, setPage] = useState(1); const [detail, setDetail] = useState(''); const getDetail = () => { request(page).then(res => setDetail(res)); }; useEffect(() => { getDetail(); }, [getDetail]); console.log('App 组件:render'); return ( <div style={{ padding: 50 }}> <p>Detail: {detail.content}</p> <p>Current page: {page}</p> <button onClick={() => setPage(page + 1)}>page increment</button> </div> ); } export default App;
Но, согласно приведенному выше письму, это приведет к
App
Компонент зацикливается бесконечноrender
, то нужно использоватьuseCallback
обрабатыватьиспользовать
useCallback
обработанный кодApp.js
import React, { useEffect, useState, useCallback } from 'react'; const request = (p) => new Promise(resolve => setTimeout(() => resolve({ content: `第 ${p} 页数据` }), 300)); function App() { const [page, setPage] = useState(1); const [detail, setDetail] = useState(''); const getDetail = useCallback(() => { request(page).then(res => setDetail(res)); }, [page]); useEffect(() => { getDetail(); }, [getDetail]); console.log('App 组件:render'); return ( <div style={{ padding: 50 }}> <p>Detail: {detail.content}</p> <p>Current page: {page}</p> <button onClick={() => setPage(page + 1)}>page increment</button> </div> ); } export default App;
В этот момент вы можете видеть,
App
компоненты могут быть выполнены нормальноrender
. Используйте только здесьuseEffect
демонстрировать, как и другиеhooks
, вам также необходимо соответствующим образом оптимизировать -
useCallback
Краткое описание сценариев использования:-
Передайте свойства функции субкомпонентам, и когда субкомпоненты необходимо оптимизировать, свойства функции должны быть
useCallback
пакет -
функционировать как другие
hooks
, функция должна бытьuseCallback
пакет
-
Сценарии использования useMemo
-
такой же
useCallback
Сценарий 1: когда требуется оптимизация производительности подкомпонентов, использование в основном такое же -
такой же
useCallback
Сценарий 2: Нужен как другойhooks
Использование в основном такое же, когда зависимости -
Когда требуется большое количество или сложные операции, для повышения производительности можно использовать
useMemo
кеш данныхЗдесь также используется функция кэширования данных useMemo.До изменения зависимостей функции, завернутые в useMemo, не будут выполняться повторно.
Посмотрите на пример ниже,
App
Компонент имеет два состояния:count
а такжеNumber
множествоdataSource
, нажмитеincrement
кнопка,count
увеличится, нажмитеfresh
кнопку, он будет получен сноваdataSource
, но интерфейс не нужно отображатьdataSource
, но нужно показатьdataSource
сумма всех элементов в , поэтому нам нужна новая переменнаяsum
для переноски и отображения на странице.См. код ниже
использовать
useMemo
код перед оптимизациейApp.js
import React, { useState } from 'react'; const request = () => new Promise(resolve => setTimeout( () => resolve(Array.from({ length: 100 }, () => Math.floor(100 * Math.random()))), 300 ) ); function App() { const [count, setCount] = useState(1); const [dataSource, setDataSource] = useState([]); const reduceDataSource = () => { console.log('reduce'); return dataSource.reduce((reducer, item) => { return reducer + item; }, 0); }; const sum = reduceDataSource(); const refreshClick = () => { request().then(res => setDataSource(res)); }; return ( <div style={{ padding: 50 }}> <p>DataSource 元素之和: {sum}</p> <button onClick={refreshClick}>Refresh</button> <p>Current count: {count}</p> <button onClick={() => setCount(count + 1)}>increment</button> </div> ); } export default App;
Откройте консоль, вы можете видеть, что независимо от того, нажмите
increment
илиRefresh
кнопка,reduceDataSource
функция будет выполнена один раз, ноdataSource
имеет 100 элементов, поэтому мы определенно хотимdataSource
Пересчитывать при измененииsum
значение, на этот разuseMemo
Это удобно.использовать
useMemo
оптимизированный кодApp.js
import React, { useMemo, useState } from 'react'; const request = () => new Promise(resolve => setTimeout( () => resolve(Array.from({ length: 100 }, () => Math.floor(100 * Math.random()))), 300 ) ); function App() { const [count, setCount] = useState(1); const [dataSource, setDataSource] = useState([]); const sum = useMemo(() => { console.log('reduce'); return dataSource.reduce((reducer, item) => { return reducer + item; }, 0); }, [dataSource]); const refreshClick = () => { request().then(res => setDataSource(res)); }; return ( <div style={{ padding: 50 }}> <p>DataSource 元素之和: {sum}</p> <button onClick={refreshClick}>Refresh</button> <p>Current count: {count}</p> <button onClick={() => setCount(count + 1)}>increment</button> </div> ); } export default App;
На данный момент вы можете увидеть это, только нажав
Refresh
кнопка,useMemo
中的函数才会重新执行。 нажмитеincrement
Когда кнопка нажата, сумма остается предыдущим кэшированным результатом и не будет пересчитана. -
useMemo
Краткое описание сценариев использования:-
перейти к дочерним компонентамтип ссылкисвойств, а когда необходимо оптимизировать подкомпоненты, свойства должны быть
useMemo
пакет -
значение ссылочного типа, как и другие
hooks
зависимости, вам нужно использоватьuseMemo
Обернуть, вернуть значение свойства -
Когда требуется большое количество или сложные операции, для повышения производительности можно использовать
useMemo
Кэшировать данные для экономии вычислительных затрат
-
Поэтому во время использования useCallback и useMemo, если в этом нет необходимости, нет необходимости его использовать, а частое использование может увеличить стоимость сравнения зависимостей и снизить производительность.
Девять, как вызвать состояние или метод дочернего компонента в родительском компоненте
В функциональном компоненте нет экземпляра компонента, он не похож на компонент класса, подсборку или вызовы методов в состоянии, связанном с помощью примера подсборки.
Итак, в функциональном компоненте, как вызвать состояние или метод дочернего компонента в родительском компоненте? Ответ заключается в использованииuseImperativeHandle
грамматика
useImperativeHandle(ref, createHandle, [deps])
-
Первый параметр — это значение ref, которое можно передать через атрибуты или использовать с forwardRef.
-
Второй параметр — это функция, которая возвращает объект, и свойства в объекте будут смонтированы на текущем свойстве первого параметра ref.
-
Третий параметр — это набор зависимых элементов, таких же, как useEffect, useCallback, useMemo.При изменении зависимости второй параметр будет повторно выполнен и перемонтирован в текущее свойство первого параметра.
Применение
Уведомление:
- Третий параметр зависимость должна быть заполнена по мере необходимости, если меньше, то это приведет к тому, что возвращаемое свойство объекта будет ненормальным, а если слишком много, то вызовет
createHandle
Повторить - компонент или
hook
, для того жеref
, можно использовать только один разuseImperativeHandle
, если он повторяется много раз, он будет выполнен позжеuseImperativeHandle
изcreateHandle
Возвращаемое значение заменит ранее выполненноеuseImperativeHandle
изcreateHandle
возвращаемое значение
Foo.js
import React, { useState, useImperativeHandle, useCallback } from 'react';
const Foo = ({ actionRef }) => {
const [value, setValue] = useState('');
/**
* 随机修改 value 值的函数
*/
const randomValue = useCallback(() => {
setValue(Math.round(Math.random() * 100) + '');
}, []);
/**
* 提交函数
*/
const submit = useCallback(() => {
if (value) {
alert(`提交成功,用户名为:${value}`);
} else {
alert('请输入用户名!');
}
}, [value]);
useImperativeHandle(
actionRef,
() => {
return {
randomValue,
submit,
};
},
[randomValue, submit]
);
/* !! 返回多个属性要按照上面这种写法,不能像下面这样使用多个 useImperativeHandle
useImperativeHandle(actionRef, () => {
return {
submit,
}
}, [submit])
useImperativeHandle(actionRef, () => {
return {
randomValue
}
}, [randomValue])
*/
return (
<div className="box">
<h2>函数组件</h2>
<section>
<label>用户名:</label>
<input
value={value}
placeholder="请输入用户名"
onChange={e => setValue(e.target.value)}
/>
</section>
<br />
</div>
);
};
export default Foo;
App.js
import React, { useRef } from 'react';
import Foo from './Foo'
const App = () => {
const childRef = useRef();
return (
<div>
<Foo actionRef={childRef} />
<button onClick={() => childRef.current.submit()}>调用子组件的提交函数</button>
<br />
<br />
<button onClick={() => childRef.current.randomValue()}>
随机修改子组件的 input 值
</button>
</div>
);
};
10. Справочные документы
написать на обороте
Если в письме есть что-то неправильное или неточное, вы можете высказать свое ценное мнение, большое спасибо.
Если вам нравится или помогаете, добро пожаловать в Star, что также является поощрением и поддержкой для автора.