详解React服务端渲染(同构)的方法
React的服务端渲染(SSR)或同构应用是指将React组件在服务器端进行渲染,将渲染结果发送到客户端,客户端将不再需要JavaScript来根据React组件生成DOM,而直接使用服务器端渲染的结果。同构应用的好处在于可以提高前端应用的性能和SEO。下面将会介绍如何进行React服务端渲染。
1.创建基础项目
首先建立一个基础项目来进行React服务端渲染。在项目根目录下执行以下命令:
$ npm init -y
$ npm install --save react react-dom express
创建一个简单的React组件,并编写服务器端渲染代码并返回给客户端显示。
// index.js
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import express from 'express';
const app = express();
const HelloWorld = () => {
return (
<div>
<h1>Hello world!</h1>
<p>This is my first server-side rendered React.</p>
</div>
)
}
app.get('/', (req, res) => {
const html = ReactDOMServer.renderToString(<HelloWorld />);
res.send(`
<html>
<head>
<title>React SSR Demo</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000, () => {
console.log('server started on port 3000');
});
在浏览器中打开http://localhost:3000,应该看到渲染出了“Hello world”和“This is my first server-side rendered React”信息。
2.使用React Router进行路由
React Router是管理应用程序路由的最流行的库之一。使用React Router进行服务器端渲染有两种方法:StaticRouter和BrowserRouter。StaticRouter用于静态网站,BrowserRouter更适用于web应用。本文使用BrowserRouter。
首先需要更改React组件到路由组件:
// HelloWorld.js
import React from 'react';
const HelloWorld = () => {
return (
<div>
<h1>Hello world!</h1>
<p>This is my first server-side rendered React.</p>
</div>
)
}
export default HelloWorld;
// App.js
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import HelloWorld from './HelloWorld';
const App = () => {
return (
<Switch>
<Route exact path="/" component={HelloWorld} />
<Route render={() => <div>404 Not Found</div>} />
</Switch>
);
};
export default App;
接下来更改服务器端代码,使用React Router渲染组件,并处理嵌套路由情况:
// index.js
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter as Router } from 'react-router-dom';
import express from 'express';
import App from './App';
const app = express();
app.use('/static', express.static('static'));
app.get('*', (req, res) => {
const context = {};
const html = ReactDOMServer.renderToString(
<Router location={req.url} context={context}>
<App />
</Router>
);
if (context.url) {
res.redirect(context.url);
} else {
res.status(200).send(`
<html>
<head>
<title>React SSR Demo</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/static/bundle.js"></script>
</body>
</html>
`);
}
});
app.listen(3000, () => {
console.log('server started on port 3000');
});
3.使用Redux控制组件状态
当你的React组件需要依赖一些异步数据时,通常会用到Redux。这时候,需要将Redux store传递给服务器端渲染的React组件。
首先需要安装Redux:
$ npm install --save redux react-redux redux-thunk
下面是更新后的App.js:
// App.js
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';
import HelloWorld from './HelloWorld';
const App = () => {
return (
<Provider store={store}>
<Switch>
<Route exact path="/" component={HelloWorld} />
<Route render={() => <div>404 Not Found</div>} />
</Switch>
</Provider>
);
};
export default App;
store.js文件如下:
// store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const initialState = {};
const reducer = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
在服务器端渲染时需要使用Redux的server-side rendering功能,并使用StoreProvider包装组件:
// index.js
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter as Router } from 'react-router-dom';
import express from 'express';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
const app = express();
app.use('/static', express.static('static'));
app.get('*', (req, res) => {
const context = {};
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<Router location={req.url} context={context}>
<App />
</Router>
</Provider>
);
if (context.url) {
res.redirect(context.url);
} else {
res.status(200).send(`
<html>
<head>
<title>React SSR Demo</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/static/bundle.js"></script>
</body>
</html>
`);
}
});
app.listen(3000, () => {
console.log('server started on port 3000');
});
4.使用Hydrate进行客户端渲染
在服务器端渲染结束后,客户端会重新渲染组件。在这个过程中,需要使用ReactDOM.hydrate()函数,而不是ReactDOM.render()。hydarte()将会尝试重用服务器端渲染后的组件,以便快速呈现静态内容。例如:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.hydrate(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
通过上面的步骤,就可以完成React的服务端渲染。
示例1:使用CSS Modules渲染样式
CSS Modules将CSS文件锁定到组件级别,为React组件提供了一种更优美的样式方案。在React应用程序中使用CSS Modules与服务器端渲染的解决方案非常相似:仅需要在服务器渲染期间的CSS中执行css-modules-require-hook并将这个CSS传递到组件级别。
首先需要安装css-loader和css-modules-require-hook:
$ npm install --save-dev css-loader css-modules-require-hook babel-register
更新webpack.config.js文件,以便使用css-loader和css-modules-require-hook提供的抽象:
// webpack.config.js
module.exports = {
entry: './client.js',
output: {
path: __dirname + '/static',
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: [
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
},
onlyLocals: true
}
}
]
}
]
}
};
添加以下代码段到index.js:
// index.js
require('css-modules-require-hook')({
generateScopedName: '[name]__[local]__[hash:base64:5]',
extensions: ['.css']
});
require('babel-register')({
extensions: ['.js', '.jsx']
});
在HelloWorld.js中添加样式:
import React from 'react';
import styles from './HelloWorld.css';
const HelloWorld = () => {
return (
<div className={styles.hello}>
<h1>Hello world!</h1>
<p>This is my first server-side rendered React.</p>
</div>
);
};
export default HelloWorld;
.hello {
color: blue;
}
在客户端组件中只需要简单地将HelloWorld.css导入即可。此时,在服务器端渲染的React组件中,class应该是动态生成的。
示例2:使用React Helmet进行SEO
React Helmet是管理head标签的最流行的库之一。应用程序需要在head标签中包含某些meta,title和link元素,特别是在SEO的方面。
在服务器渲染React组件时,需要使用React Helmet将标记添加到head标签中。在客户端代码中,使用ReactDOM.hydrate()再次渲染React组件,React Helmet会更新head标签。例如:
// HelloWorld.js
import React from 'react';
import Helmet from 'react-helmet';
import styles from './HelloWorld.css';
const HelloWorld = () => {
return (
<div className={styles.hello}>
<Helmet>
<title>Hello World</title>
<meta property="og:title" content="Hello World" />
<meta name="description" content="This is my first server-side rendered React." />
</Helmet>
<h1>Hello world!</h1>
<p>This is my first server-side rendered React.</p>
</div>
);
};
在实际应用中,可以根据需要添加更多的meta和link标记。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解react服务端渲染(同构)的方法 - Python技术站