React性能优化 - react服务端同构SSR
简介:通过首屏使用服务端渲染,其他还是使用前端代码,来减少白屏时间,优化用户体验
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() })
相关推荐
游走的豚鼠君 2020-11-10
81417707 2020-10-30
iftrueIloveit 2020-07-04
80981934 2020-05-18
ctg 2020-10-14
小飞侠V 2020-09-25
PncLogon 2020-09-24
jipengx 2020-09-10
颤抖吧腿子 2020-09-04
wwzaqw 2020-09-04
maple00 2020-09-02
青蓝 2020-08-26
罗忠浩 2020-08-16
liduote 2020-08-13
不知道该写啥QAQ 2020-08-02
pengruiyu 2020-08-01
wmd看海 2020-07-27
孝平 2020-07-18