Как работает React hook useRef() под капотом? Что это за ссылка на самом деле?

#javascript #html #reactjs #react-hooks

#javascript #HTML #reactjs #реагирующие хуки

Вопрос:

Я столкнулся с некоторыми проблемами с React hook useRef(), и я думаю, это потому, что я все еще не совсем понял концепцию и функциональность этого.

Я знаю, что ее можно использовать как «глобальную» переменную, которая находится за пределами области действия функции. Итак, следующий счетчик работает просто отлично. Мне просто нужно было принудительно обновить его, потому что изменение myCounter.current свойства само по себе не запускает повторный запуск.

 const { useState, useRef } = React;

function App() {

  const myCounter = useRef(0);
  const [forceUpdate,setForceUpdate] = useState(true);
  
  const handleClick = (e) => {
    myCounter.current =1;
    setForceUpdate((prev)=>!prev);
  }

  return (
    <div>
      <div>{myCounter.current}</div>
      <button onClick={handleClick}>Click</button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));  
 <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>  

Мои сомнения начинаются, когда я использую его для хранения ссылки на HTML-элемент. Из приведенных ниже документов React мы знаем, что при каждом рендеринге будет получен один и тот же объект. Итак, я получаю одну и ту же ссылку на html-элемент при каждом рендеринге (по крайней мере, до тех пор, пока он продолжает монтироваться).

Из React Docs

Это работает, потому что useRef() создает простой объект JavaScript. Единственная разница между useRef() и созданием объекта {current: …} самостоятельно заключается в том, что useRef предоставит вам один и тот же объект ref при каждом рендеринге.

Например:

 const { useState, useRef } = React;

function App() {

  const myDivElement = useRef(null);
  
  const [forceUpdate,setForceUpdate] = useState(true);
  
  const handleClick = (e) => {
    if (myDivElement.current.style.color === 'red') {
      myDivElement.current.style.color='black';  
    }
    else {
      myDivElement.current.style.color='red';
    }
    setForceUpdate((prev)=>!prev);
  }

  return (
    <div>
      <div ref={myDivElement}><b>Some content inside my Div element</b></div>
      <button onClick={handleClick}>Click</button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));  
 <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>  

Note: I know it’s not the best practice to have multiple questions inside here. But this one is more of a conceptual kind and understanding the following items would really help me to fully grasp the functionality of this hook.

Item 1

What does the reference myDivElement.current points to exactly? Does it point to the node object of that element inside the virtual DOM? Because I know that when I change a CSS property of it, for example, I see that change reflected on the DOM as we can see from the snippet above.

Item 2

Can I say that what I get in myDivElement.current object is one of the type HTMLElement (MDN link) ? If not, what kind/type of object is it?

Item 3

myDivElement.current is being initialized with null value. When does that change to the reference of the div ? Does it happen after 1st render?

EXTRA EDIT:

Я создал этот дополнительный фрагмент, чтобы показать некоторое разъяснение поведения React при сравнении DOM-узлов, которые были изменены ref access.

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

  • Когда вы принудительно меняете синий цвет через изменение в state и возвращаете его как встроенный атрибут в качестве возврата рендеринга, вы сразу видите, что он запускает повторный рендеринг (потому что он изменяет состояние blue), и вы видите синий цвет.

  • Но странно то, что когда вы снова нажимаете 1-ю кнопку, чтобы изменить цвет с помощью ref , он снова становится красным / черным, но теперь вы не можете вернуть его к синему, даже если вы принудительно обновите его. Он повторно отобразит, но не обновит DOM.

  • Поскольку state стала true синей, react сравнивает результат рендеринга с результатом в виртуальном DOM (который был синим с тех пор, как вы нажали Force Blue в первый раз) и возвращается как равные узлы. Он не знает, что DOM на самом деле красный, потому что вы изменили напрямую через ref .

 const { useState, useRef } = React;

function App() {

  const myDivElement = useRef(null);
  
  const [blue,setBlue] = useState(false);
  
  const [forceUpdate,setForceUpdate] = useState(true);
  
  const renderTimes = useRef(0);
  
  renderTimes.current =1;
  
  const handleClick = (e) => {
    if (myDivElement.current.style.color === 'red') {
      myDivElement.current.style.color='black';  
    }
    else {
      myDivElement.current.style.color='red';
    }
    //setForceUpdate((prev)=>!prev);
    //setBlue(false);
  }
  
  const handleClick2 = (e) => {
    setForceUpdate((prev)=>!prev);
    // setBlue(false);
  }
  
  const handleClick3 = (e) => {
    setBlue(true);
  }

  const divStyle = {
  color: 'blue',
  };

  return (
    <div>
      <div ref={myDivElement} style={{ color: blue? 'blue' : 'initial'}}><b>Some content inside my Div element</b></div>
      <p>I was rendered {renderTimes.current} time(s)</p>
      <button onClick={handleClick}>Toggle Color with useRef()</button>
      <button onClick={handleClick2}>Force Update</button>
      <button onClick={handleClick3}>Force Blue as inline style</button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));  
 <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>  

Ответ №1:

На что именно указывает ссылка myDivElement.current

myDivElement.current получает ссылку на базовый элемент DOM

Могу ли я сказать, что то, что я получаю в myDivElement.current object, относится к типу HTMLElement

Когда вы присваиваете ссылку элементу div, она myDivElement.current будет иметь тип HTMLDivElement

myDivElement.current инициализируется нулевым значением. Когда это изменится на ссылку div? Происходит ли это после 1-го рендеринга?

Во время первого рендеринга объект ref присваивается DOMNode при его создании. Обновления узла DOM также приведут к изменению объекта

Комментарии:

1. » myDivElement.current получает ссылку на базовый элемент DOM » Что именно вы имеете в виду под этим? Фактический DOM? Это похоже на обход виртуального DOM? Разве я не должен получать ссылку на VDOM? Если я получаю ссылку на сам DOM, я изменяю ее, и React обновляет VDOM на основе этого изменения? Разве не должно быть наоборот? Например, наш код, приводящий к изменениям в VDOM, и React обновляет DOM на основе этого? Спасибо.

2. ref содержит ссылку на фактический DOM, а не на виртуальный DOM, также изменения, которые вы делаете через ref, напрямую связаны с манипуляциями с DOM. Также в большинстве случаев вам нужно получить доступ только к определенным атрибутам из DOM, для которых вы используете ref. Настройка состояния или обновление стилей должно выполняться с помощью классов css или хотя бы с помощью style prop в JSX

3. Но означает ли это, что VDOM будет отличаться от самого DOM? Например, у моего div узла в VDOM не будет встроенного стиля color: 'red' , а у фактического DOM он будет? Как React обрабатывает это разделение при следующем рендеринге? Не будет ли она заменена содержимым VDOM? Еще раз спасибо!

4. VDOM отличается от DOM и является минимальной версией DOM. И react не заменяет исходный dom виртуальным DOM, вместо этого просто вносит изменения в обновление