Jaka jest różnica między `useRef` a` createRef`?

120

Przeglądałem dokumentację haków, kiedy się natknąłem useRef.

Patrząc na ich przykład…

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

… Wydaje się, że useRefmożna go zastąpić createRef.

function TextInputWithFocusButton() {
  const inputRef = createRef(); // what's the diff?
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputRef.current.focus();
  };
  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

Po co mi haczyk na referencje? Dlaczego useRefistnieje?

Rico Kahler
źródło

Odpowiedzi:

153

Różnica polega na tym, createRefże zawsze utworzy nowy ref. W komponencie opartym na klasach zwykle umieszcza się ref we właściwości instancji podczas konstruowania (np this.input = createRef().). Nie masz tej opcji w komponencie funkcji.useRefdba o to, aby za każdym razem zwracać ten sam ref, co przy początkowym renderowaniu.

Oto przykładowa aplikacja pokazująca różnicę w zachowaniu tych dwóch funkcji:

import React, { useRef, createRef, useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [renderIndex, setRenderIndex] = useState(1);
  const refFromUseRef = useRef();
  const refFromCreateRef = createRef();
  if (!refFromUseRef.current) {
    refFromUseRef.current = renderIndex;
  }
  if (!refFromCreateRef.current) {
    refFromCreateRef.current = renderIndex;
  }
  return (
    <div className="App">
      Current render index: {renderIndex}
      <br />
      First render index remembered within refFromUseRef.current:
      {refFromUseRef.current}
      <br />
      First render index unsuccessfully remembered within
      refFromCreateRef.current:
      {refFromCreateRef.current}
      <br />
      <button onClick={() => setRenderIndex(prev => prev + 1)}>
        Cause re-render
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edytuj 1rvwnj71x3

Ryan Cogswell
źródło
38

createRefzawsze zwraca nową referencję, którą zwykle przechowujesz jako pole w instancji składnika klasy. useRefzwraca ten sam ref przy każdym renderowaniu instancji komponentu funkcjonalnego. To właśnie pozwala na utrzymywanie stanu ref między renderowaniami, mimo że nigdzie go nie przechowujesz.

W twoim drugim przykładzie ref będzie odtwarzany przy każdym renderowaniu.

Joe Clay
źródło
To jest nieprawidłowe, czy masz odniesienie do potwierdzenia swojego oświadczenia?
Adeel Imran,
1
Jest tu komentarz jednego z twórców Reacta wyjaśniający, że tak to działa: reddit.com/r/reactjs/comments/a2pt15/ ... Chciałbym wiedzieć, co Twoim zdaniem jest nieprawidłowe w tej odpowiedzi.
Joe Clay
Widziałem ten link, zanim spróbowałem odpowiedzieć na to pytanie, gdzie wskazuje ten fakt w udostępnionym przez Ciebie łączu? Nie mogłem tego znaleźć? :)
Adeel Imran,
1
Link, który udostępniłem, pokazuje uproszczoną implementację useRef, opublikowaną przez jednego z programistów React. To nie to samo, co zwykłe wywołanie createRef, ponieważ createRefnie jest to podpięcie i nie utrzymuje żadnego stanu między połączeniami. Odpowiedź Ryana Cogswella również zawiera dobry przykład różnic.
Joe Clay
1
Moje rozumienie z tego kontekstu tylko wywnioskowało, że useRef jest niestandardowym punktem zaczepienia, który używa wewnątrz createRef. Dziękuję za podzielenie się wiedzą.
Adeel Imran,
13

tldr

A refto zwykły obiekt JS { current: <some value> }.

React.createRef()to fabryka zwracająca ref { current: null }- bez magii .

useRef(initValue)zwraca również ref { current: initValue }pokrewny do React.createRef(). Poza tym zapamiętuje to odniesienie, aby było trwałe w wielu renderach w komponencie funkcji .

Jest to wystarczające do użycia React.createRefw komponentach klas, ponieważ obiekt ref jest przypisany do zmiennej instancji , stąd jest dostępny w całym komponencie i jego cyklu życia:
this.myRef = React.createRef(); // stores ref in "mutable" this context (class)

useRef(null)w zasadzie jest równoważne useState(React.createRef())[0] 1 .


1 Wymienić useRefz useState+createRef

Poniższy tweet był dla mnie pouczający:

useRef()jest w zasadzie useState({current: initialValue })[0].

Dzięki spostrzeżeniom z tldrsekcji możemy teraz podsumować:

useRef(null)jest w zasadzie useState(React.createRef())[0].

Powyższy kod „nadużywa”, useStateaby zachować zwrócony ref React.createRef(). [0]po prostu wybiera część wartości useState- [1]będzie ustawiaczem.

useStatepowoduje ponowne renderowanie w przeciwieństwie do useRef. Bardziej formalnie, React porównuje stare i nowe odniesienie do obiektu useState, gdy nowa wartość jest ustawiana za pomocą metody ustawiającej. Gdybyśmy mutować stanu useStatebezpośrednio (w przeciwieństwie do setter pw), jego zachowanie staje się bardziej lub mniej równoważne do useRef, a nie ponowne renderowanie jest wyzwalany już:

// Example of mutaing object contained in useState directly
const [ref] = useState({ current: null })
ref.current = 42; // doesn't cause re-render

Uwaga: nie rób tego! Skorzystaj ze zoptymalizowanego useRefAPI zamiast wymyślać koło na nowo. Powyższe ma charakter ilustracyjny.

ford04
źródło
3

Żeby podkreślić cel:

createRefjest tak proste, jak return {current: null}. Jest to sposób na obsługę ref=rekwizytów w najnowocześniejszy sposób i to wszystko (podczas gdy oparte na ciągach znaków jest zbyt rozwlekłe).

useRefprzechowuje niektóre dane przed renderowaniem, a ich zmiana nie powoduje ponownego renderowania (tak jak to useStaterobi). Rzadko są spokrewnieni. Wszystko, czego oczekujesz od komponentu opartego na klasach, przechodzi do pól instancji ( this.* =) wygląda jak kandydat do zaimplementowania useRefw komponentach funkcjonalnych.

Say useCallbackdziała jak metody klasy ograniczonej ( this.handleClick = .....bind(this)) i może zostać ponownie zaimplementowane (ale na pewno nie powinniśmy ponownie wymyślać koła) z useRef.

Innymi przykładami są odniesienia DOM, identyfikatory limitów czasu / interwałów, identyfikatory lub odwołania do bibliotek zewnętrznych.

PS Uważam, że zespół React lepiej wybrał inne nazewnictwo, useRefaby uniknąć nieporozumień createRef. Może useAndKeepa nawet usePermanent.

skyboyer
źródło
1

Kolejny, ale ważny dodatek do odpowiedzi innych.

Nie możesz ustawić nowej wartości dla createRef. Ale możesz useRef.

const ur = useRef();
const cr = createRef();

ur.current = 10; // you can do it, and value is set
cr.current = 10; // you can, but it's no good, it will not change it
Zielony
źródło
ref jest zwykłym obiektem, możesz zmienić jego currentwłaściwość jak zwykle (właśnie to przetestowałem). Nie ma znaczenia, czy ref jest tworzony za pomocą useReflub createRef.
ford04