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

tob*_*bik 111 flux node.js reactjs reactjs-flux

React.js的一大优势应该是服务器端渲染.问题是密钥功能React.renderComponentToString()是同步的,这使得在服务器上呈现组件层次结构时无法加载任何异步数据.

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

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

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

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

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

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

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

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

Esa*_*ija 15

如果使用react-router,则只需willTransitionTo在组件中定义一个方法,然后传递一个Transition可以调用的对象.wait.

如果renderToString是同步的并不重要,因为在解决Router.run所有.waited promise 之前不会调用回调,所以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);
        });
    };
};
Run Code Online (Sandbox Code Playgroud)

routes对象(其描述了路由层次)与客户端和服务器共享逐字


pht*_*ier 0

我知道这可能不完全是您想要的,而且可能没有意义,但我记得稍微修改组件来处理这两个问题:

\n\n
    \n
  • 在服务器端渲染,已检索所有初始状态,如果需要,则异步)
  • \n
  • 在客户端渲染,如果需要的话可以使用ajax
  • \n
\n\n

所以像这样:

\n\n
/** @jsx React.DOM */\n\nvar UserGist = React.createClass({\n  getInitialState: function() {\n\n    if (this.props.serverSide) {\n       return this.props.initialState;\n    } else\xc2\xa0{\n      return {\n        username: \'\',\n        lastGistUrl: \'\'\n      };\n    }\n\n  },\n\n  componentDidMount: function() {\n    if (!this.props.serverSide) {\n\n     $.get(this.props.source, function(result) {\n      var lastGist = result[0];\n      if (this.isMounted()) {\n        this.setState({\n          username: lastGist.owner.login,\n          lastGistUrl: lastGist.html_url\n        });\n      }\n    }.bind(this));\n\n    }\n\n  },\n\n  render: function() {\n    return (\n      <div>\n        {this.state.username}\'s last gist is\n        <a href={this.state.lastGistUrl}>here</a>.\n      </div>\n    );\n  }\n});\n\n// On the client side\nReact.renderComponent(\n  <UserGist source="https://api.github.com/users/octocat/gists" />,\n  mountNode\n);\n\n// On the server side\ngetTheInitialState().then(function (initialState) {\n\n    var renderingOptions = {\n        initialState : initialState;\n        serverSide : true;\n    };\n    var str = Xxx.renderComponentAsString( ... renderingOptions ...)  \n\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

很抱歉,我手头没有确切的代码,因此这可能无法开箱即用,但我发布此信息是为了讨论。

\n\n

同样,这个想法是将大部分组件视为哑视图,并尽可能多地从组件中获取数据。

\n