有一个应用程序在前端做出反应,并表示为服务器后端。我需要将html视图重写为SSR React组件。
结构是这样的:
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组件。
我该如何正确地做到这一点?
答案 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