(Universal React + redux + react-router)如何避免在初始浏览器加载时重新获取路由数据?

时间:2016-03-26 02:24:39

标签: reactjs react-router redux redux-thunk

我在路由组件上使用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被调用两次(一次在服务器上,一次在客户端上),我不希望这样。

有没有关于如何解决这个问题的建议?建议?

3 个答案:

答案 0 :(得分:5)

我是通过手机输入的,所以我为没有格式化而道歉。

对于我的项目,我做了类似于你的事情;我有一个静态fetchData方法,我循环遍历renderProps中的组件,然后我调用静态方法并等待promises解决。

然后,我从我的redux商店调用get状态,对其进行字符串化,并将其传递给服务器上的渲染函数,以便它可以在客户端上呈现出初始状态对象。

从客户端,我只是获取初始状态变量并将其传递给我的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>
        );
    }
};

重申......

  1. 在客户端上,抓取状态变量并将其传递给您的商店。
  2. 在服务器上,遍历组件,调用fetchData并传递商店。等待承诺得到解决,然后渲染。
  3. 在HTML.js(您的renderView函数)中,序列化您的Redux存储并将输出呈现给客户端的javascript变量。
  4. 在您的React组件中,为要调用的服务器创建静态fetchData方法。发送您需要的行动。

答案 1 :(得分:1)

您可以使用fbjs模块中的CanUseDOM。

import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
//only render on the server because it doesn't have DOM
if(!canUseDOM)
 static fetch here

答案 2 :(得分:0)

更好的想法是在服务器端使您的商店状态脱水,并在客户端以脱水状态保持初始商店状态。

来自Redux Doc:

  

这样可以轻松创建通用应用程序,因为服务器中的状态可以序列化并充当客户端而无需额外的编码工作。

http://redux.js.org/docs/introduction/ThreePrinciples.html