提问者:小点点

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


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

假设我有一个通用的评论组件,我可以把它放在页面的任何地方。它只有一个属性,某种标识符(例如,注释放在下面的文章的id),其他的事情都由组件自己处理(加载、添加、管理注释)。

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

问题是什么是最好的方式来填充我的商店。在过去的几天里,我在谷歌上搜索了很多,我遇到了很少的策略,考虑到React的这个特性被“推广”了多少,这些策略似乎都不是很好。

>

  • 在我看来,最简单的方法是在实际呈现开始之前填充我的所有存储。这意味着组件层次结构之外的某个地方(例如,与我的路由器挂钩)。这种方法的问题是,我几乎必须定义两次页面结构。考虑一个更复杂的页面,例如一个包含许多不同组件的博客页面(实际的博客文章、评论、相关文章、最新文章、twitter流……)。我必须使用React组件设计页面结构,然后在其他地方定义为当前页面填充每个所需存储的过程。我觉得这不是个好办法。不幸的是,大多数同构教程都是这样设计的(例如,这个很棒的Flux教程)。

    反应-异步。这种方法是完美的。它允许我在每个组件的一个特殊函数中定义如何初始化状态(不管是同步还是异步),并且在将层次结构呈现到HTML时调用这些函数。它的工作方式是在状态完全初始化后才呈现组件。问题是它需要纤维,据我所知,纤维是一个node.js扩展,它改变了标准JavaScript行为。虽然我真的很喜欢这个结果,但在我看来,我们并没有找到解决办法,而是改变了游戏规则。我认为我们不应该被迫这样做来使用react.js的核心特性。我也不确定这种解决方案的普遍支持度。是否可以在标准Node.js web托管上使用光纤?

    我一个人在想。我还没有考虑过具体的实现细节,但一般的想法是,我将以类似的方式扩展组件,使其成为React-async,然后在根组件上重复调用React.RenderComponentToString()。在每个传递过程中,我将收集扩展回调,然后在传递的and处调用它们来填充存储。我将重复此步骤,直到填充当前组件层次结构所需的所有存储。有很多事情要解决,我特别不确定的表现。

    我错过什么了吗?是否有其他方法/解决方案?现在,我正在考虑使用React-Async/Fibers方式,但我不完全确定,正如第二点所解释的那样。

    关于GitHub的相关讨论。显然,没有官方的办法,甚至没有解决办法。也许真正的问题是React组件打算如何使用。像简单的视图层(几乎是我的第一个建议)还是像真正独立的组件?


  • 共2个答案

    匿名用户

    如果使用react-router,只需在组件中定义一个WillTransitionTo方法,该方法会传递一个Transition对象,您可以在该对象上调用.Wait

    renderToString是否同步并不重要,因为在所有.waitED承诺都得到解决之前,不会调用对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);
            });
        };
    };
    

    code>路由对象(描述路由层次结构)与客户端和服务器逐字共享

    匿名用户

    我知道这可能不完全是您想要的,也可能没有意义,但我记得稍微修改一下组件以处理这两种情况:

    • 在服务器端呈现,所有初始状态都已检索,必要时异步)
    • 在客户端呈现,需要时使用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 ...)  
    
    });
    

    很抱歉,我手边没有确切的代码,所以这可能不是开箱即用的,但我是为了讨论的兴趣才发帖的。

    同样,其想法是将组件的大部分视为哑视图,并处理从组件中尽可能多地提取数据。

    相关问题