Na jakim poziomie zagnieżdżania komponenty powinny odczytywać byty ze składów w Strumieniu?

Przepisuję aplikację na Flux i mam problem z pobieraniem danych ze sklepów. Mam dużo komponentów i dużo gniazd. Niektóre z nich są Duże (Article), niektóre małe i proste (UserAvatar, UserLink).

Zmagałem się z tym, gdzie w hierarchii komponentów powinienem odczytywać dane ze sklepów.
Próbowałem dwóch ekstremalnych podejść, z których żadne mi się nie spodobało: {]}

Wszystkie komponenty encji odczytują własne dane

Każdy komponent, który potrzebuje danych z Store otrzymuje tylko identyfikator encji i sam pobiera encję.
Na przykład, Article jest przekazywana articleId, UserAvatar i UserLink są przekazywane userId.

To podejście ma kilka istotnych wad (omówionych w próbce kodu).

var Article = React.createClass({
  mixins: [createStoreMixin(ArticleStore)],

  propTypes: {
    articleId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      article: ArticleStore.get(this.props.articleId);
    }
  },

  render() {
    var article = this.state.article,
        userId = article.userId;

    return (
      <div>
        <UserLink userId={userId}>
          <UserAvatar userId={userId} />
        </UserLink>

        <h1>{article.title}</h1>
        <p>{article.text}</p>

        <p>Read more by <UserLink userId={userId} />.</p>
      </div>
    )
  }
});

var UserAvatar = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <img src={user.thumbnailUrl} />
    )
  }
});

var UserLink = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <Link to='user' params={{ userId: this.props.userId }}>
        {this.props.children || user.name}
      </Link>
    )
  }
});

Wady tego podejścia:

    To jest frustrujące mieć komponenty 100s potencjalnie subskrybujące sklepy; [37]}
  • trudno jest śledzić jak dane są aktualizowane i w jakiej kolejności ponieważ każdy komponent pobiera jego dane niezależnie;
  • nawet jeśli możeszjuż mieć podmiot w stanie, jesteś zmuszony przekazać jego identyfikator dzieciom, które odzyskają go ponownie (lub złamać spójność).

Wszystkie dane są odczytywane raz na najwyższym poziomie i przekazywane w dół do komponentów

Kiedy byłem zmęczony tropieniem błędów, starałem się umieścić wszystkie dane na najwyższym poziomie. Okazało się to jednak niemożliwe, ponieważ dla niektórych podmiotów mam kilka poziomów gniazdowanie.

Na przykład:

  • a Category zawiera UserAvatars osób, które przyczyniają się do tej kategorii;
  • An Article może mieć kilka Categorys.

Dlatego gdybym chciał odzyskać wszystkie dane ze sklepów na poziomie Article, musiałbym:

  • Odzyskaj artykuł z ArticleStore;
  • Odzyskaj wszystkie kategorie artykułów z CategoryStore;
  • oddzielnie pobieramy współpracowników każdej kategorii z UserStore;
  • jakoś przejść wszystkie te dane aż do składników.

Jeszcze bardziej frustrujące jest to, że ilekroć potrzebuję głęboko zagnieżdżonego bytu, muszę dodać kod do każdego poziomu zagnieżdżenia, aby dodatkowo przekazać go w dół.

Podsumowanie

Oba podejścia wydają się wadliwe. Jak najbardziej elegancko rozwiązać ten problem?

Moje cele:

  • Sklepy nie powinny mieć szalonej liczby abonentów. To głupie dla każdego UserLink słuchać UserStore jeśli komponenty rodzica już to.

  • Jeśli komponent macierzysty odzyskał jakiś obiekt ze store (np. user), nie chcę, aby jakiekolwiek zagnieżdżone komponenty musiały go ponownie pobrać. Powinienem być w stanie przekazać to przez rekwizyty.

  • Nie powinienem pobierać wszystkich encji (w tym relacji) na najwyższym poziomie, ponieważ skomplikowałoby to dodawanie lub usuwanie relacji. Nie chcę wprowadzać nowych rekwizytów na wszystkich poziomach zagnieżdżania za każdym razem, gdy zagnieżdżona jednostka otrzymuje nową relację (np. a curator).

