如何在逻辑上结合 react-router 和 redux 进行客户端和服务器端渲染

Posted

技术标签:

【中文标题】如何在逻辑上结合 react-router 和 redux 进行客户端和服务器端渲染【英文标题】:How to logically combine react-router and redux for client- and server-side rendering 【发布时间】:2015-11-23 06:21:36 【问题描述】:

我希望我的基于 React 的 SPA 在服务器端呈现(现在不是)。因此,我想将 React 与 react-router、redux 和一些构建层(如 isomorphic starterkit)结合起来。

有hapi universal redux 连接在一起,但我正在努力如何组织我的流程。我的数据来自 REST API 的多个端点。不同的组件有不同的数据需求,应该在客户端及时加载数据。相反,在服务器上,必须获取特定路由(组件集)的所有数据,并将必要的组件呈现为字符串。

在我的第一种方法中,我使用 redux 的中间件来创建异步操作,它加载数据、返回一个承诺,并在承诺解决时触发一个 SOME_DATA_ARRIVED 操作。 Reducers 然后更新我的商店,重新渲染组件,一切都很好。原则上,这是可行的。但后来我意识到,在路由开始发挥作用的那一刻,流程变得很尴尬。

列出许多数据记录的某些组件有多个链接来过滤记录。每个过滤后的数据集都应该可以通过它自己的 URL 访问,例如 /filter-by/:filter。所以我使用不同的<Link to=...> 组件来更改点击时的 URL 并触发路由器。然后路由器应该根据当前 URL 表示的状态更新存储,这反过来会导致相关组件的重新渲染。

这并不容易实现。我首先尝试componentWillUpdate 触发一个操作,该操作异步加载我的数据、填充存储并导致我的组件再次重​​新渲染循环。但这在服务器上不起作用,因为只支持 3 种生命周期方法。

所以我正在寻找正确的方式来组织它。从用户角度更改应用程序状态的用户交互应更新 URL。 IMO 这应该使路由器以某种方式加载必要的数据,更新存储,并开始协调过程。

所以interaction -> URL change -> data fetching -> store update -> re-render

这种方法也应该在服务器上工作,因为从请求的 URL 中应该能够确定要加载的数据,生成 initial state 并将该状态传递给 redux 的 store 生成。但我没有找到一种方法来正确地做到这一点。所以对我来说,出现了以下问题:

    我的方法是否错误,因为有些东西我还不理解/不知道? 是否可以在 redux 的 store 中保留从 REST API 加载的数据? 将state 保留在redux 中的组件store 和其他人自己管理state 是不是有点尴尬? interaction -> URL change -> data fetching -> store update -> re-render 的想法是否完全错误?

我愿意接受各种建议。

【问题讨论】:

【参考方案1】:

我今天确实设置了完全相同的东西。我们已经拥有的是 react-router 和 redux。我们模块化了一些模块以将东西注入其中——而且 viola——它可以工作。我使用https://github.com/erikras/react-redux-universal-hot-example 作为参考。

零件:

1。路由器.js

我们返回一个 function (location, history, store) 来使用 Promise 设置路由器。 routes 是包含所有组件的 react-router 的路由定义。

module.exports = function (location, history, store) 
    return new Bluebird((resolve, reject)  => 
        Router.run(routes, location, (Handler, state) => 
            const HandlerConnected = connect(_.identity)(Handler);
            const component = (
                <Provider store=store>
                    () => <HandlerConnected />
                </Provider>
            );
            resolve(component);
        ).catch(console.error.bind(console));
    );
;

2。 store.js

您只需将初始状态传递给createStore(reducer, initialState)。您只需在服务器和客户端上执行此操作。对于客户端,您应该通过脚本标签(即window.__initialstate__)使状态可用。 请参阅http://rackt.github.io/redux/docs/recipes/ServerRendering.html 了解更多信息。

3。在服务器上渲染

获取您的数据,使用该数据设置初始状态 (...data)。 createRouter = router.js 从上面。 res.render 正在快速渲染一个翡翠模板,内容如下

script.
    window.csvistate.__initialstate__=!initialState ? JSON.stringify(initialState) : 'null';
...
#react-start
    != html

var initialState =  ...data ;
var store = createStore(reducer, initialState);
createRouter(req.url, null, store).then(function (component) 
    var html = React.renderToString(component);
    res.render('community/neighbourhood',  html: html, initialState: initialState );
);

4。适配客户端

然后您的客户可以做基本相同的事情。 location 可能是来自 React-Router 的 HistoryLocation

const initialState = window.csvistate.__initialstate__;
const store = require('./store')(initialState);

router(location, null, store).then(component => 
    React.render(component, document.getElementsByClassName('jsx-community-bulletinboard')[0]);
);

回答您的问题:

    您的方法似乎是正确的。我们也这样做。甚至可以将 url 作为状态的一部分。 redux 存储中的所有状态都是一件好事。这样,您就有了一个单一的事实来源。 我们仍在研究现在应该去哪里。目前我们在服务器上请求componentDidMount 上的数据,它应该已经存在。

【讨论】:

以上是关于如何在逻辑上结合 react-router 和 redux 进行客户端和服务器端渲染的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们需要在 express.js 服务器上使用代理才能获得 webpack 热重载服务器功能与 react-routing 相结合

为啥我们需要在 express.js 服务器上使用代理才能获得与 react-routing 相结合的 webpack 热重载服务器功能

Antd Menu组件应该如何结合react-router Link组件?

结合Grep和For-Loop构造矩阵(R)

如何限制对 react-router 中路由的访问?

React-router学习笔记