如何在React/Redux中进行服务器端渲染?

13

我是一名新手,对于react/redux中的服务器端渲染有些困惑。虽然在网上看到了一些例子,但当我尝试使用外部服务器的模拟API时,服务器端渲染并没有生效。

cat.js

import React from 'react';
import {render} from 'react-dom';
import {connect} from 'react-redux';
import * as mockApi from '../Actions/mockActions';
class Cat extends React.Component{
    componentWillMount(){
        this.props.getMockApi();
    }
    render(){
        return(
            <div>
                Hello Dude
                {this.props.mock.data.map((data,i) => {
                    return <li key={i}>{data.email}</li>
                })}
            </div>
        )
    }
}

const mapStateToProps = (state) => {

    return {
       mock:state.mock
    }
};
const mapDispatchToProps = (dispatch) => {
    return {
        getMockApi:() => dispatch(mockApi.getMockData())
    }
};


export default connect(mapStateToProps,mapDispatchToProps)(Cat);

mockActions.js

import axios from 'axios';
import * as types from './actionTypes';

export function getMockData() {
    return dispatch => {
        return axios.get('http://jsonplaceholder.typicode.com/users').then(response => {
            dispatch(setThisData(response.data))
        })
    }
}

export function setThisData(data) {

    return {
        type:types.MOCK_API,
        payload:data
    }
}

App.js

import React from 'react';
import {render} from 'react-dom';

import Cat from './components/cat'
import {Provider} from 'react-redux';
import configureStore from './Store/configureStore';
import { createStore ,applyMiddleware,compose} from 'redux';
import counterApp from './Reducers'
import thunk from 'redux-thunk';







if(typeof window !== 'undefined'){
    // Grab the state from a global variable injected into the server-generated HTML
    const preloadedState = window.__PRELOADED_STATE__

// Allow the passed state to be garbage-collected
    delete window.__PRELOADED_STATE__
    const store = createStore(counterApp, preloadedState, compose(applyMiddleware(thunk)))


    render(
        <Provider store={store} >
            <Cat/>
        </Provider>
        ,
        document.getElementById('app')
    )



}

devServer.js

import express from 'express';
import path from 'path';
import webpack from 'webpack';
import webpackMiddleware from 'webpack-dev-middleware'
import webpackHotMidleware from 'webpack-hot-middleware';
import bodyParser from 'body-parser';
import React from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import counterApp from '../../src/client/ReduxServer/Reducers';
import App from '../../src/client/ReduxServer/components/cat';
import { renderToString } from 'react-dom/server'


import webpackConfig from '../../webpack.config.dev';


let app = express();
app.use(bodyParser.json());
app.use(express.static('public'))


const compiler = webpack(webpackConfig);

app.use(webpackMiddleware(compiler, {
    hot: true,
    publicPath: webpackConfig.output.publicPath,
    noInfo: true
}));

app.use(webpackHotMidleware(compiler));


// app.get('/*', (req, res) => {
//     res.sendFile(path.join(__dirname, '../../index.html'))
// });


//Redux Start
app.use(handleRender);

function handleRender(req,res) {
   const store = createStore(counterApp);

   const html = renderToString(
       <Provider store={store} >
           <App/>
       </Provider>
   )
    const preloadedState = store.getState();
    // Send the rendered page back to the client
    res.send(renderFullPage(html, preloadedState))
}
function renderFullPage(html, preloadedState) {
    console.log(preloadedState)
    return `
            <!doctype html>
    <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
         <div id="app">${html}</div>
          <script>
           window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
        </script>

      <script src="bundle.js"></script>

      </body>
  </html>
        `
}







//Redux Ends








app.listen(3000, () => {
    console.log('Listening')
});

现在,这只会服务渲染“hello dude”,但不会渲染模拟的 API 调用数据。我知道我错过了从服务器端获取数据的步骤,但问题是如果我要渲染两个组件且每个组件有 5 个 API 请求,我该怎么做?如何获取正确的 API 请求。

现在,我的客户端预取状态将如下所示:

 window.__PRELOADED_STATE__ = {"mock":{"data":[]}}

为什么不试试 http://mern.io/。它具有内置的服务器端渲染功能。 - Irfan Ali
我想学习这些东西。 - Nane
1个回答

6

好的,为了让这个更清楚,您已经创建了处理服务器渲染的代码。然而,它没有加载应该被提取的数据,对吗?

您已经完成了第一步,非常好!下一步是将实际的动态数据加载到存储器中。让我们看看这里的代码

function handleRender(req,res) {
   const store = createStore(counterApp);

   const html = renderToString(
       <Provider store={store} >
           <App/>
       </Provider>
   )
    const preloadedState = store.getState();
    // Send the rendered page back to the client
    res.send(renderFullPage(html, preloadedState))
}

发生的事情是您创建了一个存储器。该存储器用于将HTML呈现为字符串。然后,您获取存储器状态并将其放入preloadedState中。
这很好,但是renderToString不会像您期望的那样调用this.props.getMockApi();。
相反,在调用renderToString()之前,您必须获取状态。
在这种情况下,您可以按照以下步骤操作。(请注意,这只是一个示例,在生产中您可能需要使用更通用的东西,特别是如果您使用类似react-router的东西。)

import * as mockApi from '../Actions/mockActions';

function handleRender(req, res) {
  const store = createStore(counterApp);

  store.dispatch(mockApi.getMockData())
  // And since you used redux-thunk, it should return a promise
  .then(() => {
    const html = renderToString(
      <Provider store={store}>
        <App/>
      </Provider>
    )
    const preloadedState = store.getState();
    // Send the rendered page back to the client
    res.send(renderFullPage(html, preloadedState))
  });
}

简单吧? ;D,开个玩笑。这是React的一部分,在这里还没有确切的解决方案。

就我个人而言,如果我有机会回到过去,我会告诉自己学习除服务器渲染之外的其他技术。例如代码分割、懒加载等等。对于服务器渲染来说,如果JavaScript在用户看到初始页面后很长时间才到达,他们可能会因为其他需要JavaScript的事情感到沮丧。例如在我的情况下,有些链接不起作用,有些按钮没有任何反应等。

我并不是说服务器渲染不好。它是一种有趣的技术,只是有其他更有益于先学习的技术(哦,服务器渲染基本上锁定了您必须使用nodejs作为后端)。祝你好运:)


太好了,它能正常工作。但是如果我正在使用React Router,该怎么做呢? - Nane
1
我会做的基本上是:在每个组件上创建一个静态函数。这个函数将保存所有数据调用。在handleRender()中,使用react-router的match来查找要呈现的组件列表。之后,调用之前创建的静态函数。 - Vija02
我是一个初学者,你能在这里回答一下吗?问题链接 - Nane
它不起作用,因为action的函数返回promise而不是dispatch! - Daryn K.
使用redux-thunk来实现。 - Vija02
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接