简介:通过首屏使用服务端渲染,其他还是使用前端代码,来减少白屏时间,优化用户体验
node环境使用babel-node支持jsx
// 安装babel-cli转换服务端代码
npm install babel-cli --save
// 修改package.json中的script,使用babel-node启动
"server": "NODE_ENV = test nodemon --exec babel-node --server/server.js"
//修改前的script
"server": "nodemon server/server.js"
//配置全局.babelrc文件(如果原来写在package.json则提取出来
测试:
// 在server.js中写入,不报错
function App() {
return <h2>server render</h2>
}
console.log(App())
结果:在控制台打印则成功
设置css和图片的hook
服务端不能识别css/png等静态资源文件
需要借助插件
asset-require-hook
css-moudules-require-hook
使用:
css-moudules-require-hook
npm install css-moudules-require-hook
// 1.引入 import hook before routes
import csshook from 'css-modules-require-hook/preset'
// 2.新建配置文件 cmrh.conf.js
moudule.exports = {
generateScopedName: '[name]__[local]___[hash:base64:5]',
}
asset-require-hook
npm i --save asset-require-hook
import assethook from 'asset-require-hook'
assethook({
extensions: ['jpg', 'png']
})
renderToString渲染HTML
把一个React元素渲染为原始的HTML
import { renderToString } from 'react-dom/server'
// React组件 => html
function App() {
rerurn(
<div>
<p>server render</p>
</div>)
}
console.log(renderToString(<App />))
// <div data-reactroot=""><p>server render</p></div>
简单服务端渲染例子
app.use('/msg', function(req, res) {
const htmlRes = renderToString(<App />)
res.send(htmlRes)
})
运行网页截图:
点击查看网页源代码
实战例子
如果要让代码在服务端运行,实现和前端一样的逻辑,生成代码
在服务端实现main.js
step1: 提取服务端和客户端的公共代码(达到复用,客户端有些代码,服务端需要换种方式实现)
eg:
服务端拿不到document对象,document.getElementById('root'),在服务端直接换成 - 字符串拼接
服务端没有ReactDom.render, 使用ReactDomServer.renderToString()
etc...
抽取后的app.js(只是把路由组件部分抽取出来了)
// 抽离公共组件
import React from 'react'
import { Router, Switch } from 'react-router-dom'
import Login from './containers/login'
import Register from './containers/register'
import Dashboard from './containers/dashboard'
class App extends React.Component {
render() {
return (
<div>
<AuthRoute />
<Switch>
<Route path="login" component={Login} />
<Route path="register" component={Register} />
<Route component={Dashboard} />
</Switch>
</div>
)
}
}
export default App
抽取后的main.js
import React from 'react'
import ReactDom from 'react-dom'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-connect'
import App from './app'
import reducer from './reducer'
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
))
ReactDom.render(
(<Provider store={store}>
<BrowserRouter>
<App></App>
</BrowserRouter>
</Provider>),
document.getElementById('root')
)
step2: 在服务端实现前端react代码(通过上面提取的index.js的基础上进行修改)
import React from 'react'
// import ReactDom from 'react-dom' 去除ReactDom.render换用renderToString
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
// import hook before routes
// 解决静态资源文件,服务端无法识别静态资源文件
import csshook from 'css-modules-require-hook/preset'
import assethook from 'asset-require-hook'
assethook({
extensions: ['jpg', 'png']
})
// import { BrowserRouter } from 'react-router-connect' 换用StaticRouter,服务端没有BrowserRouter
import { StaticRouter } from 'react-router-connect'
import App from '../src/app'
import reducer from '../src/reducer'
app.use('/msg', function (req, res) {
// 新建store,去除启用浏览器视察插件代码
const store = createStore(reducer, compose(
applyMiddleware(thunk),
))
let context = {}
const markUp = renderToString(
(<Provider store={store}>
<StaticRouter
location = {req.url}
context = {context}
>
<App></App>
</StaticRouter>
</Provider>)
)
res.send(markUp)
})
运行结果
缺少页面主体:
document.getElementById('root')实现
index.html
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>杏服小秘书</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
打包后的index.html
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>杏服小秘书</title>
<link href="/assert/main-532043e6aeabb57a41ec.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/assert/main-ce8b942c40513fc7d2d8.js"></script></body>
</html>
解决:直接将生成的代码,插入index.html中
// 可以做seo
const pageHtml = `<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>杏服小秘书</title>
</head>
<body>
<div id="root">${markup}</div>
</body>
</html>`
res.send(pageHtml)
表现:主体已经插入,但缺少打包后的css/js文件
页面展示仍然不完善
文件目录中的manifest.json
解决:手动插入打包后的js,css文件
完整版服务端渲染React代码
import React from 'react'
// import ReactDom from 'react-dom'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
// import hook before routes
// 解决静态资源文件
import csshook from 'css-modules-require-hook/preset'
import assethook from 'asset-require-hook'
assethook({
extensions: ['jpg', 'png']
})
// import { BrowserRouter } from 'react-router-connect'
import { StaticRouter } from 'react-router-connect'
import App from '../src/app'
import reducer from '../src/reducer'
import staticPath from '../build/asset-manifest.json'
app.use('/msg', function (req, res) {
const store = createStore(reducer, compose(
applyMiddleware(thunk),
))
let context = {}
// setp 1
const markUp = renderToString(
(<Provider store={store}>
<StaticRouter
location = {req.url}
context = {context}
>
<App></App>
</StaticRouter>
</Provider>)
)
// 可以做seo
const pageHtml = `<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>杏服小秘书</title>
<link href="/${staticPath['main.css']}" rel="stylesheet">
</head>
<body>
<div id="main">${markup}</div>
<script type="text/javascript" src="/${staticPath['main.js']}"></script>
</body>
</html>`
res.send(pageHtml)
})
补充:staticRouter
https://testudy.cc/tech/2017/...
React16服务端渲染新Api
- 之前版本的renderToString,解析为字符串
- 新版本的renderToNodeStream解析为可读的字节流对象
- 使用ReactDom.hydate取代ReactDom.render
性能可以提升至原来三倍
改写为流输出
res.write(`<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>杏服小秘书</title>
<link href="/${staticPath['main.css']}" rel="stylesheet">
</head>
<body>
<div id="main">${markup}`
const markupStream = renderToNodeStream(
(<Provider store={store}>
<StaticRouter
location = {req.url}
context = {context}
>
<App></App>
</StaticRouter>
</Provider>)
)
)
// end: false - 不关闭流,继续传输
markupStream.pipe(res, { end: false })
markupStream.on('end', () => {
res.write(`</div>
<script type="text/javascript" src="/${staticPath['main.js']}"></script>
</body>
</html>`)
res.end()
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。