W jaki sposób połączony komponent Redux wie, kiedy wykonać ponowne renderowanie?

86

Prawdopodobnie brakuje mi czegoś bardzo oczywistego i chciałbym to wyjaśnić.

Oto moje zrozumienie.
W naiwnym elemencie reagującym mamy states& props. Aktualizacja statez setStateponownym renderowaniem całego komponentu. propssą w większości tylko do odczytu i ich aktualizowanie nie ma sensu.

W komponencie reagującym, który subskrybuje sklep Redux, za pośrednictwem czegoś podobnego store.subscribe(render), jest on oczywiście ponownie renderowany za każdym razem, gdy sklep jest aktualizowany.

React-redux ma pomocnika, connect()który wstrzykuje część drzewa stanu (który jest interesujący dla komponentu) i actionCreators jako propskomponent, zwykle poprzez coś w rodzaju

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

Ale rozumiejąc, że a setStatejest niezbędna TodoListComponentdo reagowania na zmianę drzewa stanu Redux (ponowne renderowanie), nie mogę znaleźć żadnego statelub setStatepowiązanego kodu w TodoListpliku komponentu. Brzmi mniej więcej tak:

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

Czy ktoś może wskazać mi właściwy kierunek, czego mi brakuje?

PS Podążam za przykładem listy rzeczy do zrobienia dołączonej do pakietu Redux .

Joe Lewis
źródło

Odpowiedzi:

109

connectFunkcji generuje element owijający, który działa w sklepie. Gdy akcja jest uruchamiana, zostaje powiadomione wywołanie zwrotne komponentu opakowania. Następnie uruchamia twoją mapStatefunkcję i płytko porównuje obiekt wynikowy z tego czasu z obiektem wynikowym z ostatniego razu (więc gdybyś przepisał pole magazynu redux z tą samą wartością, nie spowodowałoby to ponownego renderowania). Jeśli wyniki są różne, przekazuje wyniki do „rzeczywistego” komponentu jako rekwizyty.

Dan Abramov napisał świetną, uproszczoną wersję connectat ( connect.js ), która ilustruje podstawową ideę, chociaż nie pokazuje żadnej optymalizacji. Mam również linki do wielu artykułów na temat wydajności Redux, w których omawiam kilka powiązanych pomysłów.

aktualizacja

React-Redux v6.0.0 wprowadził kilka poważnych wewnętrznych zmian w sposobie, w jaki podłączone komponenty odbierają dane ze sklepu.

W ramach tego napisałem post, który wyjaśnia, jak działa connectAPI i jego elementy wewnętrzne oraz jak zmieniały się w czasie:

Idiomatic Redux: Historia i implementacja React-Redux

markerikson
źródło
Dzięki! Czy jesteś tą samą osobą, która ostatnio mi pomogła @ HN - news.ycombinator.com/item?id=12307621 z przydatnymi linkami? Miło cię poznać :)
Joe Lewis
4
Tak, to ja :) Spędzam sposób zbyt dużo czasu na szukanie w Internecie dyskusji na Redux - Reddit, HN, więc średni i różnych innych miejsc. Miło, że mogłem pomóc! (Również do Twojej wiadomości, bardzo polecam kanały czatu Reactiflux na Discordzie. Wiele ludzi kręci się tam i odpowiada na pytania. Zwykle jestem tam online wieczorami EST w USA. Link do zaproszenia znajduje się na stronie reaktiflux.com .)
markerikson
Zakładając, że to odpowiedziała na pytanie, czy nie chcesz przyjąć odpowiedzi? :)
markerikson
Czy jest to porównanie samych obiektów, czy też właściwości obiektów przed i po? Jeśli jest to drugie, czy sprawdza tylko pierwszy poziom, czy to właśnie masz na myśli mówiąc „płytki”?
Andy,
Tak, „równość” płytkie zwrotne środki porównać pierwszych pól poziomie każdego obiektu prev.a === current.a && prev.b === current.b && ...... Zakłada się, że wszelkie zmiany danych spowodują nowe odwołania, co oznacza, że ​​zamiast bezpośredniej mutacji wymagane są niezmienne aktualizacje danych.
markerikson
13

Moja odpowiedź jest trochę poza lewym polem. Rzuca światło na problem, który doprowadził mnie do tego postu. W moim przypadku wyglądało na to, że aplikacja nie renderowała się ponownie, mimo że otrzymała nowe rekwizyty.
Twórcy Reacta mieli odpowiedź na to często zadawane pytanie, coś w rodzaju tego, że jeśli (sklep) został zmutowany, w 99% przypadków reakcja nie będzie ponownie renderowana. Jeszcze nic o pozostałych 1%. W tym przypadku mutacja nie miała miejsca.


TLDR;

componentWillReceivePropstak statemożna zsynchronizować plik z nowym props.

