Eşzamansız olarak başlatılan React.js bileşenlerinin sunucu tarafında oluşturulması için stratejiler


114

React.js'nin en büyük avantajlarından birinin sunucu tarafında render olması gerekiyor . Sorun, anahtar işlevinin React.renderComponentToString()eşzamanlı olmasıdır, bu da bileşen hiyerarşisi sunucuda işlenirken eşzamansız verilerin yüklenmesini imkansız kılar.

Diyelim ki, yorum yapmak için sayfanın hemen hemen her yerine bırakabileceğim evrensel bir bileşenim var. Yalnızca bir özelliğe, bir tür tanımlayıcıya (örneğin, yorumların altına yerleştirildiği bir makalenin kimliği) sahiptir ve diğer her şey bileşenin kendisi tarafından işlenir (yorumların yüklenmesi, eklenmesi, yönetilmesi).

Flux mimarisini gerçekten seviyorum çünkü birçok şeyi çok daha kolay hale getiriyor ve depoları sunucu ile istemci arasında durum paylaşımı için mükemmel. Yorumları içeren mağazam başlatıldıktan sonra, onu seri hale getirip sunucudan istemciye kolayca geri yükleyebiliyorum.

Soru, mağazamı doldurmanın en iyi yolunun ne olduğudur. Geçtiğimiz günlerde internette çokça arama yaptım ve birkaç stratejiyle karşılaştım, bunların hiçbiri React'in bu özelliğinin ne kadar "tanıtıldığı" düşünüldüğünde gerçekten iyi görünmüyordu.

  1. Bence en basit yol, gerçek oluşturma başlamadan önce tüm mağazalarımı doldurmak. Bu, bileşen hiyerarşisinin dışında bir yer anlamına gelir (örneğin yönlendiricime bağlı). Bu yaklaşımla ilgili sorun, sayfa yapısını hemen hemen iki kez tanımlamam gerekecek olmasıdır. Daha karmaşık bir sayfa düşünün, örneğin birçok farklı bileşen içeren bir blog sayfası (gerçek blog gönderisi, yorumlar, ilgili gönderiler, en yeni gönderiler, twitter akışı ...). Sayfa yapısını React bileşenlerini kullanarak tasarlamam ve daha sonra başka bir yerde bu mevcut sayfa için gerekli her mağazayı doldurma sürecini tanımlamam gerekirdi. Bu bana iyi bir çözüm gibi görünmüyor. Ne yazık ki çoğu izomorfik öğretici bu şekilde tasarlanmıştır (örneğin bu harika akı öğreticisi ).

  2. React-async . Bu yaklaşım mükemmel. Her bileşendeki özel bir işlevde durumun nasıl başlatılacağını (eşzamanlı veya eşzamansız olarak fark etmez) ve bu işlevler hiyerarşi HTML'ye dönüştürülürken adlandırılır. Durum tamamen başlatılana kadar bir bileşen işlenmeyecek şekilde çalışır. Sorun, Fibers gerektirmesidirbu, anladığım kadarıyla standart JavaScript davranışını değiştiren bir Node.js uzantısı. Sonucu gerçekten beğenmeme rağmen hala bir çözüm bulmak yerine oyunun kurallarını değiştirdik gibi geliyor bana. Ve bence React.js'nin bu temel özelliğini kullanmak için bunu yapmaya zorlanmamalıyız. Ayrıca bu çözümün genel desteğinden de emin değilim. Fiber'i standart Node.js web barındırmada kullanmak mümkün mü?

  3. Biraz kendi kendime düşünüyordum. Gerçekleştirme ayrıntılarını gerçekten düşünmedim, ancak genel fikir, bileşenleri React-async'e benzer şekilde genişleteceğim ve ardından tekrar tekrar React.renderComponentToString () 'i kök bileşenine çağıracağım. Her geçiş sırasında, genişleyen geri aramaları toplar ve ardından mağazaları doldurmak için geçişin sonunda arardım. Mevcut bileşen hiyerarşisinin gerektirdiği tüm mağazalar doldurulana kadar bu adımı tekrarlardım. Çözülmesi gereken birçok şey var ve özellikle performans konusunda emin değilim.

Bir şey mi kaçırdım? Başka bir yaklaşım / çözüm var mı? Şu anda react-async / fiberler yoluna gitmeyi düşünüyorum ama ikinci noktada açıklandığı gibi bundan tam olarak emin değilim.