Author: M. Junaid Salaat, 2014-09-06

4 answers

Większość ludzi zaczyna od słuchania odpowiednich sklepów w komponencie kontroler-widok blisko góry hierarchii.

Później, kiedy wydaje się, że wiele nieistotnych rekwizytów jest przekazywanych przez hierarchię do jakiegoś głęboko zagnieżdżonego komponentu, niektórzy ludzie uznali, że dobrym pomysłem jest pozwolić głębszemu komponentowi słuchać zmian w sklepach. Oferuje to lepszą hermetyzację domeny problemu, którą ta głębsza gałąź drzewa komponentów jest około. Istnieją dobre argumenty przemawiające za tym, aby robić to rozsądnie.

[9]}jednak wolę zawsze słuchać na górze i po prostu przekazywać wszystkie dane. Czasami wezmę nawet cały stan sklepu i przekażę go w dół przez hierarchię jako pojedynczy obiekt, i zrobię to dla wielu sklepów. Więc chciałbym mieć prop dla ArticleStore'S stan, a inny dla UserStore'S Stan, itp. Uważam, że unikanie głęboko zagnieżdżonego kontrolera-views utrzymuje pojedynczy punkt wejścia dla danych i jednoczy przepływ danych. W przeciwnym razie mam wiele źródeł danych i może to być trudne do debugowania.

Sprawdzanie typu jest trudniejsze w tej strategii, ale możesz skonfigurować "kształt" lub szablon typu dla dużego obiektu jako propa za pomocą Proptypów Reacta. Zobacz też: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop-validation

Zauważ, że możesz umieścić logikę kojarzenia danych między sklepami w samych sklepach. Tak więc twój ArticleStore Może waitFor() UserStore i zawierać odpowiednich użytkowników z każdym rekordem Article dostarczonym przez getArticles(). Robienie tego w swoich widokach brzmi jak wpychanie logiki do warstwy widoku, której należy unikać, gdy jest to możliwe.

Możesz też pokusić się o użycie transferPropsTo(), a wielu ludzi to lubi, ale wolę zachować wszystko dla czytelności i dlatego konserwacja.

FWIW, rozumiem, że David Nolen przyjmuje podobne podejście ze swoim frameworkiem Om (który jest nieco Kompatybilny Z Flux) z jednym punktem wejścia danych na węźle głównym -- odpowiednikiem Flux byłoby mieć tylko jeden kontroler-widok nasłuchujący wszystkich sklepów. Jest to wydajne dzięki użyciu shouldComponentUpdate() i niezmiennych struktur danych, które mogą być porównywane przez odniesienie, z===. Dla niezmiennych struktur danych, checkout David ' s mori lub Facebook 's immutable-js . Moja ograniczona wiedza o Om pochodzi przede wszystkim z przyszłości frameworków MVC JavaScript

 36
Author: fisherwebdev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-07 08:45:04

Podejście, do którego przybyłem, polega na tym, że każdy komponent otrzymuje swoje dane (nie identyfikatory) jako rekwizyt. Jeśli jakiś zagnieżdżony komponent potrzebuje podmiotu powiązanego, to do komponentu nadrzędnego należy jego odzyskanie.

W naszym przykładzie, Article powinien mieć article rekwizyt, który jest obiektem (prawdopodobnie pobrany przez ArticleList lub ArticlePage).

Ponieważ Article chce również renderować UserLink i UserAvatar dla autora artykułu, zapisze się do UserStore i zachowa author: UserStore.get(article.authorId) w swoim stanie. Następnie renderuje UserLink i UserAvatar z tym this.state.author. Jeśli chcą to przekazać dalej, mogą. Żadne komponenty potomne nie będą musiały ponownie pobierać tego użytkownika.

Do powtórzenia:

  • żaden komponent nigdy nie otrzymuje ID jako rekwizytu; wszystkie komponenty otrzymują swoje odpowiednie obiekty.
  • jeśli komponenty potomne potrzebują encji, obowiązkiem rodzica jest jej odzyskanie i przekazanie jej jako rekwizytu.
