Bileşenler, Flux'daki Mağazalardan varlıkları hangi yuvalama düzeyinde okumalıdır?


89

Flux'ı kullanmak için uygulamamı yeniden yazıyorum ve Mağazalardan veri alma konusunda sorun yaşıyorum. Çok fazla bileşenim var ve çok fazla iç içe geçiyorlar. Bazıları büyük ( Article), bazıları küçük ve basit ( UserAvatar, UserLink).

Mağazalardan verileri bileşen hiyerarşisinde nerede okumam gerektiği konusunda mücadele ediyorum.
İkisini de pek sevmediğim iki aşırı yaklaşımı denedim:

Tüm varlık bileşenleri kendi verilerini okur

Mağazadan bazı verilere ihtiyaç duyan her bileşen yalnızca varlık kimliğini alır ve varlığı kendi başına alır.
Örneğin Article, geçerarticleId , UserAvatarve UserLinkgeçirilir userId.

Bu yaklaşımın birkaç önemli dezavantajı vardır (örnek kod altında tartışılmıştır).

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>
    )
  }
});

Bu yaklaşımın olumsuz yönleri:

  • Potansiyel olarak Mağazalara abone olan 100'lerin bileşenlerine sahip olmak sinir bozucu;
  • Bu var verilerin güncellenir nasıl ve hangi sırayla takip etmek zor her bileşen bağımsız olarak verileri alır, çünkü;
  • Halihazırda durumdaki bir varlığınız olsa bile , kimliğini tekrar alacak (veya tutarlılığı bozacak) çocuklara vermek zorunda kalırsınız.

Tüm veriler en üst düzeyde bir kez okunur ve bileşenlere aktarılır

Hataları izlemekten yorulduğumda, tüm verileri geri getirmeyi en üst seviyeye koymaya çalıştım. Ancak bunun imkansız olduğu kanıtlandı çünkü bazı varlıklar için çeşitli seviyelerde yuvalama var.

Örneğin:

  • A CategoryiçerirUserAvatar bu kategoriye katkıda bulunan kişileri ;
  • Bir Articlebirkaç tane olabilir Category.

Bu nedenle, Mağazalardan tüm verileri bir düzeyinde almak Articleistersem, şunları yapmam gerekir:

  • Makaleyi şuradan al: ArticleStore ;
  • Tüm makalelerin kategorilerini şuradan al: CategoryStore ;
  • Her kategorinin katılımcılarını ayrı ayrı şuradan alın: UserStore ;
  • Bir şekilde tüm bu verileri bileşenlere aktarın.

Daha da sinir bozucu bir şekilde, derinlemesine iç içe geçmiş bir varlığa ihtiyaç duyduğumda, ek olarak geçmek için her bir yuvalama düzeyine kod eklemem gerekir.

Özetliyor

Her iki yaklaşım da kusurlu görünüyor. Bu sorunu en zarif şekilde nasıl çözerim?

Hedeflerim:

  • Mağazaların çılgın sayıda abonesi olmamalıdır. Ana bileşenler zaten bunu yapıyorsa, her birinin UserLinkdinlemesi aptalca UserStore.

  • Eğer üst bileşen depodan bir nesne aldıysa (örn. user), İç içe geçmiş herhangi bir bileşenin onu tekrar getirmesini istemiyorum. Sahne aracılığıyla geçirebilmeliyim.

  • Tüm varlıkları (ilişkiler dahil) en üst düzeyde getirmek zorunda olmamalıyım çünkü bu, ilişkileri eklemeyi veya kaldırmayı karmaşıklaştırır. İç içe geçmiş bir varlık her yeni ilişki aldığında (örneğin kategori a alır curator) tüm yuvalama düzeylerinde yeni sahne tanıtmak istemiyorum .


1
Bunu gönderdiğiniz için teşekkürler. Az önce burada çok benzer bir soru yayınladım: groups.google.com/forum/… Gördüğüm her şey, ilişkili verilerden ziyade düz veri yapılarıyla ilgiliydi. Bu konuda en iyi uygulamaları görmemek beni şaşırttı.
uberllama

Yanıtlar:


37