Krawędź obudowy: Po stateaktualizacji, następnie aplikacja ma ponownie uczynić!


Okazuje się, że jeśli twoja aplikacja używa tylko statedo wyświetlania swoich elementów, propsmoże aktualizować, ale statenie będzie, więc nie ma ponownego renderowania.

Miałem stateto zależne od propsotrzymanego od Redux store. Danych, których potrzebowałem, nie było jeszcze w sklepie, więc pobrałem je componentDidMount, jak trzeba . Otrzymałem rekwizyty z powrotem, kiedy mój reduktor zaktualizował sklep, ponieważ mój komponent jest połączony przez mapStateToProps. Ale strona nie została wyrenderowana, a stan wciąż był pełen pustych ciągów.

Przykładem tego jest sytuacja, w której użytkownik załadował stronę „edytuj post” z zapisanego adresu URL. Masz dostęp do postIdadresu z adresu URL, ale informacji storejeszcze nie ma, więc możesz je pobrać. Elementy na Twojej stronie są kontrolowanymi komponentami - więc wszystkie wyświetlane dane znajdują się w state.

Korzystając z Redux, dane zostały pobrane, sklep został zaktualizowany, a komponent jest connectedytowany, ale aplikacja nie odzwierciedlała zmian. Przy bliższym przyjrzeniu się propsotrzymaliśmy, ale aplikacja nie została zaktualizowana. statenie zaktualizowany.

Cóż, propsbędzie aktualizować i propagować, ale statenie będzie. Musisz konkretnie powiedzieć, stateaby zaktualizować.

Nie możesz tego zrobić w render(), a componentDidMountjuż skończyłeś swoje cykle.

componentWillReceivePropsto miejsce, w którym aktualizujesz statewłaściwości, które zależą od zmienionej propwartości.

Przykładowe zastosowanie:

componentWillReceiveProps(nextProps){
  if (this.props.post.category !== nextProps.post.category){
    this.setState({
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    })
  }      
}

Muszę wypowiedzieć się na temat tego artykułu, który wyjaśnił mi rozwiązanie, o którym nie wspomniały dziesiątki innych postów, blogów i repozytoriów. Każdy, kto miał problem ze znalezieniem odpowiedzi na ten ewidentnie niejasny problem, oto jest:

Metody cyklu życia komponentów ReactJs - szczegółowe omówienie

componentWillReceivePropsto miejsce, w którym będziesz aktualizować, stateaby być zsynchronizowanym z propsaktualizacjami.

Po stateaktualizacji, następnie pola w zależności od state wykonania ponownego renderowania!

SherylHohman
źródło
3
Od tej React 16.3pory powinieneś używać getDerivedStateFromPropszamiast componentWillReceiveProps. Ale w rzeczywistości nie powinieneś nawet robić tego wszystkiego, co wyartykułowano tutaj
kyw,
to nie działa, za każdym razem, gdy zmieni się stan, wtedy componentWillReceiveProps będzie działać tak wiele przypadków, próbuję to nie działa. Zwłaszcza wielopoziomowe zagnieżdżone lub złożone rekwizyty, więc muszę przypisać komponentowi identyfikator i wyzwolić dla niego zmianę i zaktualizować stan, aby ponownie renderować nowe dane
May Weather VN
0

Ta odpowiedź jest podsumowaniem artykułu Briana Vaughna zatytułowanego You Probably Don't Need Derived State (07 czerwca 2018).

Wyprowadzanie stanu z rekwizytów jest anty-wzorcem we wszystkich jego formach. W tym starsze componentWillReceivePropsi nowsze getDerivedStateFromProps.

Zamiast wyprowadzać stan z rekwizytów, rozważ następujące rozwiązania.

Dwa zalecenia dotyczące najlepszych praktyk

Zalecenie 1. W pełni kontrolowany element
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
Zalecenie 2. W pełni niekontrolowany komponent z kluczem
// parent class
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

// child instance
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Dwie alternatywy, jeśli z jakiegokolwiek powodu zalecenia nie sprawdzają się w Twojej sytuacji.

Alternatywa 1: zresetuj niekontrolowany komponent za pomocą właściwości ID
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Alternatywa 2: Zresetuj niekontrolowany komponent za pomocą metody instancji
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail
  };

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}
Pozwól mi się nad tym zastanowić
źródło
-2

jak wiem jedyne co robi Redux, przy zmianie stanu sklepu wywołuje componentWillRecieveProps, jeśli twój komponent był zależny od zmutowanego stanu i wtedy powinieneś zmusić komponent do aktualizacji, tak jest

1-magazynowa zmiana stanu 2-wywołanie (componentWillRecieveProps (() => {3-składnikowa zmiana stanu}))

ghazaleh javaheri
źródło