Реагировать на установщики useState, вызывая повторную визуализацию - PullRequest
2 голосов
/ 25 февраля 2020

Мне интересно, ожидается ли ожидаемое поведение для установщика в: const [state, setState] = useState(0) для запуска повторного рендеринга компонента. Так что, если я передам установщик компоненту как пропеллер, должен ли он вызвать повторную визуализацию, и если да, то почему? Есть ли способ избежать этого?

Я создаю очень простую песочницу, чтобы продемонстрировать поведение: https://codesandbox.io/s/bold-maxwell-qj5mi

Здесь мы можем видеть при просмотре console.logs что компонент кнопки перерисовывается при каждом щелчке, в то время как переданная в него функция incrementCounter не меняется. Что дает?

Ответы [ 2 ]

2 голосов
/ 25 февраля 2020

Если вы запомнили кнопку, вы не испытываете такого поведения.

В частности, это:

const Button = memo(({ incrementCounter }) => {
  const renderCount = useRef(1);
  console.log("button rendered: ", renderCount.current);
  renderCount.current++;
  return <button onClick={incrementCounter}>Increment</button>;
});

CodeSandbox Mirror:

Edit delicate-http-b6sym


Обновление

Если вы хотите что-то из документов первое предложение расскажет вам, почему это происходит. Я знаю, что для setState, но та же самая концепция следует за крючком useState, но документы для такого рода отстой. Вы можете проверить эту часть документов, особенно там, где написано "Строка 9:" ...

Помните, что когда состояние изменяется в компоненте X, компонент X перерисовывается, вместе со всеми своими детьми. Я знаю, что React говорит вам «поднимать состояние вверх», чего я никогда не понимал, потому что поднятие состояния вверх приводит к чрезмерной загрузке повторных визуализаций.

Именно поэтому кнопка выполняет повторную визуализацию .. потому что состояние меняется в его родителя. Родитель (<App />) изменил свое состояние counter, что вызывает повторную визуализацию компонента <App /> и его дочерних элементов, включая <Button />.

По моему мнению, React трудно контролировать состояние и повторный рендеринг, с которым Redux может помочь, но в целом такие вещи, как memo, useCallback, et c .. все это кажется мне бинтами. Если вы поместите свое состояние в неправильный компонент, у вас будет плохое время.

Оборачивание <Button /> компонента в memo в основном говорит: если этот компонент имеет parent (в нашем случае <App />), и этот родитель перерисовывает, я хочу посмотреть на все наши реквизиты, и если наши реквизиты не изменились по сравнению с тем, что мы получили в прошлый раз, не перерисовывать. По сути, только повторно сделать, если наши реквизиты меняются. Вот почему memo исправляет это .. потому что функция, которую мы используем для обработки реквизита incrementCounter, не изменяется - она ​​остается постоянной.

Я добавил несколько примеров ниже, демонстрирующих это.


Оригинальный ответ / фрагмент:

const { memo, useState, useCallback, useEffect, useRef } = React;
const { render } = ReactDOM;

const App = () => {
  const [counter, setCounter] = useState(0);
  const incrementCounter = useCallback(() => {
    setCounter(c => c + 1);
  }, [setCounter]);

  useEffect(() => {
    console.log("increment changed!");
  }, [incrementCounter]);

  return (
    <div>
      <CountValue counter={counter} />
      <Button incrementCounter={incrementCounter} />
    </div>
  );
}

const CountValue = ({ counter }) => {
  return <div>Count value: {counter}</div>;
};

const Button = memo(({ incrementCounter }) => {
  const renderCount = useRef(1);
  console.log("button rendered: ", renderCount.current);
  renderCount.current++;
  return <button onClick={incrementCounter}>Increment</button>
});

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

SNIPPET # 2:

Этот фрагмент показывает, как все, а не просто кнопка, перерисовывается.

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");
  const [counter, setCounter] = useState(0);
  const incrementCounter = () => setCounter(c => c + 1);

  useEffect(() => {
    console.log(" - Increment fired!");
    console.log();
  }, [incrementCounter]);

  return (
    <div>
      <CountValue counter={counter} />
      <Button incrementCounter={incrementCounter} />
      <p>Open console</p>
    </div>
  );
}

const CountValue = ({ counter }) => {
  console.log("CountValue rendered");
  return <div>Count value: {counter}</div>;
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

SNIPPET # 3:

Этот фрагмент показывает, как вы двигаетесь состояние, и т. д. c .. в компонент <CountValue />, компонент <App /> не выполняет повторную визуализацию ..

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");
  
  return (
    <div>
      <CountValue />
      <p>Open console</p>
    </div>
  );
}

const CountValue = () => {
  console.log("CountValue rendered");
  
  const [counter, setCounter] = useState(0);
  const incrementCounter = () => setCounter(c => c + 1);
  
  return (
    <div>
      <div>Count value: {counter}</div>
      <Button incrementCounter={incrementCounter} />
    </div>
  );
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  console.log();
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

SNIPPET # 4:

Этот фрагмент больше похож на мысль эксперимент, который показывает, как использовать рендер реквизит.

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");

  return (
    <div>
      <CountValue present={({ increment, counter }) => {
        return (
          <div><Button incrementCounter={() => increment()} />
          <p>Counter Value: {counter}</p></div>
        )
      }} />
      <p>Open console</p>
    </div>
  );
}

const CountValue = ({ present }) => {
  const [counter, setCounter] = useState(0);
  
  const increment = () => {
    setCounter(c => c + 1);
  }
  
  console.log("CountValue rendered");
  return (
    <React.Fragment>
      {present({ increment, counter })}
    </React.Fragment>
  );
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

SNIPPET # 5:

Похоже, это то, что вы 'after after .. Это только повторно визуализирует CountValue. Это достигается путем передачи метода setCounter, который создается useState, до родительского объекта через объект обратного вызова.

This способ, которым родитель может манипулировать состоянием, без необходимости фактически удерживать состояние.

const { useState, useEffect } = React;
const { render } = ReactDOM;

const App = () => {
  console.log("App rendered");
  let increaseCount;

  return (
    <div>
      <CountValue callback={({increment}) => increaseCount = increment} />
      <Button incrementCounter={() => increaseCount()} />
      <p>Open console</p>
    </div>
  );
}

const CountValue = ({ callback }) => {
  console.log("CountValue rendered");
  const [counter, setCounter] = useState(0);
  
  callback && callback({
    increment: () => setCounter(c => c + 1)
  });
  
  return <p>Counter Value: {counter}</p>;
};

const Button = ({ incrementCounter }) => {
  console.log("Button rendered");
  return <button onClick={incrementCounter}>Increment</button>
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
0 голосов
/ 25 февраля 2020

Да, это ожидается.

Если вы избежите повторного рендеринга, вы не увидите значение, которое было установлено на экране.

В случае, если вы хотите создать значение что вы можете изменить, не вызывая повторного рендеринга, используйте useRef.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...