Реагируйте перехватывает 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, но будьте осторожны с запоминаемыми функциями: их обычно необходимо обновлять, если вы используете реквизиты внутри замыкания. Обновите[]соответственно.

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