React перехватывает useCallback с параметрами внутри цикла

Я пытаюсь узнать о функциональности хуков, однако я не совсем понимаю, как я должен правильно использовать функцию useCallback. Насколько я понимаю из правил о хуках, я должен называть их верхним уровнем, а не внутри логики (например, циклами). Это приводит меня в замешательство, как я должен реализовать useCallback для компонентов, которые отображаются из цикла.

Взгляните на следующий фрагмент кода, где я создаю 5 кнопок с обработчиком onClick, который выводит номер кнопки на консоль.

const Example = (props) => {
  const onClick = (key) => {
    console.info("You clicked: ", key);
  };
  
  return(
    <div>
      {
        _.times(5, (key) => {
          return (
            <button onClick = {() => onClick(key)}>
              {key}
            </button>
          );
        })
      }
    </div>
  );
};
console.info("hello there");

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src = "https://cdn.jsdelivr.net/npm/[email protected]/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, и я m не разрешено применять его к рендерингу внутри цикла, насколько мне известно (особенно если порядок цикла может измениться?).

Итак, как мне реализовать useCallback в этом сценарии, сохранив при этом ту же функциональность? Это вообще возможно?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
26
0
16 311
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Простой ответ: вам, вероятно, не следует использовать здесь useCallback. Смысл useCallback состоит в том, чтобы передать один и тот же экземпляр функции оптимизированным компонентам (например, PureComponent или React.memoized компонентам), чтобы избежать ненужного повторного рендеринга.

В этом случае (или, как я подозреваю, в большинстве случаев) вы не имеете дело с оптимизированными компонентами, поэтому на самом деле нет причин запоминать обратные вызовы, как с 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.info("You clicked: ", key);
  }, [/* dependencies */]);
  
  return(
    <div>
      {
        _.times(5, (key) => {
          return (
            <button data-key = {key} onClick = {onClick}>
              {key}
            </button>
          );
        })
      }
    </div>
  );
};
console.info("hello there");

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src = "https://cdn.jsdelivr.net/npm/[email protected]/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.info("You clicked", key)
), [/* dependencies */]);

const Example = (props) => {
  const clickHandlers = React.useMemo(() => _.times(5, key => 
    () => console.info("You clicked", key)
  ), [/* dependencies */])
  
  return(
    <div>
      {
        _.times(5, (key) => {
          return (
            <button onClick = {clickHandlers[key]}>
              {key}
            </button>
          );
        })
      }
    </div>
  );
};
console.info("hello there");

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src = "https://cdn.jsdelivr.net/npm/[email protected]/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>

Возможно, есть случай, когда это полезно, но в этом случае я бы либо оставил его в покое (и не беспокоился об оптимизации), либо использовал бы один обработчик для каждой кнопки.

Часть атрибутов data-* этого ответа также представлена ​​в Реагировать на документацию для оптимизации событий обратного вызова для большого количества элементов.

James Hay 29.11.2019 08:04

ОП не спрашивал мнения о том, когда уместно использовать обратный вызов. Это не ответ.

tshm001 12.08.2021 19:31

@ tshm001 Они спрашивали о «правильном использовании», и иногда правильное использование - не использовать его. Я показал, как запоминать массив обратных вызовов (useMemo — та же цель, что и useCallback). Если у вас есть лучший ответ, не стесняйтесь публиковать его.

Retsam 13.08.2021 04:02

Другие вопросы по теме