Çoğu kişi, hiyerarşinin üst kısmına yakın bir denetleyici görünümü bileşenindeki ilgili mağazaları dinleyerek başlar.

Daha sonra, pek çok ilgisiz sahne hiyerarşiden derinlemesine iç içe geçmiş bir bileşene aktarılıyor gibi göründüğünde, bazı insanlar daha derin bir bileşenin mağazalardaki değişiklikleri dinlemesine izin vermenin iyi bir fikir olduğuna karar verecek. Bu, bileşen ağacının bu daha derin dalının ilgili olduğu sorun alanının daha iyi bir kapsüllenmesini sağlar. Bunu mantıklı bir şekilde yapmak için yapılacak iyi argümanlar var.

Bununla birlikte, her zaman en üstte dinlemeyi ve tüm verileri basitçe aktarmayı tercih ederim. Hatta bazen mağazanın tüm durumunu alıp hiyerarşiden tek bir nesne olarak geçireceğim ve bunu birden çok mağaza için yapacağım. Böylece, ArticleStore'nin durumu için bir payandam veUserStore . Derinlemesine iç içe geçmiş denetleyici görünümlerinden kaçınmanın veriler için tekil bir giriş noktası sağladığını ve veri akışını birleştirdiğini görüyorum. Aksi takdirde, birden fazla veri kaynağım var ve bu hata ayıklamak zor olabilir.

Bu strateji ile tür denetimi daha zordur, ancak React'in PropTypes'iyle prop olarak büyük nesne için bir "şekil" veya yazı şablonu oluşturabilirsiniz. Bakınız: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -geçerlilik

Mağazalardaki mağazalar arasında veri ilişkilendirme mantığını koymak isteyebileceğinizi unutmayın. Senin Yani ArticleStorekudretini ve her ile alakalı Kullanıcılar şunlardır içinden sağlar rekor . Bunu görünümlerinizde yapmak, mantığı görünüm katmanına itmek gibi geliyor, bu da mümkün olduğunda kaçınmanız gereken bir uygulamadır.waitFor()UserStoreArticlegetArticles()

Ayrıca kullanmak isteyebilirsiniz transferPropsTo()ve birçok insan bunu yapmaktan hoşlanır, ancak okunabilirlik ve dolayısıyla sürdürülebilirlik için her şeyi açık tutmayı tercih ederim.

FWIW, benim anladığım kadarıyla David Nolen, Om çerçevesiyle (bir şekilde Flux uyumludur ) kök düğümdeki tek bir veri giriş noktasıyla benzer bir yaklaşım benimsiyor - Flux'taki eşdeğeri yalnızca bir denetleyici görünümüne sahip olmak olacaktır. tüm mağazaları dinlemek. Bu, shouldComponentUpdate()=== ile referansla karşılaştırılabilen değişmez veri yapıları kullanılarak verimli hale getirilir . Değişmez veri yapıları için David'in mori'sine veya Facebook'un immutable-js'lerine göz atın . Om hakkındaki sınırlı bilgim, öncelikle JavaScript MVC Çerçevelerinin Geleceği'nden geliyor


4
Ayrıntılı bir cevap için teşekkürler! Mağaza durumunun her iki anlık görüntüsünü de aktarmayı düşündüm. Ancak bu bana mükemmel bir sorun verebilir çünkü UserStore'un her güncellemesi ekrandaki tüm Makalelerin yeniden oluşturulmasına neden olur ve PureRenderMixin hangi makalelerin güncellenmesi gerektiğini ve hangilerinin güncellenmediğini söyleyemez. İkinci önerinize gelince, ben de düşündüm, ancak her getArticles () çağrısında makalenin kullanıcısı "enjekte edilirse" makale referans eşitliğini nasıl koruyabileceğimi anlayamıyorum. Bu yine potansiyel olarak bana mükemmel problemler verecektir.
Dan Abramov

1
Demek istediğini anlıyorum, ama çözümler var. Birincisi, bir makale için bir dizi kullanıcı kimliği değiştiyse shouldComponentUpdate () 'i kontrol edebilirsiniz, bu temelde bu özel durumda PureRenderMixin'de gerçekten istediğiniz işlevselliği ve davranışı elle yuvarlamaktır.
fisherwebdev

