Stan ReactJS vs prop


121

Może to oznaczać stąpanie po granicy między odpowiedzialnym a upartym, ale będę poruszać się w tę iz powrotem, jak skonstruować komponent ReactJS w miarę wzrostu złożoności i przydałby się jakiś kierunek.

Pochodząc z AngularJS, chcę przekazać mój model do komponentu jako właściwość i pozwolić komponentowi bezpośrednio modyfikować model. A może powinienem podzielić model na różnestate właściwości i skompilować go z powrotem podczas wysyłania z powrotem w górę? Jak działa ReactJS?

Weź przykład edytora postów na blogu. Próba bezpośredniej modyfikacji modelu kończy się następująco:

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

Co wydaje się niewłaściwe.

Czy jest to bardziej sposób Reacta na utworzenie naszej textwłaściwości modelu statei skompilowanie go z powrotem do modelu przed zapisaniem, na przykład:

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

Nie wymaga to wywołania this.forceUpdate(), ale wraz z rozwojem modelu (post może mieć autora, temat, tagi, komentarze, oceny itp.), Komponent zaczyna być naprawdę skomplikowany.

Czy pierwsza metoda z ReactLink jest najlepsza?

Odpowiedzi:


64

Twoje drugie podejście jest bardziej podobne. React nie tyle dba o modele, ile o wartości i ich przepływ przez aplikację. Idealnie byłoby, gdyby twój model postu był przechowywany w jednym komponencie w katalogu głównym. Następnie tworzysz komponenty potomne, z których każdy zużywa części modelu.

Możesz przekazywać wywołania zwrotne do elementów podrzędnych, które muszą zmodyfikować dane, i wywoływać je z komponentu podrzędnego.

Bezpośrednia modyfikacja this.props lub this.state nie jest dobrym pomysłem, ponieważ React nie będzie w stanie wychwycić zmian. Dzieje się tak, ponieważ React wykonuje płytkie porównanie Twojego wpisu, aby określić, czy się zmienił.

Zrobiłem to jsfiddle, aby pokazać, jak dane mogą przepływać z komponentu zewnętrznego do wewnętrznego.

Te handleClickmetody wykazuje 3 sposoby (IM) właściwie stanie aktualizacji:

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

Ale co zrobimy, jeśli mamy nieprzejrzysty model z własnymi funkcjami manipulowania stanem? Na przykład załóżmy, że zamiast textpola mamy setText metodę, która wykonuje walidację i kilka innych rzeczy. Widzę, że metoda (2) działa, jeśli setTextjest czysta i zwraca zupełnie nową instancję modelu. Jednakże, gdybyśmy setTexttylko zaktualizowali stan wewnętrzny, nadal musielibyśmy zadzwonić forceUpdate, prawda?
hugomg

1
Tak, możesz zadzwonić forceUpdate, ale w tym momencie „wyciekasz” z Reacta. Lepszym rozwiązaniem może być przekazanie setState()wywołania zwrotnego do nieprzezroczystego modelu, aby uniknąć konieczności ręcznego uruchamiania ponownego renderowania.
jxg

Nadal nie jestem pewien, czy w pełni rozumiem. Więc jakikolwiek komponent przeznaczony do modyfikowania danych musi wykonać głęboką kopię przekazanych właściwości? Następnie zmodyfikuj i wyślij tę kopię w górę, aby nie modyfikować oryginalnych danych? W końcu zmiana dotrze aż do katalogu głównego, gdzie zostanie rozwiązana, a cała aplikacja zostanie ponownie wyrenderowana? Czy to prawda?
Nicholas

97

Aktualizacja 2016: React został zmieniony, a wyjaśnienie „props vs state” stało się bardzo proste. Jeśli komponent wymaga zmiany danych - ustaw go w stanie, w przeciwnym razie we właściwościach. Ponieważ rekwizyty są tylko do odczytu teraz do .

Jaka jest dokładna różnica między rekwizytami a stanem?

Można znaleźć dobre wyjaśnienie tutaj (pełna wersja)

Zmiana rekwizytów i stanu


1
faktycznie setProps () może zmienić właściwości wewnątrz komponentu i wyzwolić ponowne renderowanie
WaiKit Kung

2
setPropsjest przestarzały i nie powinien być używany. Wymiana polega na ponownym wydaniu komponentu i pozwoleniu Reactowi zająć się różnicami.
jdmichal

A jeśli szukasz filmu wyjaśniającego: youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis

35

Z dokumentu React

rekwizyty są niezmienne: są przekazywane od rodzica i są „własnością” rodzica. Aby zaimplementować interakcje, do komponentu wprowadzamy zmienny stan. this.state jest prywatne dla komponentu i można je zmienić, wywołując this.setState (). Kiedy stan jest aktualizowany, komponent ponownie się renderuje.

