Użyć state lub refs w komponentach formularza React.js?

116

Zaczynam od React.js i chcę zrobić prosty formularz, ale w dokumentacji znalazłem na to dwa sposoby.

Pierwszy używa Refs :

var CommentForm = React.createClass({
  handleSubmit: function(e) {
    e.preventDefault();
    var author = React.findDOMNode(this.refs.author).value.trim();
    var text = React.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    // TODO: send request to the server
    React.findDOMNode(this.refs.author).value = '';
    React.findDOMNode(this.refs.text).value = '';
    return;
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

A druga korzysta stan wewnątrz React komponent:

var TodoTextInput = React.createClass({
  getInitialState: function() {
    return {
      value: this.props.value || ''
    };
  },

  render: function() /*object*/ {
    return (
      <input className={this.props.className}
      id={this.props.id}
      placeholder={this.props.placeholder}
      onBlur={this._save}
      value={this.state.value}
      />
    );
  },

  _save: function() {
    this.props.onSave(this.state.value);
    this.setState({value: ''
  });
});

Nie widzę zalet i wad tych dwóch alternatyw, jeśli takie istnieją. Dzięki.

gabrielgiussi
źródło
Czy coś mi umyka? Dlaczego nie używasz obiektu zdarzenia do pobierania wartości formularza? Wydaje się, że jest to jedyny powód, dla którego warto użyć tutaj formularza. Jeśli nie używasz domyślnego zachowania przesyłania i masz odniesienia na danych wejściowych, nie musisz ich zawijać w formularz.
NectarSoft

Odpowiedzi:

143

Krótka wersja: unikaj odniesień.


Są złe z punktu widzenia łatwości konserwacji i tracą wiele z prostoty, jaką zapewnia renderowanie modelu WYSIWYG.

Masz formularz. Musisz dodać przycisk, który resetuje formularz.

  • referencje:
    • manipulować DOM
    • render opisuje, jak wyglądała forma 3 minuty temu
  • stan
    • setState
    • render opisuje wygląd formularza

Masz pole numer CCV w danych wejściowych i kilka innych pól w aplikacji, które są liczbami. Teraz musisz wymusić, aby użytkownik wprowadzał tylko liczby.

  • referencje:
    • dodaj procedurę obsługi onChange (czy nie używamy referencji, aby tego uniknąć?)
    • manipulować domem w onChange, jeśli to nie jest liczba
  • stan
    • masz już moduł obsługi onChange
    • dodaj instrukcję if, jeśli jest niepoprawna, nic nie rób
    • render jest wywoływany tylko wtedy, gdy ma przynieść inny wynik

Ech, nieważne, premier chce, żebyśmy zrobili cienie w czerwonym pudełku, jeśli jest nieważne.

  • referencje:
    • make onChange handler po prostu wywołaj forceUpdate czy coś?
    • zrobić renderowanie w oparciu o ... hę?
    • skąd bierzemy wartość do sprawdzenia w renderowaniu?
    • ręcznie manipulować właściwością className elementu elementu?
    • zgubiłem się
    • przepisać bez referencji?
    • czytać z domeny w renderowaniu, jeśli jesteśmy zamontowani w inny sposób, zakładając, że jest poprawny?
  • stan:
    • usuń instrukcję if
    • make render validate na podstawie this.state

Musimy oddać kontrolę rodzicowi. Dane są teraz w rekwizytach i musimy reagować na zmiany.

  • referencje:
    • zaimplementuj componentDidMount, componentWillUpdate i componentDidUpdate
    • ręcznie porównaj poprzednie właściwości
    • manipulować domeną przy minimalnym zestawie zmian
    • Hej! wdrażamy Reaguj w Reaguj ...
    • jest tego więcej, ale bolą mnie palce
  • stan:
    • sed -e 's/this.state/this.props/' 's/handleChange/onChange/' -i form.js

Ludzie myślą, że refs są „łatwiejsze” niż utrzymywanie ich w stanie. Może to być prawdą przez pierwsze 20 minut, ale z mojego późniejszego doświadczenia to nieprawda. Postaw się w sytuacji, gdy powiesz „Tak, zrobię to za 5 minut” zamiast „Jasne, po prostu przepiszę kilka składników”.

Bandyta
źródło
3
Czy mógłbyś wyjaśnić trochę więcej na temat sed -e 's / this.state / this.props /' 's / handleChange / onChange /' -i form.js?
gabrielgiussi
1
Nie, mam na myśli faktyczne zmiany w domenie. React.findDOMNode(this.refs.foo). Jeśli np. Zmienisz, this.refs.foo.props.barnic się nie stanie.
Brigand
1
np. jeśli <input onChange={this.handleChange} value={this.state.foo} />zmienisz go na <input onChange={this.props.handleChange} value={this.props.foo} />lub zmodyfikujesz funkcje handleChange, aby wywoływały wywołania zwrotne w właściwościach. Tak czy inaczej, jest to kilka małych oczywistych zmian.
Brigand
4
Nie jestem pewien, czy tylko ja uważam, że twoja odpowiedź jest trochę zagmatwana. Czy mógłbyś pokazać kilka przykładów kodu, aby wyjaśnić swoje uwagi?
Rishabh
2
Posiadanie ponad 50 wejść na ekranie i renderowanie po każdej zmianie stanu jest niepożądane. inputIdealne jest komponowanie każdego pola, w którym każde utrzymuje swój własny stan. W pewnym momencie musimy pogodzić te różne niezależne państwa z jakimś większym modelem. Może mamy autozapis na liczniku czasu lub po prostu oszczędzamy. componentWillUnmountTutaj uważam, że jest refsidealny, podczas uzgadniania statez każdego wyciągamy wartość refi żadna nie jest mądrzejsza. Zgadzam się w większości przypadków statejest odpowiedzią, ale przy dużej liczbie inputsużycie odpowiedniego refswzorca jest dobrodziejstwem dla wydajności
luks
105

Widziałem, jak kilka osób cytuje powyższą odpowiedź jako powód, by „nigdy nie używać referencji” i chcę wyrazić swoją opinię (a także kilku innych programistów React, z którymi rozmawiałem).

Sentyment „nie używaj referencji” jest poprawny, gdy mówimy o używaniu ich w instancjach komponentów. Oznacza to, że nie powinieneś używać referencji jako sposobu na przechwytywanie instancji komponentów i wywoływanie na nich metod. Jest to niewłaściwy sposób używania referencji i ma to miejsce, gdy sędziowie szybko idą na południe.

Prawidłowym (i bardzo użytecznym) sposobem użycia referencji jest użycie ich do uzyskania jakiejś wartości z DOM. Na przykład, jeśli masz pole wejściowe dołączające ref do tego wejścia, to późniejsze pobranie wartości przez ref jest w porządku. Bez tego musisz przejść przez dość zorganizowany proces, aby zapewnić aktualizację pola wejściowego do stanu lokalnego lub magazynu flux - co wydaje się niepotrzebne.

Edycja 2019: Witajcie przyjaciele przyszłości. Oprócz tego, o czym wspomniałem kilka lat temu ^, dzięki React Hooks, referencje są również świetnym sposobem na śledzenie danych między renderami i nie ograniczają się do przechwytywania węzłów DOM.

Tyler McGinnis
źródło
3
Twój ostatni akapit ma sens, ale czy możesz wyjaśnić swój drugi akapit? Jaki jest konkretny przykład przechwytywania instancji składnika i wywoływania metody, która została uznana za nieprawidłową?
Danny Libin,
2
Zgodziłbym się z tym. Używam referencji, chyba że / dopóki muszę dokonać walidacji lub manipulowania wartością pola. Jeśli muszę programowo zweryfikować zmianę lub zmienić wartości, używam stanu.
Christopher Davies,
1
Ja też się z tym zgadzam. Podczas fazy odkrywania celowo podchodziłem do ekranu z dużą liczbą danych wejściowych z naiwnością. Wszystkie wartości wejściowe przechowywane w mapie (w stanie) z kluczem id. Nie trzeba dodawać, że wydajność spadła, ponieważ ustawienie stanu i renderowanie 50+ danych wejściowych (niektóre elementy interfejsu użytkownika, które były ciężkie!) Przy tak drobnych zmianach interfejsu użytkownika, jak kliknięcie pola wyboru, nie było idealne. Komponowanie każdego wejścia, które może utrzymać swój własny stan, wydawało się właściwym podejściem. Jeśli potrzebne jest uzgodnienie, po prostu zajrzyj do refsi uzyskaj wartość stanu. Wydaje się, że to naprawdę ładny wzór.
lux
2
Całkowicie się zgadzam. Przyjęta odpowiedź jest moim zdaniem zbyt ogólnikowa.
James Wright,
Zgadzam się. Podczas projektowania ogólnego komponentu Form, uwidacznia to problemy kontrolowanych komponentów i zarządzania koncentracją, obsługą błędów itp. W rzeczywistości nie jest możliwe uzyskanie czystej architektury. Porozmawiaj ze mną, jeśli to konieczne. Przenoszę moje komponenty do referencji.
kushalvm
6

TL; DR Ogólnie rzecz biorąc, refssprzeciwiaj się deklaratywnej filozofii Reacta , więc powinieneś używać ich w ostateczności. Używaj w state / propsmiarę możliwości.


Aby zrozumieć, gdzie używasz refsvs state / props, spójrzmy na niektóre zasady projektowania, których przestrzega React.

Dokumentacja Per React dotyczącarefs

Unikaj używania referencji do wszystkiego, co można zrobić deklaratywnie.

Zgodnie z zasadami projektowania firmy React dotyczącymi kreskowania ucieczki

Jeśli jakiś wzorzec, który jest przydatny do budowania aplikacji, jest trudny do wyrażenia w sposób deklaratywny, zapewnimy dla niego imperatywny interfejs API. (i odsyłają do referencji tutaj)

Co oznacza, że ​​zespół Reacta sugeruje unikanie refsi używanie state / propswszystkiego, co można zrobić w sposób reaktywny / deklaratywny.

@Tyler McGinnis udzielił bardzo dobrej odpowiedzi , stwierdzając również, że

Prawidłowym (i bardzo użytecznym) sposobem użycia referencji jest użycie ich do uzyskania jakiejś wartości z DOM ...

Chociaż możesz to zrobić, będziesz działać przeciwko filozofii React. Jeśli masz wartość w danych wejściowych, z pewnością pochodzi ona z state / props. Aby kod był spójny i przewidywalny, również powinieneś się tego trzymać state / props. Przyjmuję do wiadomości, że refsczasami daje to szybsze rozwiązanie, więc jeśli wykonasz dowód słuszności koncepcji, szybkie i brudne jest dopuszczalne.

To pozostawia nas z kilku przypadków użycia betonu dlarefs

Zarządzanie aktywnością, zaznaczaniem tekstu lub odtwarzaniem multimediów. Wyzwalanie imperatywnych animacji. Integracja z bibliotekami DOM innych firm.

Lyubomir
źródło
5

Ten post jest stary.

Podzielę się moim małym doświadczeniem w jednej sprawie w tej sprawie.

Pracowałem nad dużym komponentem (414 linii) z dużą ilością „dynamicznych” danych wejściowych i dużą ilością danych w pamięci podręcznej. (Nie pracuję sam na stronie, a zmysły podpowiadają mi, że strukturę kodu pewnie dałoby się lepiej rozłożyć, ale nie o to chodzi (no cóż, mogłoby być, ale sobie z tym radzę)

Najpierw pracowałem ze stanem, aby obsłużyć wartości danych wejściowych:

  const [inputsValues, setInputsValues] = useState([])
  const setInputValue = (id, value) => {
    const arr = [...inputsValues]
    arr[id] = value
    setInputsValues(arr)
  }

i oczywiście w wejściach:

value={inputsValues[id] || ''}
onChange={event => setInputValue(id, event.target.value)}

Renderowanie było tak ciężkie, że zmiana danych wejściowych była przerywana na **** (nie próbuj trzymać klawisza wciśniętego, tekst pojawiłby się dopiero po przerwie)

Byłem pewien, że mogę tego uniknąć, używając ref.

skończyło się tak:

  const inputsRef = useRef([])

i na wejściach:

ref={input => (inputsRef.current[id] = input)}

[w moim przypadku dane wejściowe to Material-UI TextField, więc było:

inputRef={input => (inputsRef.current[id] = input)}

]

Dzięki temu nie ma ponownego renderowania, wejście jest płynne, funkcjonalność działa tak samo. Oszczędza cykle i obliczenia, a więc także energię. Zrób to dla Ziemi x)

Mój wniosek: może być nawet potrzebny useRef dla wartości wejściowych.

Kaphar
źródło