Реагируйте перехватывает useCallback с параметрами внутри цикла - PullRequest
6 голосов
/ 05 марта 2019

Я пытаюсь узнать о функциональности хуков, но я не могу понять, как я должен правильно использовать функцию 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 в этом сценарии при сохранении той же функциональности?Это вообще возможно?

1 Ответ

4 голосов
/ 06 марта 2019

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

В этом случае вы не имеете дело с оптимизированными компонентами (или, как я подозреваю, в большинстве случаев), поэтому на самом деле нет причин запоминать обратные вызовы, такие как 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>

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

...