2016-03-26 3 views
7

...(Universal React + redux + react-router)最初のブラウザの読み込み時にルートデータを再取得しないようにするにはどうすればよいですか?私は私のルートコンポーネントに<code>static fetchData</code>方法を使用しています

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が2回呼び出されています(1回はサーバー上で、もう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 
+0

しかし、その方法を使用すると、私はサーバー側の約束を解決できないでしょうか? – kevinwolf

0

良いアイデアは、サーバー側であなたの店の状態を脱水し、脱水状態で、クライアント側の初期ストア状態を水和することです。 Reduxのドクから

サーバーから状態が余分なコーディング作業をクライアントにシリアライズし、水和することができるように、これは、ユニバーサルアプリケーションを作成することが容易になります。

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

5

私は自分の携帯電話からこれを入力していますので、私は、フォーマットの不足をお詫び申し上げます。

私のプロジェクトでは、あなたと似たようなことをしています。私は静的なfetchDataメソッドを持っている、私はrenderPropsからコンポーネントをループし、私は静的メソッドを呼び出して、約束を解決するのを待つ。

次に、私のreduxストアからget stateを呼び出し、文字列化して、クライアント上の初期状態オブジェクトをレンダリングできるようにサーバー上のレンダリング関数に渡します。

クライアントから、私はちょうどその初期状態変数を取得し、それを私のreduxストアに渡します。 Reduxはクライアントストアをサーバ上のものと一致するように処理します。そこから、あなたはあなたの店をプロバイダに渡し、いつものように続行するだけです。クライアント上で静的メソッドを呼び出す必要はありません。

私が言ったことの例として、コードが説明するように自分のgithubプロジェクトをチェックすることができます。 https://github.com/mr-antivirus/riur

お役に立てれば幸いです!


[編集]ここにコードがあります!

クライアント。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. あなたのリアクションコンポーネントでは、サーバーが呼び出すONLYの静的fetchDataメソッドを作成します。必要な措置を派遣します。
+0

ありがとう!私はOPと同じ問題を抱えていましたが、ステップ#4は、クライアント側のマウントに必要な場合にのみ取得するためのah-haでした。 – franky

+0

喜んでそれが助けられました:-) –

関連する問題