Я забыл сохранить последнюю версию моей песочницы - ссылка обновлена.

аюсь перейти от компонентов класса к функциональным компонентам, используя новые хуки. Однако он чувствует, что сuseCallback Я получу ненужные визуализации детей в отличие от функций класса в компонентах класса.

Ниже у меня есть два относительно простых фрагмента. Первый - мой пример, написанный как классы, а второй - мой пример, переписанный как функциональные компоненты. Цель состоит в том, чтобы получить то же поведение с функциональными компонентами, что и с компонентами класса.

Тестовый пример компонента класса

class Block extends React.PureComponent {
  render() {
    console.log("Rendering block: ", this.props.color);

    return (
        <div onClick={this.props.onBlockClick}
          style = {
            {
              width: '200px',
              height: '100px',
              marginTop: '12px',
              backgroundColor: this.props.color,
              textAlign: 'center'
            }
          }>
          {this.props.text}
         </div>
    );
  }
};

class Example extends React.Component {
  state = {
    count: 0
  }
  
  
  onClick = () => {
    console.log("I've been clicked when count was: ", this.state.count);
  }
  
  updateCount = () => {
    this.setState({ count: this.state.count + 1});
  };
  
  render() {
    console.log("Rendering Example. Count: ", this.state.count);
    
    return (
      <div style={{ display: 'flex', 'flexDirection': 'row'}}>
        <Block onBlockClick={this.onClick} text={'Click me to log the count!'} color={'orange'}/>
        <Block onBlockClick={this.updateCount} text={'Click me to add to the count'} color={'red'}/>
      </div>
    );
  }
};

ReactDOM.render(<Example/>, document.getElementById('root'));
<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' style='width: 100%; height: 100%'>
</div>

Функциональный компонент тест-кейс

const Block = React.memo((props) => {
  console.log("Rendering block: ", props.color);
  
  return (
      <div onClick={props.onBlockClick}
        style = {
          {
            width: '200px',
            height: '100px',
            marginTop: '12px',
            backgroundColor: props.color,
            textAlign: 'center'
          }
        }>
        {props.text}
       </div>
  );
});

const Example = () => {
  const [ count, setCount ] = React.useState(0);
  console.log("Rendering Example. Count: ", count);
  
  const onClickWithout = React.useCallback(() => {
    console.log("I've been clicked when count was: ", count);
  }, []);
  
  const onClickWith = React.useCallback(() => {
    console.log("I've been clicked when count was: ", count);
  }, [ count ]);
  
  const updateCount = React.useCallback(() => {
    setCount(count + 1);
  }, [ count ]);
  
  return (
    <div style={{ display: 'flex', 'flexDirection': 'row'}}>
      <Block onBlockClick={onClickWithout} text={'Click me to log with empty array as input'} color={'orange'}/>
      <Block onBlockClick={onClickWith} text={'Click me to log with count as input'} color={'cyan'}/>
      <Block onBlockClick={updateCount} text={'Click me to add to the count'} color={'red'}/>
    </div>
  );
};

ReactDOM.render(<Example/>, document.getElementById('root'));
<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' style='width: 100%; height: 100%'>
</div>

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

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

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

Итак, как бы я сделал этоonClick функционировать внутри функционального компонента без повторного рендеринга детей? Это вообще возможно?

Обновление (решение): Используя ответ Райана Когсуэлла, приведенный ниже, я создал собственный хук, чтобы упростить создание функций, подобных классам.

const useMemoizedCallback = (callback, inputs = []) => {
    // Instance var to hold the actual callback.
    const callbackRef = React.useRef(callback);

    // The memoized callback that won't change and calls the changed callbackRef.
    const memoizedCallback = React.useCallback((...args) => {
      return callbackRef.current(...args);
    }, []);

    // The callback that is constantly updated according to the inputs.
    const updatedCallback = React.useCallback(callback, inputs);

    // The effect updates the callbackRef depending on the inputs.
    React.useEffect(() => {
        callbackRef.current = updatedCallback;
    }, inputs);

    // Return the memoized callback.
    return memoizedCallback;
};

Затем я могу очень легко использовать это в компоненте функции и просто передать onClick дочернему элементу. Он больше не будет перерисовывать дочерний объект, но все равно будет использовать обновленные переменные.

const onClick = useMemoizedCallback(() => {
    console.log("NEW I've been clicked when count was: ", count);
}, [count]);

Ответы на вопрос(1)

Ваш ответ на вопрос