3
Orada belli olduğumda Sağ ve yine sadece bunu yapmak gerekir olan mağazalar için durum bu güncellemenin birkaç kez dakikada en fazla olmamalıdır perf sorunları. Tekrar teşekkürler!
Dan Abramov

8
Ne tür bir uygulama oluşturduğunuza bağlı olarak, tek bir giriş noktasını tutmak, ekranda doğrudan aynı köke ait olmayan daha fazla "widget" sahibi olmaya başlar başlamaz zarar verecektir. Bu şekilde zorla yapmaya çalışırsanız, o kök düğümün çok fazla sorumluluğu olacaktır. Müşteri tarafında olmasına rağmen KISS ve SOLID.
Rygu

37

Ulaştığım yaklaşım, her bileşenin verilerini (ID'leri değil) bir destek olarak almasını sağlamaktır. İç içe yerleştirilmiş bir bileşenin ilgili bir varlığa ihtiyacı varsa, onu almak üst bileşene bağlıdır.

Örneğimizde, bir nesne olan (muhtemelen veya tarafından alınan ) Articlebir articlepervane olmalıdır .ArticleListArticlePage

Çünkü Articleaynı zamanda render etmek istiyor UserLinkve UserAvatarmakalenin yazarı için abone olacak UserStoreve durumunda kalacaktır author: UserStore.get(article.authorId). Daha sonra render edecek UserLinkve UserAvatarbununla this.state.author. Daha ileriye aktarmak isterlerse, yapabilirler. Hiçbir alt bileşenin bu kullanıcıyı tekrar alması gerekmeyecek.

Tekrarlamak için:

  • Hiçbir bileşen hiçbir zaman bir destek olarak ID almaz; tüm bileşenler ilgili nesnelerini alır.
  • Alt bileşenlerin bir varlığa ihtiyacı varsa, onu almak ve bir destek olarak geçmek ebeveynin sorumluluğundadır.

Bu, sorunumu oldukça güzel bir şekilde çözdü. Bu yaklaşımı kullanmak için yeniden yazılmış kod örneği:

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>
    )
  }
});

Bu, en içteki bileşenleri aptal tutar, ancak bizi en üst düzey bileşenlerin cehennemini karmaşıklaştırmaya zorlamaz.


Sadece bu cevabın üstüne bir bakış açısı eklemek için. Kavramsal olarak, bir sahiplik kavramı geliştirebilirsiniz. Bir Veri Toplama verildiğinde, verilere gerçekten sahip olan en üst görünümde sonuçlandırın (dolayısıyla deposunu dinleyerek). Birkaç Örnek: 1. AuthData'nın Uygulama Bileşenine ait olması gerekir, Yetkilendirmedeki herhangi bir değişiklik, Uygulama Bileşeni tarafından tüm alt öğelere props olarak yayılmalıdır. 2. Makale Verileri, Cevapta Önerildiği gibi ArticlePage veya Article List'e ait olmalıdır. artık ArticleList'in herhangi bir alt öğesi, o veriyi gerçekte hangi bileşenin aldığına bakılmaksızın ArticleList'ten AuthData ve / veya ArticleData alabilir.
Saumitra R. Bhave

2

Benim çözümüm çok daha basit. Kendi durumuna sahip her bileşenin mağazalarda konuşmasına ve dinlemesine izin verilir. Bunlar çok denetleyici benzeri bileşenlerdir. Durumu korumayan ancak yalnızca oluşturma işlemlerine izin veren daha derin iç içe bileşenlere izin verilmez. Sadece saf render için sahne alırlar, çok görüntü gibi.

Bu şekilde her şey durum bilgili bileşenlerden durum bilgisi olmayan bileşenlere doğru akar. Durum bilgisini düşük tutmak.

Sizin durumunuzda, Makale durum bilgisine sahip olacak ve bu nedenle mağazalarla ve UserLink vb. İle görüşmeler, yalnızca makale.user'ı prop olarak alacak şekilde işleyecektir.