GitHub ile ilgili tartışma . Görünüşe göre resmi bir yaklaşım ya da çözüm bile yok. Belki asıl soru, React bileşenlerinin nasıl kullanılmasının amaçlandığıdır. Basit görünüm katmanı gibi (hemen hemen benim önerim bir numaralı) veya gerçek bağımsız ve bağımsız bileşenler gibi mi?


Sırf bir şeyler elde etmek için: asenkron çağrılar sunucu tarafında da mı olur? Görünümü bazı kısımları boş bırakarak oluşturmanın ve eşzamansız yanıtın sonuçları geldikçe doldurmanın aksine bu durumda faydaları anlamıyorum. Muhtemelen bir şey eksik, üzgünüm!
phtrivier

JavaScript'te en son gönderileri almak için veritabanına yapılan en basit sorgunun bile eşzamansız olduğunu unutmamalısınız. Dolayısıyla, bir görünüm oluşturuyorsanız, veriler veritabanından alınana kadar beklemeniz gerekir. Ve sunucu tarafında oluşturmanın bariz faydaları vardır: Örneğin SEO. Ayrıca sayfanın titremesini engeller. Aslında sunucu tarafı oluşturma, çoğu web sitesinin hala kullandığı standart yaklaşımdır.
tobik

Elbette, ancak tüm sayfayı oluşturmaya mı çalışıyorsunuz (tüm eşzamansız db sorguları yanıt verdikten sonra)? Bu durumda, onu saf bir şekilde 1 olarak ayırırdım / tüm verileri eşzamansız olarak alırdım 2 / tamamlandığında, "aptal" bir React View'a geçirir ve isteğe yanıt verirdim. Ya da hem sunucu tarafı oluşturmayı, hem de istemci tarafında aynı kodla mı yapmaya çalışıyorsunuz (ve eşzamansız kodun react görünümüne yakın olması mı gerekiyor?) Üzgünüm, kulağa aptalca geliyorsa, anladığımdan emin değilim ne yapıyorsun.
phtrivier

Sorun değil, belki başkalarının da anlamakta güçlükleri vardır :) Az önce anlattığınız şey ikinci çözümdür. Ama örneğin sorudan yorum yapmak için bileşeni ele alalım. Ortak istemci tarafı uygulamasında, bu bileşendeki her şeyi yapabilirim (yorum yükleme / ekleme). Bileşen dış dünyadan ayrılacak ve dış dünyanın bu bileşeni önemsemesine gerek kalmayacaktı. Tamamen bağımsız ve bağımsız olacaktır. Ancak bir kez sunucu tarafı oluşturmayı tanıtmak istediğimde, eşzamansız şeyleri dışarıda halletmem gerekiyor. Ve bu tüm prensibi yıkıyor.
tobik

Açıkçası, fiber kullanımını savunmuyorum, sadece tüm asenkron çağrılarını yapıyorum ve hepsi bittikten sonra (söz veya her neyse kullanarak) bileşeni sunucu tarafında işle . (Bileşenler bilemeyiz tepki Yani hiç sadece bir görüş olduğunu, Şimdi. Asenkron şeylere), ama aslında böyle tamamen bileşenleri tepki sunucu haberleşme ile ilgili bir şey kaldırma fikri (ki gerçekten sadece burada görünümünü oluşturmak için vardır Bence tepkinin arkasındaki felsefe bu, yaptığınız şeyin neden biraz karmaşık olduğunu açıklayabilir. Her neyse, iyi şanslar :)
phtrivier

Yanıtlar:


15

Eğer kullanırsanız tepki-yönlendirici , sadece bir tanımlayabilirsiniz willTransitionTobir iletilir bileşenlerindeki yöntemleri, TransitionArayabileceğin nesneyi .waitüzerinde.

RenderToString'in eşzamanlı olup olmadığı önemli değildir, çünkü Router.runtüm .waited vaatler çözülene kadar geri çağırma çağrılmayacaktır , bu nedenle renderToStringara yazılım çağrıldığında depoları doldurmuş olabilirsiniz. Mağazalar tekil olsa bile, verilerini eşzamanlı işleme çağrısından önce geçici olarak tam zamanında ayarlayabilirsiniz ve bileşen görecek.