To całkiem ładnie rozwiązuje mój problem. Przykład kodu przepisany w celu użycia tego podejście:
var Article = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    article: PropTypes.object.isRequired
  },

  getStateFromStores() {
    return {
      author: UserStore.get(this.props.article.authorId);
    }
  },

  render() {
    var article = this.props.article,
        author = this.state.author;

    return (
      <div>
        <UserLink user={author}>
          <UserAvatar user={author} />
        </UserLink>

        <h1>{article.title}</h1>
        <p>{article.text}</p>

        <p>Read more by <UserLink user={author} />.</p>
      </div>
    )
  }
});

var UserAvatar = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

  render() {
    var user = this.props.user;

    return (
      <img src={user.thumbnailUrl} />
    )
  }
});

var UserLink = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

  render() {
    var user = this.props.user;

    return (
      <Link to='user' params={{ userId: this.props.user.id }}>
        {this.props.children || user.name}
      </Link>
    )
  }
});

To sprawia, że najgłębsze składniki są głupie, ale nie zmusza nas do komplikowania składników najwyższego poziomu.

 36
Author: Dan Abramov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-15 16:24:49

Moje rozwiązanie jest znacznie prostsze. Każdy komponent, który ma swój własny stan, może mówić i słuchać sklepów. Są to elementy bardzo podobne do kontrolera. Głębiej zagnieżdżone komponenty, które nie zachowują stanu, ale tylko renderują rzeczy, nie są dozwolone. Otrzymują tylko rekwizyty do czystego renderowania, bardzo podobne do widoku.

W ten sposób wszystko przepływa z komponentów stanowych do komponentów bezstanowych. / Align = "left" /

W Twoim przypadku artykuł byłby stateczny i dlatego rozmowy ze sklepami, UserLink itp. tylko renderować, aby otrzymać artykuł.użytkownik jako prop.

 0
Author: Rygu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-06 16:22:21

Problemy opisane w Twoich 2 filozofiach są wspólne dla każdej aplikacji jednostronicowej.

Są one omówione krótko w tym filmie: https://www.youtube.com/watch?v=IrgHurBjQbg i Relay ( https://facebook.github.io/relay ) został opracowany przez Facebook, aby przezwyciężyć kompromis, który opisujesz.

Podejście przekaźnika jest bardzo skoncentrowane na danych. Jest to odpowiedź na pytanie " Jak uzyskać tylko potrzebne dane dla każdego komponentu w tym widoku w jednym zapytanie do serwera?"I w tym samym czasie Relay zapewnia, że masz niewiele sprzężenia w kodzie, gdy komponent używany w wielu widokach.

Jeśli Relay nie jest opcją, "wszystkie komponenty entity odczytują własne dane" wydaje mi się lepszym podejściem do opisywanej sytuacji. Myślę, że błędem w Flux jest to, czym jest sklep. Pojęcie sklepu nie oznacza miejsca, w którym przechowywany jest model lub zbiór przedmiotów. Sklepy to tymczasowe miejsca, w których Twoja aplikacja umieszcza dane przed renderowaniem widoku. Prawdziwym powodem ich istnienia jest rozwiązanie problemu zależności między danymi, które trafiają do różnych sklepów.

To, co Flux nie określa, to jak sklep odnosi się do koncepcji modeli i kolekcji obiektów (a la Backbone). W tym sensie niektórzy ludzie robią sklep flux miejsce, w którym można umieścić kolekcję przedmiotów określonego typu, które nie są flush przez cały czas Użytkownik utrzymuje przeglądarkę otwartą, ale, jak I zrozum flux, to nie jest to, co sklep powinien być.

Rozwiązaniem jest posiadanie innej warstwy, w której przechowywane są i aktualizowane elementy niezbędne do renderowania widoku (i potencjalnie więcej). Jeśli ta warstwa abstrakcyjnych modeli i kolekcji, to nie jest problemem, jeśli podkomponenty muszą ponownie zapytać, aby uzyskać własne dane.

 0
Author: Chris Cinelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-16 22:03:56