Как React.useState вызывает повторный рендеринг?

import { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick = {() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

В приведенном выше примере всякий раз, когда вызывается setCount(count + 1), происходит повторный рендеринг. Мне любопытно узнать поток.

Я попытался заглянуть в исходный код. Я не смог найти никаких упоминаний о useState или других хуках на github.com/facebook/react.

Я установил react@next через npm i react@next и нашел следующее на node_modules/react/cjs/react.development.js

function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

При поиске dispatcher.useState() я смог найти только следующее ...

function resolveDispatcher() {
  var dispatcher = ReactCurrentOwner.currentDispatcher;
  !(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component.') : void 0;
  return dispatcher;
}
var ReactCurrentOwner = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: null,
  currentDispatcher: null
};

Интересно, где я могу найти реализацию dispatcher.useState() и узнать, как она запускает повторный рендеринг при вызове setStatesetCount.

Любой указатель был бы полезен.

Спасибо!

Я бы поверил, что это поведение fiber, так сказал основной алгоритм Reacts. Они описывают это как: React Fiber is an ongoing reimplementation of React's core algorithm.github.com/acdlite/react-fiber-architecture Я бы поверил, что userState создает объект и указатель в fiber, и при изменении его значения запускается повторный рендеринг. Это всего лишь предположение, но я ожидал чего-то похожего на это.

Jimi Pajala 27.10.2018 19:31

Фактически, хуки в реакции используют useState для вызова обновления состояния, которое похоже на setState. Таким образом, всякий раз, когда изменяется состояние, компонент будет повторно отображаться.

Adesh Kumar 05.06.2019 06:57
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
38
2
22 175
4

Ответы 4

setState - это метод класса Component/PureComponent, поэтому он будет делать все, что реализовано в классе Component (включая вызов метода render).

setState выгружает обновление состояния на enqueueSetState, поэтому тот факт, что он привязан к нему, на самом деле является только следствием использования классов и расширения от Component. Как только вы понимаете, что обновление состояния на самом деле не обрабатывается самим компонентом, а this - это просто удобный способ доступа к функциям обновления состояния, тогда useState, явно не привязанный к вашему компоненту, имеет гораздо больший смысл.

Спасибо. Я понял, что использовал setState во втором последнем предложении вопроса. Это была непреднамеренная опечатка, это должен был быть setCount. Я соответствующим образом обновил вопрос. Не стесняйтесь обновлять свой ответ.

Sarbbottam 27.10.2018 21:45

FunctionComponent отличается. В прошлом они были чистыми, простыми. Но теперь у них есть собственное государство. Легко забыть, что реагирующее использование createElement обертывает весь узел JSX, включая FunctionComponent.

function FunctionComponent(){
  return <div>123</div>;
}
const a=<FunctionComponent/>
//after babel transform
function FunctionComponent() {
  return React.createElement("div", null, "123");
}

var a = React.createElement(FunctionComponent, null);

Функциональный компонент был передан для реакции. Когда вызывается setState, его легко перерисовать;

Ключ к пониманию этого - следующий абзац из Хуки FAQ

How does React associate Hook calls with components?

React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).

There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.

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

Давайте рассмотрим ваш встречный пример и посмотрим, что из этого получится. Для простоты я буду ссылаться на скомпилированный исходный код React разработки и Исходный код React DOM, обе версии 16.13.1.

Пример начинается, когда компонент монтируется и useState() (определенный в строке 1581) вызывается в первый раз.

function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

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

console.info(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current)

Если вы примените это в случае примера счетчика, вы заметите, что dispatcher.useState() ссылается на код React-dom. Когда компонент монтируется впервые, useState ссылается на компонент, определенный в строке 15986, который вызывает mountState(). После повторного рендеринга диспетчер изменился, и функция useState() в строке 16077 запускается, вызывая updateState(). Оба метода, mountState() в строке 15352 и updateState() в строке 15371, возвращают пару count, setCount.

Отслеживание ReactCurrentDispatcher становится довольно запутанным. Однако самого факта его существования уже достаточно, чтобы понять, как происходит повторный рендеринг. магия происходит за сценой. Как указано в FAQ, React отслеживает текущий визуализированный компонент. Это означает, что useState()знает, к какому компоненту он подключен, как найти информацию о состоянии и как запустить повторный рендеринг.

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

  1. поддержание государства, основная работа, которую нужно сделать
  2. повторный рендеринг компонента, через который он вызывается, чтобы вызывающий компонент мог получить последнее значение для состояния
  3. поскольку это вызвало повторный рендеринг вызывающего компонента, это означает, что он также должен поддерживать экземпляр или контекст этого компонента, что также позволяет нам использовать useState для нескольких компонентов одновременно.
  4. поскольку мы можем использовать столько useState внутри нашего компонента, сколько захотим, это означает, что он должен поддерживать некоторую идентичность для каждого useState внутри одного и того же компонента.

помня об этом, я пришел к следующему фрагменту

const Demo = (function React() {
  let workInProgress = false;
  let context = null;

  const internalRendering = (callingContext) => {
    context = callingContext;
    context();
  };

  const intialRender = (component) => {
    context = component;
    workInProgress = true;
    context.state = [];
    context.TotalcallerId = -1; // to store the count of total number of useState within a component
    context.count = -1; // counter to keep track of useStates within component
    internalRendering(context);
    workInProgress = false;
    context.TotalcallerId = context.count;
    context = null;
  };

  const useState = (initState) => {
    if (!context) throw new Error("Can only be called inside function");

     // resetting the count so that it can maintain the order of useState being called

    context.count =
      context.count === context.TotalcallerId ? -1 : context.count; 

    let callId = ++context.count;

    // will only initialize the value of setState on initial render
    const setState =
      !workInProgress ||
      (() => {
        const instanceCallerId = callId;
        const memoizedContext = context;
        return (updatedState) => {
          memoizedContext.state[instanceCallerId].value = updatedState;
          internalRendering(memoizedContext);
        };
      })();

    context.state[callId] = context.state[callId] || {
      value: initState,
      setValue: setState,
    };

    return [context.state[callId].value, context.state[callId].setValue];
  };

  return { useState, intialRender };
})();

const { useState, intialRender } = Demo;

const Component = () => {
  const [count, setCount] = useState(1);
  const [greeting, setGreeting] = useState("hello");

  const changeCount = () => setCount(100);
  const changeGreeting = () => setGreeting("hi");

  setTimeout(() => {
    changeCount();
    changeGreeting();
  }, 5000);

  return console.info(`count ${count} name ${greeting}`);
};

const anotherComponent = () => {
  const [count, setCount] = useState(50);
  const [value, setValue] = useState("World");

  const changeCount = () => setCount(500);
  const changeValue = () => setValue("React");

  setTimeout(() => {
    changeCount();
    changeValue();
  }, 10000);

  return console.info(`count ${count} name ${value}`);
};
intialRender(Component);
intialRender(anotherComponent);

здесь useState и initialRender взяты из Demo. intialRender используется для первоначального вызова компонентов, он сначала инициализирует контекст, а затем в этом контексте устанавливает штат как пустой массив (на каждом компоненте есть несколько useState, поэтому нам нужен массив для его поддержки), а также нам нужен прилавок, чтобы сделать счетчик для каждого useState и TotalCounter для хранения общего количества useState, вызываемых для каждого компонента.

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