Реагируйте перехватывает useCallback с параметрами внутри цикла
Я пытаюсь узнать о функциональности хуков, но я не могу понять, как я должен использовать эту функцию useCallback
должным образом. Насколько я понимаю из правил о хуках, я должен называть их на высшем уровне, а не в логике (например, в циклах). Что приводит меня в замешательство, как я должен реализовать useCallback
на компоненты, которые отображаются из цикла.
Взгляните на следующий пример, где я создаю 5 кнопок с onClick
обработчик, который печатает номер кнопки на консоли.
const Example = (props) => {
const onClick = (key) => {
console.log("You clicked: ", key);
};
return(
<div>
{
_.times(5, (key) => {
return (
<button onClick={() => onClick(key)}>
{key}
</button>
);
})
}
</div>
);
};
console.log("hello there");
ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>
Прямо сейчас он создает новый экземпляр функции каждый раз, когда Пример рендерит из-за моего использования функции стрелки в <button onClick={() => onClick(key)}>
что я делаю, потому что хочу onClick
Функция, чтобы узнать, какая кнопка называется. Я думал, что смогу предотвратить это с помощью новой функциональной реакции, используя useCallback
Однако, если я применю его к const onClick
тогда он будет по-прежнему создавать новый экземпляр при каждом рендере из-за встроенной функции стрелки, необходимой для key
параметр, и мне не разрешено применять его к визуализации в цикле, насколько я знаю (особенно, если порядок цикла может измениться правильно?).
Так как бы я пошел о реализации useCallback
в этом сценарии при сохранении той же функциональности? Это вообще возможно?
2 ответа
Простой ответ здесь, вы, вероятно, не должны использовать useCallback
Вот. Точка useCallback
должен передать тот же экземпляр функции оптимизированным компонентам (например, PureComponent
или же React.memo
компоненты), чтобы избежать ненужных визуализаций.
В этом случае вы не имеете дело с оптимизированными компонентами (или, как я подозреваю, в большинстве случаев), поэтому на самом деле нет причин запоминать обратные вызовы, как с useCallback
,
Предполагая, что запоминание важно, однако, лучшее решение здесь, вероятно, состоит в том, чтобы использовать одну функцию вместо пяти: вместо уникальной функции для каждой кнопки выполняется key
закрыв, вы можете прикрепить key
к элементу:
<button data-key={key}>{key}</button>
А затем прочитайте ключ от event.target.dataset["key"]
внутри обработчика одного клика:
const Example = (props) => {
// Single callback, shared by all buttons
const onClick = React.useCallback((e) => {
// Check which button was clicked
const key = e.target.dataset["key"]
console.log("You clicked: ", key);
}, [/* dependencies */]);
return(
<div>
{
_.times(5, (key) => {
return (
<button data-key={key} onClick={onClick}>
{key}
</button>
);
})
}
</div>
);
};
console.log("hello there");
ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>
Все это, как говорится, позволяет запоминать несколько функций в одном хуке. useCallback(fn, deps)
эквивалентно useMemo(() => fn, deps)
, а также useMemo
может использоваться для запоминания нескольких функций одновременно:
const clickHandlers = useMemo(() => _.times(5, key =>
() => console.log("You clicked", key)
), [/* dependencies */]);
const Example = (props) => {
const clickHandlers = React.useMemo(() => _.times(5, key =>
() => console.log("You clicked", key)
), [/* dependencies */])
return(
<div>
{
_.times(5, (key) => {
return (
<button onClick={clickHandlers[key]}>
{key}
</button>
);
})
}
</div>
);
};
console.log("hello there");
ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>
Возможно, есть случай, когда это полезно, но в этом случае я бы либо оставил это в покое (и не беспокоился об оптимизации), либо использовал бы один обработчик для каждой кнопки.
Вам нужно использовать замыкание, и вы можете сделать это даже сuseCallback
;
На вашем примере:
const Example = (props) => {
const handleClick = useCallback((key) => (event) => {
console.log("You clicked: ", key);
}, []);
return(
<div>
{
_.times(5, (key) => {
return (
<button onClick={handleClick(key)}>
{key}
</button>
);
})
}
</div>
);
};
Этот пример прост, без необходимости обновлятьhandleClick
, но будьте осторожны с запоминаемыми функциями: их обычно необходимо обновлять, если вы используете реквизиты внутри замыкания. Обновите[]
соответственно.