首页 文章

异步初始化的React.js组件的服务器端呈现策略

提问于
浏览
110

React.js 的最大优势之一应该是 server-side rendering . 问题是关键函数 React.renderComponentToString() 是同步的,这使得在服务器上呈现组件层次结构时无法加载任何异步数据 .

假设我有一个用于评论的通用组件,我可以在页面的任何位置删除它 . 它只有一个属性,某种标识符(例如文章的id,其下方放置了注释),其他所有内容都由组件本身处理(加载,添加,管理注释) .

我非常喜欢 Flux 架构,因为它使很多事情变得更容易,而且它的商店非常适合在服务器和客户端之间共享状态 . 一旦我的包含注释的商店被初始化,我就可以将其序列化并将其从服务器发送到客户端,以便轻松恢复 .

问题是填充我的商店的最佳方式是什么 . 在过去的几天里,我一直在谷歌搜索,我遇到了一些策略,考虑到React的这个功能被“提升”了多少,这些策略看起来都不是很好 .

  • 在我看来,最简单的方法是在实际渲染开始之前填充我的所有商店 . 这意味着在组件层次结构之外的某个地方(例如,挂钩到我的路由器) . 这种方法的问题是我必须两次定义页面结构 . 考虑一个更复杂的页面,例如一个包含许多不同组件的博客页面(实际的博客帖子,评论,相关帖子,最新帖子,推特流......) . 我必须使用React组件设计页面结构,然后在其他地方我必须定义为当前页面填充每个所需存储的过程 . 这似乎不是一个很好的解决方案 . 不幸的是,大多数同构教程都是这样设计的(例如,这个伟大的flux-tutorial) .

  • React-async . 这种方法很完美 . 它让我只需在每个组件的特殊函数中定义如何初始化状态(无论是同步还是异步),并在层次结构呈现为HTML时调用这些函数 . 它的工作方式是在状态完全初始化之前不会呈现组件 . 问题是它需要Fibers,据我所知,这是一个改变标准JavaScript行为的Node.js扩展 . 虽然我非常喜欢这个结果,但在我看来,我们并没有找到解决方案,而是改变了游戏规则 . 而且我认为我们也不应该对这个解决方案的普遍支持不确定 . 是否可以在标准Node.js虚拟主机上使用Fiber?

  • 我自己在思考一点 . 我没有真正考虑过实现细节,但一般的想法是我会以类似于React-async的方式扩展组件,然后我会在根组件上重复调用React.renderComponentToString() . 在每次传递过程中,我会收集扩展回调,然后在传递和传递中调用它们来填充商店 . 我将重复此步骤,直到填充当前组件层次结构所需的所有存储 . 有很多事情需要解决,我对性能特别不确定 .

我错过了什么?还有其他方法/解决方案吗?现在我正在考虑采用react-async / fiber方式,但我并不完全确定它,如第二点所述 .

Related discussion on GitHub . 显然,没有官方方法甚至解决方案 . 也许真正的问题是如何使用React组件 . 像简单的视图层(几乎是我的建议第一)或像真正的独立和独立组件?

6 回答

  • 0

    今天我真的搞砸了,虽然这不是你问题的答案,但我使用了这种方法 . 我想使用Express进行路由而不是React Router,我不想使用Fibers,因为我不需要节点中的线程支持 .

    所以我只是决定对于需要在加载时将其呈现给磁通存储的初始数据,我将执行AJAX请求并将初始数据传递到存储中

    我在这个例子中使用Fluxxor .

    所以在我的快速路线上,在这种情况下是 /products 路线:

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

    然后我初始化渲染方法,将数据传递给商店 .

    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

    我知道这可能不是你想要的,它可能没有意义,但我记得通过轻微修改组件来处理两者:

    在服务器端进行

    • 渲染,已经检索到所有初始状态,如果需要,可以异步进行

    • 在客户端呈现,如果需要,使用ajax

    所以类似于:

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

    对不起,我手边没有确切的代码,所以这可能不是开箱即用的,但我是为了讨论而发布的 .

    同样,这个想法是对待大部分的组件作为一个哑视图,并处理尽可能多的从组件中获取数据 .

  • 0

    我知道这个问题是在一年前问的,但是我们遇到了同样的问题,我们用嵌套的promises来解决它,这些promises是从将要渲染的组件派生出来的 . 最后,我们获得了应用程序的所有数据,并将其发送给了我们 .

    例如:

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

    并在路由器中

    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

    想与我分享我使用 Flux 进行服务器端渲染的方法,例如:

    • 假设我们有 component 来自商店的初始数据:
    class MyComponent extends Component {
      constructor(props) {
        super(props);
        this.state = {
          data: myStore.getData()
        };
      }
    }
    
    • 如果类需要一些初始状态的预加载数据,让我们为 MyComponent 创建Loader:
    class MyComponentLoader {
        constructor() {
            myStore.addChangeListener(this.onFetch);
        }
        load() {
            return new Promise((resolve, reject) => {
                this.resolve = resolve;
                myActions.getInitialData(); 
            });
        }
        onFetch = () => this.resolve(data);
    }
    
    • 商店:
    class MyStore extends StoreBase {
        constructor() {
            switch(action => {
                case 'GET_INITIAL_DATA':
                this.yourFetchFunction()
                    .then(response => {
                        this.data = response;
                        this.emitChange();
                     });
                 break;
        }
        getData = () => this.data;
    }
    
    • 现在只需在路由器中加载数据:
    on('/my-route', async () => {
        await new MyComponentLoader().load();
        return <MyComponent/>;
    });
    
  • 0

    就像一个简短的汇总 - > GraphQL将为你的堆栈解决这个问题...

    • 添加GraphQL

    • 使用apollo和react-apollo

    • 在开始渲染之前使用"getDataFromTree"

    • getDataFromTree将自动在您的应用程序中查找所有涉及的查询并执行它们,在服务器上放置您的apollo缓存,从而实现完全正常工作的SSR..BÄM

  • 14

    如果使用react-router,则可以在组件中定义 willTransitionTo 方法,这些方法将传递 Transition 对象,您可以在其上调用 .wait .

    如果renderToString是同步的并不重要,因为在解析所有 .wait ed promises之前不会调用 Router.run 的回调,因此在中间件中调用 renderToString 时,您可以填充存储 . 即使存储是单例,您也可以在同步呈现调用之前暂时及时设置其数据,组件将看到它 .

    中间件示例:

    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 对象(描述路由层次结构)与客户端和服务器逐字共享

相关问题