内容简介:大家好,我是神三元,这一次,让我们来把React服务端渲染(Server Side Render,简称“SSR”)学个明明白白。什么是服务端渲染?废话不多说,直接起一个express服务器。
大家好,我是神三元,这一次,让我们来把React服务端渲染(Server Side Render,简称“SSR”)学个明明白白。
一. SSR vs CSR
什么是服务端渲染?
废话不多说,直接起一个express服务器。
var express = require('express')
var app = express()
app.get('/', (req, res) => {
res.send(
`
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>hello</h1>
<p>world</p>
</body>
</html>
`
)
})
app.listen(3001, () => {
console.log('listen:3001')
})
复制代码
启动之后打开localhost:3001可以看到页面显示了hello world。而且打开网页源代码:
也能够完成显示。
这就是服务端渲染。其实非常好理解,就是服务器返回一堆html字符串,然后让浏览器显示。
与服务端渲染相对的是客户端渲染(Client Side Render)。那什么是客户端渲染? 现在创建一个新的React项目,用脚手架生成项目,然后run起来。 这里你可以看到React脚手架自动生成的首页。
然而打开网页源代码。
body中除了兼容处理的noscript标签之外,只有一个id为root的标签。那首页的内容是从哪来的呢?很明显,是下面的script中拉取的JS代码控制的。
因此,CSR和SSR最大的区别在于前者的页面渲染是JS负责进行的,而后者是服务器端直接返回HTML让浏览器直接渲染。
为什么要使用服务端渲染呢?
传统CSR的弊端:
- 由于页面显示过程要进行JS文件拉取和React代码执行,首屏加载时间会比较慢。
- 对于SEO(Search Engine Optimazition,即搜索引擎优化),完全无能为力,因为搜索引擎爬虫只认识html结构的内容,而不能识别JS代码内容。
SSR的出现,就是为了解决这些传统CSR的弊端。
二、实现React组件的服务端渲染
刚刚起的express服务返回的只是一个普通的html字符串,但我们讨论的是如何进行React的服务端渲染,那么怎么做呢? 首先写一个简单的React组件:
// containers/Home.js
import React from 'react';
const Home = () => {
return (
<div>
<div>This is sanyuan</div>
</div>
)
}
export default Home
复制代码
现在的任务就是将它转换为html代码返回给浏览器。 总所周知,JSX中的标签其实是基于虚拟DOM的,最终要通过一定的方法将其转换为真实DOM。
而react-dom这个库中刚好实现了这个方法。做法如下:
// server/index.js
import express from 'express';
import { renderToString } from 'react-dom/server';
import Home from './containers/Home';
const app = express();
const content = renderToString(<Home />);
app.get('/', function (req, res) {
res.send(
`
<html>
<head>
<title>ssr</title>
</head>
<body>
<div id="root">${content}</div>
</body>
</html>
`
);
})
app.listen(3001, () => {
console.log('listen:3001')
})
复制代码
启动express服务,再浏览器上打开对应端口,页面显示出"this is sanyuan"。 到此,就初步实现了一个React组件是服务端渲染。
三.引入同构
其实上面的React组件是不完整的,平时在开发的过程中难免会有一些事件绑定,比如加一个button:
// containers/Home.js
import React from 'react';
const Home = () => {
return (
<div>
<div>This is sanyuan</div>
<button onClick={() => {alert('666')}}>click</button>
</div>
)
}
export default Home
复制代码
再试一下,你会惊奇的发现,事件绑定无效!那这是为什么呢?原因很简单,react-dom/server下的renderToString并没有做事件相关的处理,因此返回给浏览器的内容不会有事件绑定。
那怎么解决这个问题呢?
这就需要进行同构了。所谓同构,通俗的讲,就是一套React代码在服务器上运行一遍,到达浏览器又运行一遍。服务端渲染完成页面结构,浏览器端渲染完成事件绑定。
那如何进行浏览器端的事件绑定呢?
唯一的方式就是让浏览器去拉取JS文件执行,让JS代码来控制。于是服务端返回的代码变成了这样:
有没有发现和之前的区别?区别就是多了一个script标签。而它拉取的JS代码就是来完成同构的。
那么这个index.js我们如何生产出来呢?
在这里,要用到react-dom。具体做法其实就很简单了:
//client/index. js
import React from 'react';
import ReactDom from 'react-dom';
import Home from '../containers/Home';
ReactDom.hydrate(<Home />, document.getElementById('root'))
复制代码
然后用webpack将其编译打包成index.js:
//webpack.client.js
const path = require('path');
const merge = require('webpack-merge');
const config = require('./webpack.base');
const clientConfig = {
mode: 'development',
entry: './src/client/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'public')
},
}
module.exports = merge(config, clientConfig);
//webpack.base.js
module.exports = {
module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react', ['@babel/preset-env', {
targets: {
browsers: ['last 2 versions']
}
}]]
}
}]
}
}
复制代码
在这里需要开启express的静态文件服务:
const app = express();
app.use(express.static('public'));
复制代码
现在前端的script就能拿到控制浏览器的JS代码啦。
绑定事件完成!
现在来初步总结一下同构代码执行的流程:
四.同构中的路由问题
现在写一个路由的配置文件:
// Routes.js
import React from 'react';
import {Route} from 'react-router-dom'
import Home from './containers/Home';
import Login from './containers/Login'
export default (
<div>
<Route path='/' exact component={Home}></Route>
<Route path='/login' exact component={Login}></Route>
</div>
)
复制代码
在客户端的控制代码,也就是上面写过的client/index.js中,要做相应的更改:
import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter } from 'react-router-dom'
import Routes from '../Routes'
const App = () => {
return (
<BrowserRouter>
{Routes}
</BrowserRouter>
)
}
ReactDom.hydrate(<App />, document.getElementById('root'))
复制代码
这时候控制台会报错,
因为在Routes.js中,每个Route组件外面包裹着一层div,但服务端返回的代码中并没有这个div,所以报错。如何去解决这个问题?需要将服务端的路由逻辑执行一遍。
// server/index.js
import express from 'express';
import {render} from './utils';
const app = express();
app.use(express.static('public'));
//注意这里要换成*来匹配
app.get('*', function (req, res) {
res.send(render(req));
});
app.listen(3001, () => {
console.log('listen:3001')
});
复制代码
// server/utils.js
import Routes from '../Routes'
import { renderToString } from 'react-dom/server';
//重要是要用到StaticRouter
import { StaticRouter } from 'react-router-dom';
import React from 'react'
export const render = (req) => {
//构建服务端的路由
const content = renderToString(
<StaticRouter location={req.path} >
{Routes}
</StaticRouter>
);
return `
<html>
<head>
<title>ssr</title>
</head>
<body>
<div id="root">${content}</div>
<script src="/index.js"></script>
</body>
</html>
`
}
复制代码
现在路由的跳转就没有任何问题啦。
这篇文章中,总结了服务端与客户端渲染的对比、简单react组件的服务端渲染、通过事件绑定需求引入同构、如何来解决同构中的路由问题这四个方面的内容,也是SSR中非常重要的部分。希望对大家有所帮助,也欢迎大家在评论区交流。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- react同构实践——实现自己的同构模板
- 如何构建一个WEB同构应用
- React Native 三端同构实战
- React服务端渲染(前后端路由同构)
- React 中同构(SSR)原理脉络梳理
- 打造可降级的React服务端同构框架
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
新零售:低价高效的数据赋能之路
刘润 / 中信出版集团 / 2018-9 / 65.00元
小米新零售,如何做到20倍坪效? 天猫小店,如何利用大数据助力线下零售? 盒马鲜生,为什么坚持必须用App才能买单? 名创优品,实体小店在电商冲击下,如何拥抱春天? 新零售的未来在何方?什么样的思维模式才可应对? 新零售,不是商界大佬的专用名词,它就在我们生活触手可及的各个角落——小到便利店的酸奶,大到京东商城的冰箱,都蕴含着消费者、货物、经营场所三者共同作用的经济逻......一起来看看 《新零售:低价高效的数据赋能之路》 这本书的介绍吧!