React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

栏目: IOS · Android · 发布时间: 4年前

内容简介:大家好,我是神三元,这一次,让我们来把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。而且打开网页源代码:

React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

也能够完成显示。

这就是服务端渲染。其实非常好理解,就是服务器返回一堆html字符串,然后让浏览器显示。

与服务端渲染相对的是客户端渲染(Client Side Render)。那什么是客户端渲染? 现在创建一个新的React项目,用脚手架生成项目,然后run起来。 这里你可以看到React脚手架自动生成的首页。

React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

然而打开网页源代码。

React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

body中除了兼容处理的noscript标签之外,只有一个id为root的标签。那首页的内容是从哪来的呢?很明显,是下面的script中拉取的JS代码控制的。

因此,CSR和SSR最大的区别在于前者的页面渲染是JS负责进行的,而后者是服务器端直接返回HTML让浏览器直接渲染。

为什么要使用服务端渲染呢?

React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

传统CSR的弊端:

  1. 由于页面显示过程要进行JS文件拉取和React代码执行,首屏加载时间会比较慢。
  2. 对于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代码来控制。于是服务端返回的代码变成了这样:

React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

有没有发现和之前的区别?区别就是多了一个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代码啦。

绑定事件完成!

现在来初步总结一下同构代码执行的流程:

React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

四.同构中的路由问题

现在写一个路由的配置文件:

// 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'))
复制代码

这时候控制台会报错,

React服务端渲染探秘:1.实现一个基础的React组件SSR、初识同构

因为在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中非常重要的部分。希望对大家有所帮助,也欢迎大家在评论区交流。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

理解专业程序员

理解专业程序员

(美)杰拉尔德·温伯格(GeraldM.Weinberg) / 刘天北 / 清华大学出版社 / 2006-7 / 25.00元

《理解专业程序员》通过行内专家的独特视角,介绍了如何成为优秀程序员,如何提高工作绩效等问题。全书由多篇讨论程序员职业的短文组成,内容精彩绝伦,是一部任何在这个变化急剧的领域工作的人都不可错过的重要作品。本书论述生动翔实——你肯定能从中认出你自己和你的公司的故事——因此不仅极富教益,而且读来也引人入胜。 各篇主题包括:对于专业程序员重要的若干问题,成为专业程序员的途径,在企业官僚体......一起来看看 《理解专业程序员》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码