1
Makalede kullanıcı nesnesi özelliği varsa bunun işe yarayacağını düşünüyorum, ancak benim durumumda yalnızca userId'ye sahip (çünkü gerçek kullanıcı UserStore'da depolanıyor). Yoksa fikrini mi kaçırıyorum? Bir örnek paylaşır mısın?
Dan Abramov

Makaledeki kullanıcı için bir getirme işlemi başlatın. Veya kullanıcı kimliğini "genişletilebilir" hale getirmek için API arka ucunuzu değiştirin, böylece kullanıcıyı bir kerede alabilirsiniz. Her iki durumda da daha derin iç içe yerleştirilmiş bileşenleri basit görünümler ve yalnızca sahne öğelerine bağlı tutun.
Rygu

2
Makaledeki getirmeyi başlatmaya gücüm yetmez çünkü birlikte oluşturulması gerekiyor. Genişletilebilir API'ler gelince, kullanılan onlara sahip olmak, ancak bunlar mağazalardan ayrıştırmak çok sorunludur. Bu nedenle yanıtları düzleştirmek için bir kütüphane bile yazdım .
Dan Abramov

0

2 felsefenizde anlatılan sorunlar, herhangi bir tek sayfalık uygulamada ortaktır.

Bu videoda kısaca tartışılmaktadır: https://www.youtube.com/watch?v=IrgHurBjQbg ve Relay ( https://facebook.github.io/relay ), tanımladığınız değiş tokuşun üstesinden gelmek için Facebook tarafından geliştirilmiştir.

Rölenin yaklaşımı oldukça veri merkezlidir. Bu, "Bu görünümdeki her bileşen için yalnızca gerekli verileri bir sorguda sunucuya nasıl alırım?" Sorusunun cevabıdır. Ve aynı zamanda Relay, bir bileşen birden fazla görünümde kullanıldığında kodda çok az bağlantıya sahip olmanızı sağlar.

Aktarma bir seçenek değilse , "Tüm varlık bileşenleri kendi verilerini okur", tanımladığınız durum için bana daha iyi bir yaklaşım gibi görünüyor. Bence Flux'taki yanlış anlamanın bir mağaza ne olduğu. Mağaza kavramı, bir modelin veya bir nesne koleksiyonunun saklandığı yer olamaz. Mağazalar, uygulamanızın görünüm oluşturulmadan önce verileri koyduğu geçici yerlerdir. Var olmalarının gerçek nedeni, farklı mağazalardaki verilerdeki bağımlılıklar sorununu çözmektir.

Flux'ın belirtmediği şey, bir mağazanın modeller konseptiyle ve nesnelerin koleksiyonuyla nasıl ilişkili olduğudur (a la Backbone). Bu anlamda, bazı insanlar aslında bir akış deposu yapıyorlar, belirli bir türden nesnelerin koleksiyonunu, kullanıcının tarayıcıyı tüm zaman boyunca açık tuttuğu, ancak benim anladığım kadarıyla, bir mağaza değil. olması gerekiyordu.

Çözüm, görünümünüzü oluşturmak için gerekli varlıkların (ve muhtemelen daha fazlasının) depolandığı ve güncel tutulduğu başka bir katmana sahip olmaktır. Modelleri ve koleksiyonları soyutlayan bu katmanı seçerseniz, alt bileşenlerin kendi verilerini elde etmek için tekrar sorgulamanız gerekmesi sorun olmaz.


1
Anladığım kadarıyla, Dan'in farklı türdeki nesneleri tutma ve bunlara kimlikler aracılığıyla başvurma yaklaşımını anladığım kadarıyla, bu nesnelerin önbelleğini tutmamıza ve bunları yalnızca tek bir yerde güncellememize olanak tanıyor. Örneğin, aynı kullanıcıya atıfta bulunan birkaç Makaleniz varsa ve ardından kullanıcının adı güncellenirse, bu nasıl başarılabilir? Tek yol, tüm Makaleleri yeni kullanıcı verileriyle güncellemektir. Bu, arka ucunuz için belge ile ilişkisel db arasında seçim yaparken karşılaştığınız problemin aynısıdır. Bununla ilgili düşüncelerin neler? Teşekkür ederim.
Alex Ponomarev
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.