Ara yazılım örneği:

var Router = require('react-router');
var React = require("react");
var url = require("fast-url-parser");

module.exports = function(routes) {
    return function(req, res, next) {
        var path = url.parse(req.url).pathname;
        if (/^\/?api/i.test(path)) {
            return next();
        }
        Router.run(routes, path, function(Handler, state) {
            var markup = React.renderToString(<Handler routerState={state} />);
            var locals = {markup: markup};
            res.render("layouts/main", locals);
        });
    };
};

routes(Yolları hiyerarşisini açıklar) nesnesi, istemci ve sunucu ile aynen paylaşılır


Teşekkürler. Mesele şu ki, bildiğim kadarıyla sadece yol bileşenleri bu willTransitionToyöntemi destekliyor . Bu, soruda anlattığım gibi tamamen bağımsız yeniden kullanılabilir bileşenler yazmanın hala mümkün olmadığı anlamına geliyor. Ancak Fibers ile devam etmeye istekli değilsek, bu muhtemelen sunucu tarafı oluşturmayı gerçekleştirmenin en iyi ve en tepkisel yoludur.
tobik

Bu ilginç. WillTransitionTo yönteminin bir uygulaması, eşzamansız verilerin yüklenmesi gibi nasıl görünür?
Hyra

transitionNesneyi bir parametre olarak alacaksınız , böylece basitçe çağıracaksınız transition.wait(yourPromise). Bu, elbette, vaatleri desteklemek için API'nizi uygulamanız gerektiği anlamına gelir. Bu yaklaşımın diğer bir dezavantajı, istemci tarafında bir "yükleme göstergesi" uygulamanın basit bir yolunun olmamasıdır. Tüm sözler çözülene kadar geçiş yol işleyici bileşenine geçmeyecektir.
tobik

Ama aslında "tam zamanında" yaklaşımından emin değilim. Birden çok iç içe geçmiş yol işleyicisi bir url ile eşleşebilir, bu da birden çok sözün çözülmesi gerektiği anlamına gelir. Hepsinin aynı anda biteceğinin garantisi yok. Mağazalar tekil ise, çatışmalara neden olabilir. @Esailija cevabınızı biraz açıklar mısınız?
tobik

.waitedBir geçiş için tüm vaatleri toplayan otomatik tesisatım var . Hepsi yerine getirildikten sonra .rungeri arama çağrılır. .render()Verilen tüm verileri bir arada toplamadan ve singelton mağaza durumlarını ayarlamadan hemen önce , render çağrısından sonraki satırda singleton mağazaları yeniden başlatıyorum. Oldukça karmaşık ama hepsi otomatik olarak gerçekleşir ve bileşen ve mağaza uygulama kodu neredeyse aynı kalır.
Esailija

0

Bunun muhtemelen tam olarak istediğiniz şey olmadığını biliyorum ve mantıklı gelmeyebilir, ancak her ikisini de işlemek için bileşeni hafifçe değiştirdiğimi hatırlıyorum:

  • sunucu tarafında işleme, tüm başlangıç ​​durumu zaten alınmış, gerekirse zaman uyumsuz olarak)
  • istemci tarafında, gerekirse ajax ile işleme

Yani şöyle bir şey:

/** @jsx React.DOM */

var UserGist = React.createClass({
  getInitialState: function() {

    if (this.props.serverSide) {
       return this.props.initialState;
    } else {
      return {
        username: '',
        lastGistUrl: ''
      };
    }

  },

  componentDidMount: function() {
    if (!this.props.serverSide) {

     $.get(this.props.source, function(result) {
      var lastGist = result[0];
      if (this.isMounted()) {
        this.setState({
          username: lastGist.owner.login,
          lastGistUrl: lastGist.html_url
        });
      }
    }.bind(this));

    }

  },

  render: function() {
    return (
      <div>
        {this.state.username}'s last gist is
        <a href={this.state.lastGistUrl}>here</a>.
      </div>
    );
  }
});

// On the client side
React.renderComponent(
  <UserGist source="https://api.github.com/users/octocat/gists" />,
  mountNode
);

// On the server side
getTheInitialState().then(function (initialState) {

    var renderingOptions = {
        initialState : initialState;
        serverSide : true;
    };
    var str = Xxx.renderComponentAsString( ... renderingOptions ...)  

});

