Strategies for server-side rendering of asynchronously initialized React.js components

前端 未结 6 1067
一向
一向 2020-12-12 09:51

One of the biggest advantages of React.js is supposed to be server-side rendering. The problem is that the key function React.renderC

相关标签:
6条回答
  • 2020-12-12 10:05

    If you use react-router, you can just define a willTransitionTo methods in components, which gets passed a Transition object that you can call .wait on.

    It doesn't matter if renderToString is synchronous because the callback to Router.run will not be called until all .waited promises are resolved, so by the time renderToString is called in the middleware you could have populated the stores. Even if the stores are singletons you can just set their data temporarily just-in-time before the synchronous rendering call and the component will see it.

    Example of middleware:

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

    The routes object (which describes the routes hierarchy) is shared verbatim with client and server

    0 讨论(0)
  • 2020-12-12 10:07

    I know this is probably not exactly what you want, and it might not make sense, but I remember getting by with slighly modifying the component to handle both :

    • rendering on the server side, with all the initial state already retrieved, asynchronously if needed)
    • rendering on the client side, with ajax if needed

    So something like :

    /** @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 ...)  
    
    });
    

    I'm sorry I don't have the exact code at hand, so this might not work out of the box, but I'm posting in the interest of discussion.

    Again, the idea is to treat most of the component as a dumb view, and deal with fetching data as much as possible out of the component.

    0 讨论(0)
  • 2020-12-12 10:07

    just as a short rollup -> GraphQL will solve this entierly for your stack...

    • add GraphQL
    • use apollo and react-apollo
    • use "getDataFromTree" before you start rendering

    -> getDataFromTree will automatically find all the involved queries in your app and execute them, pouplating your apollo cache on the server and thus, enabling fully working SSR.. BÄM

    0 讨论(0)
  • 2020-12-12 10:09

    I was really messed around with this today, and although this is not an answer to your problem, I have used this approach. I wanted to use Express for routing rather than React Router, and I didn't want to use Fibers as I didn't need threading support in node.

    So I just made a decision that for initial data which needs to be rendered to the flux store on load, I will perform an AJAX request and pass the initial data into the store

    I was using Fluxxor for this example.

    So on my express route, in this case a /products route:

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

    Then my initialize render method which passes the data to the store.

    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 讨论(0)
  • 2020-12-12 10:15

    Wanna share with you my approach of server side rendering using Flux, little be simplified for example:

    1. Let's say we have component with initial data from store:

      class MyComponent extends Component {
        constructor(props) {
          super(props);
          this.state = {
            data: myStore.getData()
          };
        }
      }
      
    2. If class require some preloaded data for initial state let's create Loader for MyComponent:

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

      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. Now just load data in router:

      on('/my-route', async () => {
          await new MyComponentLoader().load();
          return <MyComponent/>;
      });
      
    0 讨论(0)
  • 2020-12-12 10:22

    I know this question was asked a year ago but we had the same problem and we solve it with nested promises that were derived from the components that are going to be render. In the end we had the all data for the app and just sent it down the way.

    For example:

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

    and in the router

    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 讨论(0)
提交回复
热议问题