Ваш первый вопрос:
В вашем случае это не имеет значения, потому что все отображается в одном компоненте. Если у вас есть список вещей, которые получают обработчик событий, то useCallback может сэкономить вам некоторые рендеры.
В приведенном ниже примере первые 2 элемента отображаются с помощью onClick, который создается каждый раз при повторной визуализации приложения. Это не только вызовет повторную визуализацию элементов, но также приведет к сбою виртуального сравнения DOM, и React создаст Itms в DOM (дорогая операция).
Последние 2 элемента получают созданный onClick когда приложение монтируется и не создается заново при рендеринге приложения, поэтому они никогда не будут рендериться.
const { useState, useCallback, useRef, memo } = React;
const Item = memo(function Item({ onClick, id }) {
const rendered = useRef(0);
rendered.current++;
return (
<button _id={id} onClick={onClick}>
{id} : rendered {rendered.current} times
</button>
);
});
const App = () => {
const [message, setMessage] = useState('');
const onClick = (e) =>
setMessage(
'last clicked' + e.target.getAttribute('_id')
);
const memOnClick = useCallback(onClick, []);
return (
<div>
<h3>{message}</h3>
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={onClick} />
))}
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={memOnClick} />
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Другой пример - когда вы хотите вызвать функцию с эффектом, который также необходимо вызывать вне эффекта, чтобы вы не могли поместить функцию внутрь Эффект. Вы хотите запускать эффект только при изменении определенного значения, поэтому вы можете сделать что-то вроде этого.
//fetchById is (re) created when ID changes
const fetchById = useCallback(
() => console.log('id is', ID),
[ID]
);
//effect is run when fetchById changes so basically
// when ID changes
useEffect(() => fetchById(), [fetchById]);
Ваш второй вопрос:
setValues({ ...prev, [name]: value })
выдаст вам ошибку, потому что вы никогда не определял pref, но если вы имели в виду: setValues({ ...values, [name]: value })
и обернуть обработчик в useCallback, то теперь ваш обратный вызов имеет зависимость от values
и будет без необходимости заново создаваться при изменении значений.
Если вы не Если вы не обеспечите зависимость, то линтер предупредит вас, и вы получите устаревшее закрытие . Вот пример устаревшего закрытия, так как counter.count никогда не будет go вверх, потому что вы никогда не создаете onClick после первого рендера, таким образом, закрытие счетчика всегда будет {count:1}
.
const { useState, useCallback, useRef } = React;
const App = () => {
const [counts, setCounts] = useState({ count: 1 });
const rendered = useRef(0);
rendered.current++;
const onClick = useCallback(
//this function is never re created so counts.count is always 1
// every time it'll do setCount(1+1) so after the first
// click this "stops working"
() => setCounts({ count: counts.count + 1 }),
[] //linter warns of missing dependency count
);
return (
<button onClick={onClick}>
count: {counts.count} rendered:{rendered.current}
</button>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>