对组件

时间:2017-07-06 17:40:20

标签: reactjs express serverside-rendering ssr

有一个应用程序在前端做出反应,并表示为服务器后端。我需要将html视图重写为SSR React组件。

结构是这样的:

  • 配置
  • 日志
  • 维护
  • node_modules
    • (这里是所有应用程序,服务器,客户端和其他组件)

HTML视图位于node_modules/webserver/views

例如,在node_modules/webserver/server/routes.js内,我们有一行:

app.get('/test', script.test);

node_modules/webserver/server/script.js内我们有方法:

exports.test = function (req, res, next) {    
    res.render('test', { user: req.user, page: 'test' });
}; 

这里应该渲染新的React组件。

我该如何正确地做到这一点?

1 个答案:

答案 0 :(得分:0)

基本上,您需要一个请求处理程序来处理服务器端的api调用,您需要在component中定义一个静态方法,以便我们可以从服务器端进行调用。

'use strict';

import React from 'react';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { applyMiddleware, createStore } from 'redux';
import { renderToString } from 'react-dom/server';
import { SheetsRegistry } from 'jss';
import JssProvider from 'react-jss/lib/JssProvider';
import path from 'path'
import fs from 'fs'
import {
  MuiThemeProvider,
  createMuiTheme,
  createGenerateClassName,
} from '@material-ui/core/styles';
import green from '@material-ui/core/colors/green';
import red from '@material-ui/core/colors/red';
import { StaticRouter, matchPath } from 'react-router-dom';
import DocumentMeta from 'react-document-meta';
import reducers from '../reducers/index';
import routes from '../routes';
import routesConfigs from './routesConfig';

const middleware = applyMiddleware(thunk);

function renderView(req, res, state) {

  // Create a theme instance.
  const theme = createMuiTheme({
    palette: {
      primary: green,
      accent: red,
      type: 'light',
    },
  });

  // STEP-1 CREATE A REDUX STORE ON THE SERVER
  const store = createStore(reducers, state, middleware);
  const sheetsRegistry = new SheetsRegistry();
  // Create a sheetsManager instance.
  const sheetsManager = new Map();
  const generateClassName = createGenerateClassName();
  // STEP-2 GET INITIAL STATE FROM THE STORE
  const initialState = JSON.stringify(store.getState()).replace(/<\/script/g, '<\\/script').replace(/<!--/g, '<\\!--');
  // STEP-3 IMPLEMENT REACT-ROUTER ON THE SERVER TO INTERCEPT CLIENT REQUESTs AND DEFINE WHAT TO DO WITH THEM
  const context = {};
  const reactComponent = renderToString(
    <JssProvider registry={sheetsRegistry} generateClassName={generateClassName}>
      <MuiThemeProvider theme={theme} sheetsManager={sheetsManager}>
        <Provider store={store}>
          <StaticRouter
            location={req.url}
            context={context}>
            {routes}
          </StaticRouter>
        </Provider>
      </MuiThemeProvider>
    </JssProvider>
  );
  const css = sheetsRegistry.toString()
  const reactMetaComponent = DocumentMeta.renderToStaticMarkup();
  if (context.url) {
    // can use the `context.status` that
    // we added in RedirectWithStatus
    redirect(context.status, context.url);
  } else {
    //https://crypt.codemancers.com/posts/2016-09-16-react-server-side-rendering/
    //res.status(200).render('index', { reactComponent, reactMetaComponent, initialState });
    fs.readFile(path.resolve('build/index.html'), 'utf8', (err, data) => {
      if (err) {
        return res.status(500).send('An error occurred')
      }
      const replacedData = data.replace(
        '<div id="root"></div>',
        `<div id="root">${reactComponent}</div>
        <style id="jss-server-side">${css}</style>
        <script>
          window.INITIAL_STATE = ${initialState}
        </script>`
      );
      const replacedMetaTagData = replacedData
        .replace(`<meta id="reactMetaTags"/>`,
          `${reactMetaComponent}`);
      res.send(replacedMetaTagData);
    })
  }
}

function handleRender(req, res) {
  const components = routesConfigs
    .filter(route => matchPath(req.path, route)) // filter matching paths
    .map(route => route.component); // check if components have data requirement
  let promiseObj = null;
  if (components.length > 0 && (components[0].fetchData instanceof Function)) {
   /* fetchData is the function defined in each component and make it like class 
     function and it will be called at server side
   */
    components[0]
      .fetchData(req.query)
      .then((response) => {
        renderView(req, res, response);
      })
      .catch((error) => {
        console.log('***--- handleRender error ', error);
        renderView(req, res, {});
      });
  } else {
    renderView(req, res, {});
  }
}

module.exports = handleRender;

// App.js

const app = express();
// REQUEST HANDLER FOR SERVER-SIDE RENDERING
const requestHandler = require('./requestHandler');
app.use(requestHandler);

Routes Configurations,基本上,我们将维护一个js文件来跟踪作为组件的每个路由

//routesConfig.js
'use strict';

import CommentsComponent from '../client/components/pages/CommentsComponent';
import LandingComponent from '../client/components/pages/LandingComponent'

export default [
    {
        path: "/",
        component: LandingComponent,
        exact: true,
    },
    {
        path: `/comments`,
        component: CommentsComponent,
        exact: true,
    }
];

组件

//CommentsComponent.js
'use strict';
import React from 'react';
import { connect } from 'react-redux';
import DocumentMeta from 'react-document-meta';
import { fetchComments } from '../actions/commentsActions';
const queryString = require('query-string');

class CommentsComponent extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {

    return (
      <div className='container-fluid'>
        /* populate comments here */
      </div>
    );
  }
}

CommentsComponent.fetchData = fetchComments;
export default CommentsComponent;

//Index.js 
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './client/src/serviceWorker';
import App from './client/src/App';

ReactDOM.hydrate(
  <App />,
  document.getElementById('root')
);
serviceWorker.unregister();

//客户端app.js

import React, { Component } from 'react';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import JssProvider from 'react-jss/lib/JssProvider';
import {
  MuiThemeProvider,
  createMuiTheme,
  createGenerateClassName
} from '@material-ui/core/styles';
import { applyMiddleware, createStore } from 'redux';
import { BrowserRouter } from 'react-router-dom'
import green from '@material-ui/core/colors/green'
import red from '@material-ui/core/colors/red';
import Routes from './routes';
import reducers from './reducers';

const middleware = applyMiddleware(thunk);
const initialState = window.INITIAL_STATE;
const store = createStore(reducers, initialState, middleware);

// Create a theme instance.
const theme = createMuiTheme({
  palette: {
    primary: green,
    accent: red,
    type: 'light',
  },
});
const generateClassName = createGenerateClassName();

class App extends Component {
  componentDidMount() {
    //Not required to remove css since it is already came from sssr
    // const jssStyles = document.getElementById('jss-server-side');
    // if (jssStyles && jssStyles.parentNode) {
    //   jssStyles.parentNode.removeChild(jssStyles);
    // }
  }

  render() {
    return (
      <Provider store={store}>
        <BrowserRouter>
          <JssProvider generateClassName={generateClassName}>
            <MuiThemeProvider theme={theme} >
              {Routes}
            </MuiThemeProvider>
          </JssProvider>
        </BrowserRouter>
      </Provider>
    );
  }
}

export default App;

注意:如果您不使用材料ui库,则排除MuiThemeProvider和JssProvider

有关更多信息: http://knowledge-cess.com/server-side-rendering-in-react-16-with-create-react-app-using-material-ui-redux-and-express-libraries/