Реактивный компонент создает новый экземпляр состояния при повторном рендеринге, даже если состояние не изменилось - PullRequest
1 голос
/ 25 апреля 2020

Я использую Socket.IO с React.js, и я хочу, чтобы веб-сокет запускался только при рендеринге указанного компонента c, поэтому я использую веб-сокет в качестве состояния этого компонента, например:

const Comp = () => {
  const [ws] = useState(socketIO());
  // ... the rest of the component ...
};

Но при повторном рендеринге Comp создается новое соединение с веб-сокетом, даже если я не делаю никаких изменений в соединении. Через некоторое время у меня получается более 10 подключений к веб-сокету. Как сделать так, чтобы компонент сохранял только 1 соединение? Я не хочу, чтобы подключение к веб-сокету стало глобальным.

1 Ответ

1 голос
/ 25 апреля 2020

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

const Comp = () => {
  const [ws] = useState(socketIO());
  // ... the rest of the component ...
};

будет всегда вызывать socketIO, поэтому он может передать возвращаемое значение в useState.

. Для вычисления только значения один раз , когда компонент создается впервые, используйте ссылку (useRef) для представления информации об экземпляре, не являющейся состоянием (такая вещь, которую вы бы сохранили непосредственно в компоненте класса) например):

const Comp = () => {
  const {current: instance} = useRef({});
  const ws = instance.ws = instance.ws || socketIO();
  // ... the rest of the component ...
};

{} по-прежнему оценивается каждый раз, когда вызывается Comp, но эти издержки тривиальны. Вы получаете от useRef объект со свойством current, относящимся к изменяемому объекту - объект, который всегда будет одинаковым в течение всего времени существования компонента. Вторая строка использует свойство ws этого объекта, инициализируя его в первый раз, если оно ложно.

Это использование вызывается в useRef документах :

Однако useRef() полезен не только для атрибута ref. Это удобно для хранения любого изменяемого значения примерно так же, как вы используете поля экземпляров в классах.

Вот пример с заменой для socketIO:

const { useRef, useState } = React;

function socketIO() {
  console.log("socketIO called");
  return {};
}

const Comp = () => {
  console.log("Comp called");
  const {current: instance} = useRef({});
  const ws = instance.ws = instance.ws || socketIO();
  const [counter, setCounter] = useState(0);
  
  return (
    <div>
      {counter} <input type="button" value="+" onClick={() => setCounter(c => c + 1)} />
    </div>
  );
};

ReactDOM.render(<Comp/>, document.getElementById("root"));
<div id="root"></div>
<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>

Я должен отметить, что заманчиво использовать useMemo здесь, но useMemo не гарантирует, что ваша функция будет создавать запомненный результат не будет вызван во второй раз. useMemo для оптимизации производительности, а не семантики. Начиная с документов :

Вы можете полагаться на использованиеMemo в качестве оптимизации производительности, а не в качестве гарантии c. В будущем React может выберите «забыть» некоторые ранее запомненные значения и пересчитать их при следующем рендере, например, чтобы освободить память для компонентов вне экрана. Напишите свой код, чтобы он по-прежнему работал без useMemo, а затем добавьте его для оптимизации производительности.

(их выделение)

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