(通用 React + redux + react-router)如何避免在初始浏览器加载时重新获取路由数据?
Posted
技术标签:
【中文标题】(通用 React + redux + react-router)如何避免在初始浏览器加载时重新获取路由数据?【英文标题】:(Universal React + redux + react-router) How to avoid re-fetching route data on initial browser load? 【发布时间】:2016-07-13 20:36:42 【问题描述】:我在我的 Route 组件上使用 static fetchData
方法...
const mapStateToProps = (state) => (
posts: state.posts
)
@connect(mapStateToProps)
class Blog extends Component
static fetchData (dispatch)
return dispatch(fetchPosts())
render ()
return (
<PostsList posts=this.props.posts />
)
...并在服务器端初始渲染之前收集所有承诺...
match( routes, location , (error, redirectLocation, renderProps) =>
const promises = renderProps.components
.filter((component) => component.fetchData)
.map((component) => component.fetchData(store.dispatch))
Promise.all(promises).then(() =>
res.status(200).send(renderView())
)
)
它工作正常,服务器等到我的所有承诺都得到解决后再渲染应用程序。
现在,在我的客户端脚本上,我正在执行与服务器上类似的操作...
...
function resolveRoute (props)
props.components
.filter((component) => component.fetchData)
.map((component) => component.fetchData(store.dispatch))
return <RouterContext ...props />
render((
<Provider store=store>
<Router
history=browserHistory
routes=routes
render=resolveRoute />
</Provider>
), document.querySelector('#app'))
而且效果很好。但是,正如您可能推断的那样,在初始页面呈现时,静态 fetchData
被调用了两次(一次在服务器上,一次在客户端上),我不希望这样。
关于如何解决这个问题有什么建议吗?推荐?
【问题讨论】:
【参考方案1】:我是用手机输入的,所以对于格式缺失表示歉意。
对于我的项目,我正在做与您类似的事情;我有一个静态的 fetchData 方法,我遍历来自 renderProps 的组件,然后调用静态方法并等待 Promise 解决。
然后,我从我的 redux 存储中调用 get state,对其进行字符串化,并将其传递给服务器上的渲染函数,以便它可以在客户端渲染出初始状态对象。
我只是从客户端获取初始状态变量并将其传递给我的 redux 存储。然后 Redux 将处理让您的客户端存储与服务器上的存储相匹配。从那里,您只需将商店传递给提供商并照常继续。您根本不需要在客户端上调用静态方法。
作为我所说的示例,您可以查看我的 github 项目,因为代码会自行解释。 https://github.com/mr-antivirus/riur
希望有所帮助!
[编辑] 这是代码!
Client.js
'use strict'
import React from 'react';
import render from 'react-dom';
import Provider from 'react-redux';
import Router, browserHistory from 'react-router';
import createStore from '../shared/store/createStore';
import routes from '../shared/routes';
const store = createStore(window.__app_data);
const history = browserHistory;
render (
<Provider store=store>
<Router history=history routes=routes />
</Provider>,
document.getElementById('content')
)
Server.js
app.use((req, res, next) =>
match( routes, location:req.url , (err, redirectLocation, renderProps) =>
if (err)
return res.status(500).send(err);
if (redirectLocation)
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
if (!renderProps)
return next();
// Create the redux store.
const store = createStore();
// Retrieve the promises from React Router components that have a fetchData method.
// We use this data to populate our store for server side rendering.
const fetchedData = renderProps.components
.filter(component => component.fetchData)
.map(component => component.fetchData(store, renderProps.params));
// Wait until ALL promises are successful before rendering.
Promise.all(fetchedData)
.then(() =>
const asset =
javascript:
main: '/js/bundle.js'
;
const appContent = renderToString(
<Provider store=store>
<RouterContext ...renderProps />
</Provider>
)
const isProd = process.env.NODE_ENV !== 'production' ? false : true;
res.send('<!doctype html>' + renderToStaticMarkup(<Html assets=asset content=appContent store=store isProd=isProd />));
)
.catch((err) =>
// TODO: Perform better error logging.
console.log(err);
);
);
);
RedditContainer.js
class Reddit extends Component
// Used by the server, ONLY, to fetch data
static fetchData(store)
const selectedSubreddit = store.getState();
return store.dispatch(fetchPosts(selectedSubreddit));
// This will be called once on the client
componentDidMount()
const dispatch, selectedSubreddit = this.props;
dispatch(fetchPostsIfNeeded(selectedSubreddit));
... Other methods
;
HTML.js
'use strict';
import React, Component, PropTypes from 'react';
import ReactDom from 'react-dom';
import Helmet from 'react-helmet';
import serialize from 'serialize-javascript';
export default class Layout extends Component
static propTypes =
assets: PropTypes.object,
content: PropTypes.string,
store: PropTypes.object,
isProd: PropTypes.bool
render ()
const assets, content, store, isProd = this.props;
const head = Helmet.rewind();
const attrs = head.htmlAttributes.toComponent();
return (
<html ...attrs>
<head>
head.base.toComponent()
head.title.toComponent()
head.meta.toComponent()
head.link.toComponent()
head.script.toComponent()
<link rel='shortcut icon' href='/favicon.ico' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
</head>
<body>
<div id='content' dangerouslySetInnerHTML=__html: content />
<script dangerouslySetInnerHTML=__html: `window.__app_data=$serialize(store.getState()); window.__isProduction=$isProd` charSet='utf-8' />
<script src=assets.javascript.main charSet='utf-8' />
</body>
</html>
);
;
重申...
-
在客户端,获取状态变量并将其传递给您的商店。
在服务器上,循环访问调用 fetchData 并传递存储的组件。等待 promise 被解析,然后渲染。
在 HTML.js(您的 renderView 函数)中,序列化您的 Redux 存储并将输出呈现到客户端的 javascript 变量中。
在您的 React 组件中,为 仅 服务器创建一个静态 fetchData 方法来调用。调度您需要的操作。
【讨论】:
谢谢!我遇到了与 OP 相同的问题,但第 4 步是啊哈,只有在客户端挂载时才需要获取 很高兴它有帮助:-)【参考方案2】:更好的办法是在服务器端对存储状态进行脱水,并在客户端将初始存储状态与脱水状态混合。
来自 Redux 文档:
这使得创建通用应用程序变得容易,因为来自服务器的状态可以被序列化并混合到客户端中,而无需额外的编码工作。
http://redux.js.org/docs/introduction/ThreePrinciples.html
【讨论】:
【参考方案3】:您可以使用来自 fbjs 模块的 CanUseDOM。
import canUseDOM from 'fbjs/lib/ExecutionEnvironment';
//only render on the server because it doesn't have DOM
if(!canUseDOM)
static fetch here
【讨论】:
但是,使用这种方式我不会失去服务器端的承诺解析吗?以上是关于(通用 React + redux + react-router)如何避免在初始浏览器加载时重新获取路由数据?的主要内容,如果未能解决你的问题,请参考以下文章
ReactJS React+Redux+Router+antDesign通用高效率开发模板,夜间模式为例