Z TrySpace : kiedy właściwości (lub stan) są aktualizowane (przez setProps / setState lub rodzica), komponent również renderuje się ponownie.


16

Czytanie z Thinking in React :

Przeanalizujmy każdy z nich i dowiedzmy się, który jest stanem. Po prostu zadaj trzy pytania dotyczące każdego elementu danych:

  1. Czy jest przekazywany przez rodzica za pośrednictwem rekwizytów? Jeśli tak, to prawdopodobnie nie jest stan.
  2. Czy zmienia się z czasem? Jeśli nie, to prawdopodobnie nie jest stan.

  3. Czy możesz obliczyć to na podstawie dowolnego innego stanu lub właściwości w swoim komponencie? Jeśli tak, to nie jest stan.


13

Nie jestem pewien, czy odpowiadam na twoje pytanie, ale odkryłem, że szczególnie w dużej / rosnącej aplikacji wzór kontenera / komponentu działa niesamowicie dobrze.

Zasadniczo masz dwa komponenty React:

  • „czysty” komponent wyświetlania, który zajmuje się stylizacją i interakcją DOM;
  • komponent kontenera, który zajmuje się dostępem / zapisywaniem danych zewnętrznych, zarządzaniem stanem i renderowaniem komponentu wyświetlania.

Przykład

Uwaga: Ten przykład jest prawdopodobnie zbyt prosty, aby zilustrować korzyści płynące z tego wzoru, ponieważ jest dość rozwlekły w tak prostym przypadku.

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

Korzyści

Oddzielając logikę wyświetlania i zarządzanie danymi / stanem, otrzymujesz komponent wyświetlania wielokrotnego użytku, który:

  • można łatwo iterować z różnymi zestawami rekwizytów, używając czegoś takiego jak plac zabaw z komponentami reakcji
  • można opakować w inny kontener dla innego zachowania (lub połączyć z innymi składnikami, aby zbudować większe części aplikacji

Masz również komponent kontenera, który zajmuje się całą komunikacją zewnętrzną. Powinno to ułatwić elastyczne podejście do sposobu uzyskiwania dostępu do danych w przypadku późniejszego wprowadzenia poważnych zmian *.

Ten wzorzec również znacznie upraszcza pisanie i implementowanie testów jednostkowych.

Po kilkukrotnym iterowaniu dużej aplikacji React odkryłem, że ten wzorzec sprawia, że ​​rzeczy są stosunkowo bezbolesne, zwłaszcza gdy masz większe komponenty z obliczonymi stylami lub skomplikowanymi interakcjami DOM.

* Przeczytaj wzorzec flux i spójrz na Marty.js , który w dużej mierze zainspirował tę odpowiedź (a ostatnio często używałem ) Redux (i React-Redux ), które bardzo dobrze implementują ten wzór.

Uwaga dla osób czytających to w 2018 roku lub później:

React bardzo się rozwinął od czasu napisania tej odpowiedzi, szczególnie wraz z wprowadzeniem hooków . Jednak podstawowa logika zarządzania stanem z tego przykładu pozostaje taka sama, a co ważniejsze, korzyści wynikające z oddzielenia logiki stanu i prezentacji nadal obowiązują w ten sam sposób.


0

Myślę, że używasz anty-wzorca, który Facebook już wyjaśnił pod tym linkiem

Oto rzecz, którą znalazłeś:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

Gdy składnik wewnętrzny zostanie wyrenderowany po raz pierwszy, będzie miał {foo: 'bar'} jako wartość właściwości. Jeśli użytkownik kliknie kotwicę, stan komponentu nadrzędnego zostanie zaktualizowany do {value: {foo: 'barbar'}}, uruchamiając proces ponownego renderowania komponentu wewnętrznego, który otrzyma {foo: 'barbar'} jako nowa wartość rekwizytu.

Problem polega na tym, że ponieważ komponent nadrzędny i komponent wewnętrzny współużytkują odniesienie do tego samego obiektu, kiedy obiekt zostanie zmutowany w linii 2 funkcji onClick, właściwość, jaką miał komponent wewnętrzny, ulegnie zmianie. Tak więc, gdy rozpoczyna się proces ponownego renderowania i wywoływana jest metoda shouldComponentUpdate, this.props.value.foo będzie równe nextProps.value.foo, ponieważ w rzeczywistości this.props.value odwołuje się do tego samego obiektu, co nextProps.value.

W związku z tym, ponieważ przegapimy zmianę w rekwizycie i zerwamy proces ponownego renderowania, interfejs użytkownika nie zostanie zaktualizowany z „bar” na „barbar”


Czy możesz również wysłać Innercomponentskod?
Abdullah Khan
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.