Üzgünüm elimde tam kod yok, bu yüzden bu kutunun dışında işe yaramayabilir, ancak tartışmak için gönderiyorum.

Yine, fikir dilsiz görünüm olarak bileşenin en tedavi ve mümkün olduğunca veriler alınırken başa olan out bileşeninin.


1
Teşekkür ederim. Fikir anladım, ama gerçekten istediğim bu değil. Diyelim ki bbc.com gibi React kullanarak daha karmaşık bir web sitesi oluşturmak istiyorum . Sayfaya baktığımda her yerde "bileşenleri" görebiliyorum. Bir bölüm (spor, iş ...) tipik bir bileşendir. Nasıl uygularsın? Tüm verileri nereye önceden getirirsiniz? Böylesine karmaşık bir site tasarlamak için, bileşenler (prensip olarak, küçük MVC konteynerleri gibi) çok iyidir (belki de tek yol). Bileşen yaklaşımı, birçok tipik sunucu tarafı çerçevesi için yaygındır. Soru şu: Bunun için React'i kullanabilir miyim?
tobik

Verileri sunucu tarafına önceden getireceksiniz (bu durumda muhtemelen yapıldığı gibi, "geleneksel" bir sunucu tarafı şablon sistemine geçirmeden önce); sırf verinin gösterimi modüler olmanın faydasını gördüğü için, bu, verinin hesaplanmasının zorunlu olarak aynı yapıyı takip etmesi gerektiği anlamına mı geliyor? Burada biraz şeytanın avukatlığını oynuyorum, om kontrolünde de seninle aynı sıkıntıyı yaşadım. Ve eminim ki birinin bu konuda daha fazla kavrayışa sahip olmasını umuyorum, o zaman benden - kablonun herhangi bir tarafında sorunsuz bir şekilde bir şeyler bestelemek çok yardımcı olur.
phtrivier

1
Kodun neresinden kastettiğim. Denetleyicide mi? Öyleyse, bbc'nin ana sayfasını işleyen denetleyici yöntemi, her bölüm bir için düzinelerce benzer sorgu içerecek mi? Bu cehenneme giden bir yol. Yani evet, bunu o hesaplama yanı modüler olması gerektiğini düşünüyorum. Her şey tek bir bileşende, tek bir MVC konteynerinde paketlenmiştir. Standart sunucu tarafı uygulamaları bu şekilde geliştiriyorum ve bu yaklaşımın iyi olduğundan oldukça eminim. Ve React.js hakkında bu kadar heyecanlanmamın nedeni, harika izomorfik uygulamalar oluşturmak için bu yaklaşımı hem istemci hem de sunucu tarafında kullanmak için büyük bir potansiyel olması.
tobik

1
Herhangi bir sitede (büyük / küçük), yalnızca başlangıç ​​durumundaki geçerli sayfayı sunucu tarafında görüntülemeniz (SSR) gerekir; her sayfa için başlatma durumuna ihtiyacınız yoktur. Sunucu başlatma durumunu alır, işler ve istemciye iletir <script type=application/json>{initState}</script>; bu şekilde veriler HTML'de olacaktır. İstemcide oluşturmayı çağırarak UI olaylarını sayfaya yeniden nemlendirin / bağlayın. Sonraki sayfalar, müşterinin js kodu tarafından oluşturulur (gerektiğinde veri alınır) ve müşteri tarafından oluşturulur. Bu şekilde herhangi bir yenileme, yeni SSR sayfalarını yükleyecek ve bir sayfaya tıklamak CSR olacaktır. = izomorfik ve SEO dostu
Federico

0

Bugün bununla gerçekten uğraştım ve bu, sorununuz için bir cevap olmasa da, bu yaklaşımı kullandım. Yönlendirme için React Router yerine Express kullanmak istedim ve düğümde iş parçacığı desteğine ihtiyacım olmadığı için Fibers kullanmak istemedim.

Bu yüzden, yükleme sırasında akı deposuna işlenmesi gereken ilk veriler için bir AJAX isteği gerçekleştireceğim ve ilk verileri depoya aktaracağım.

Bu örnek için Fluxxor kullanıyordum.

Yani ekspres rotamda, bu durumda bir /productsrota:

