如何在逻辑上结合 react-router 和 redux 进行客户端和服务器端渲染
How to logically combine react-router and redux for client- and server-side rendering
我希望我的基于 React 的 SPA 在服务器端呈现(现在不是)。因此我想将 React 与 react-router, redux and some build layer like isomorphic starterkit.
结合起来
hapi universal redux 将所有内容连接在一起,但我正在为如何组织流程而苦苦挣扎。我的数据来自 REST API 的多个端点。不同的组件有不同的数据需求,应该在客户端及时加载数据。相反,在服务器上,必须获取特定路由(组件集)的所有数据,并将必要的组件呈现为字符串。
在我的第一种方法中,我使用 redux 的中间件来创建异步操作,它加载数据,return 一个承诺,并在承诺解决时触发一个 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
并将该状态传递给 store
生成 redux。但是我找不到正确的方法来做到这一点。所以对我来说出现了以下问题:
- 是不是我的方法不对,因为我还不明白/不知道什么?
- 在 redux 的
store
中保留从 REST API 加载的数据是否正确?
- 在 redux
store
中保留 state
的组件而其他人自己管理他们的 state
是不是有点尴尬?
interaction -> URL change -> data fetching -> store update -> re-render
的想法是错误的吗?
我愿意接受各种建议。
我今天确实设置了完全相同的东西。我们已经拥有的是一个 react-router 和 redux。我们模块化了一些模块以将东西注入其中 - 和 viola - 它起作用了。我使用 https://github.com/erikras/react-redux-universal-hot-example 作为参考。
零件:
1。 router.js
我们 return function (location, history, store)
使用 promises 设置路由器。 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
express rendering a jade template 如下
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 store 中的所有状态都是好东西。这样你就有了一个单一的事实来源。
- 我们仍在研究现在应该去哪里。目前我们在服务器上请求
componentDidMount
上的数据,它应该已经存在了。
我希望我的基于 React 的 SPA 在服务器端呈现(现在不是)。因此我想将 React 与 react-router, redux and some build layer like isomorphic starterkit.
结合起来hapi universal redux 将所有内容连接在一起,但我正在为如何组织流程而苦苦挣扎。我的数据来自 REST API 的多个端点。不同的组件有不同的数据需求,应该在客户端及时加载数据。相反,在服务器上,必须获取特定路由(组件集)的所有数据,并将必要的组件呈现为字符串。
在我的第一种方法中,我使用 redux 的中间件来创建异步操作,它加载数据,return 一个承诺,并在承诺解决时触发一个 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
并将该状态传递给 store
生成 redux。但是我找不到正确的方法来做到这一点。所以对我来说出现了以下问题:
- 是不是我的方法不对,因为我还不明白/不知道什么?
- 在 redux 的
store
中保留从 REST API 加载的数据是否正确? - 在 redux
store
中保留state
的组件而其他人自己管理他们的state
是不是有点尴尬? interaction -> URL change -> data fetching -> store update -> re-render
的想法是错误的吗?
我愿意接受各种建议。
我今天确实设置了完全相同的东西。我们已经拥有的是一个 react-router 和 redux。我们模块化了一些模块以将东西注入其中 - 和 viola - 它起作用了。我使用 https://github.com/erikras/react-redux-universal-hot-example 作为参考。
零件:
1。 router.js
我们 return function (location, history, store)
使用 promises 设置路由器。 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
express rendering a jade template 如下
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 store 中的所有状态都是好东西。这样你就有了一个单一的事实来源。
- 我们仍在研究现在应该去哪里。目前我们在服务器上请求
componentDidMount
上的数据,它应该已经存在了。