var request = require('superagent');
var url = 'http://myendpoint/api/product?category=FI';

request
  .get(url)
  .end(function(err, response){
    if (response.ok) {    
      render(res, response.body);        
    } else {
      render(res, 'error getting initial product data');
    }
 }.bind(this));

Daha sonra verileri depoya ileten ilklendirme render yöntemim.

var render = function (res, products) {
  var stores = { 
    productStore: new productStore({category: category, products: products }),
    categoryStore: new categoryStore()
  };

  var actions = { 
    productActions: productActions,
    categoryActions: categoryActions
  };

  var flux = new Fluxxor.Flux(stores, actions);

  var App = React.createClass({
    render: function() {
      return (
          <Product flux={flux} />
      );
    }
  });

  var ProductApp = React.createFactory(App);
  var html = React.renderToString(ProductApp());
  // using ejs for templating here, could use something else
  res.render('product-view.ejs', { app: html });

0

Bu sorunun bir yıl önce sorulduğunu biliyorum ama aynı sorunu yaşadık ve bunu render edilecek bileşenlerden türetilen iç içe geçmiş vaatlerle çözüyoruz. Sonunda, uygulama için tüm verilere sahip olduk ve onu yolumuza gönderdik.

Örneğin:

var App = React.createClass({

    /**
     *
     */
    statics: {
        /**
         *
         * @returns {*}
         */
        getData: function (t, user) {

            return Q.all([

                Feed.getData(t),

                Header.getData(user),

                Footer.getData()

            ]).spread(
                /**
                 *
                 * @param feedData
                 * @param headerData
                 * @param footerData
                 */
                function (feedData, headerData, footerData) {

                    return {
                        header: headerData,
                        feed: feedData,
                        footer: footerData
                    }

                });

        }
    },

    /**
     *
     * @returns {XML}
     */
    render: function () {

        return (
            <label>
                <Header data={this.props.header} />
                <Feed data={this.props.feed}/>
                <Footer data={this.props.footer} />
            </label>
        );

    }

});

ve yönlendiricide

var AppFactory = React.createFactory(App);

App.getData(t, user).then(
    /**
     *
     * @param data
     */
    function (data) {

        var app = React.renderToString(
            AppFactory(data)
        );       

        res.render(
            'layout',
            {
                body: app,
                someData: JSON.stringify(data)                
            }
        );

    }
).fail(
    /**
     *
     * @param error
     */
    function (error) {
        next(error);
    }
);

0

Sizinle sunucu tarafı oluşturma yaklaşımımı kullanarak Fluxbiraz basitleştirmek istiyorum, örneğin:

  1. componentMağazadaki ilk verilere sahip olduğumuzu varsayalım :

    class MyComponent extends Component {
      constructor(props) {
        super(props);
        this.state = {
          data: myStore.getData()
        };
      }
    }
  2. Sınıf, başlangıç ​​durumu için önceden yüklenmiş bazı veriler gerektiriyorsa, aşağıdakiler için Yükleyici oluşturalım MyComponent:

     class MyComponentLoader {
        constructor() {
            myStore.addChangeListener(this.onFetch);
        }
        load() {
            return new Promise((resolve, reject) => {
                this.resolve = resolve;
                myActions.getInitialData(); 
            });
        }
        onFetch = () => this.resolve(data);
    }
  3. Mağaza:

    class MyStore extends StoreBase {
        constructor() {
            switch(action => {
                case 'GET_INITIAL_DATA':
                this.yourFetchFunction()
                    .then(response => {
                        this.data = response;
                        this.emitChange();
                     });
                 break;
        }
        getData = () => this.data;
    }
  4. Şimdi sadece yönlendiriciye veri yükleyin:

    on('/my-route', async () => {
        await new MyComponentLoader().load();
        return <MyComponent/>;
    });

0

tıpkı kısa bir toplama gibi -> GraphQL bunu yığınız için tamamen çözecektir ...

  • GraphQL ekle
  • apollo ve react-apollo kullanın
  • oluşturmaya başlamadan önce "getDataFromTree" kullanın

-> getDataFromTree, uygulamanızda ilgili tüm sorguları otomatik olarak bulur ve yürütür, apollo önbelleğinizi sunucuda pouplat eder ve böylece tam çalışan SSR'yi etkinleştirir .. BÄM

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.