react ssr 服务端渲染入门
前言
为什么要用服务端渲染?
- 加快首屏渲染,减少白屏时间
与传统的web项目直接获取服务端渲染好的HTML不同,单页面应用使用JavaScript在脚本客户端生成HTML来呈现内容,用户需要等待JS解析执行完成后才能看到页面,这就使得白屏加载时间变长,影响用户体验。
- SEO 友好
对于单页面应用,当搜索引擎的爬虫爬取网站HTMl文件时,通常情况下单页面应用中没有任何的内容,仅有<div id="root"> </div>
这么一句话,从而影响排名。
因此,业界借鉴传统的服务端渲染的方案,提出在服务端执行前端框架(React/Vue/Angular)代码生成HTML,然后将渲染好的HTML直接返回给客户端。
[图片引自 www.jianshu.com/p/a3bce57e7…
技术原理
以React为例,首先我们让React代码在服务端执行一次,使得用户下载的HTML已经包含了所有的页面展示内容(达到新增SEO的目的)。同时,用户不需要等到JavaScript代码全部执行完就可以看到页面效果,增强用户体验。之后,我们让React在客户端再次执行,为HTML页面中的内容添加数据及事件的绑定,页面就具备了React的各种交互能力。
核心API
服务端:使用 ReactDOMServer.renderToString | ReactDOMServer.renderToNodeStream 生成HTML,并且在首次请求下发。
客户端:使用 ReactDOM.hydrate 根据服务端返回的HTML进行 hydrate 操作。React 会尝试在已有标记上绑定事件监听器(从服务端返回的HTMl是不带任何事件的)。
在SSR项目中渲染组件
技术栈: React + Koa2 + Webpack
1. 使用koa搭建服务端环境
新建文件夹,并且初始化项目
mkdir ssr-demo && cd ssr-demo
npm init -y
安装Koa环境
cnpm install --save koa
在项目根目录创建app.js,监听8888端口,当请求根目录时,返回一些HTML
// app.js
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
ctx.body = `
<html>
<head>
<title>ssr demo</title>
</head>
<body>
<div style="color: red"> Hello World </>
</body>
</html>
`
})
app.listen(8888);
console.log('app is starting at port 8888');
在终端输入命令启动服务 node app.js
访问本地的 http://localhost:8888/,可以看到HTMl返回了。
2.在服务端编写React代码
我们已经启动了一个Node服务器,下一步我们需要在服务器上编写React代码(也可以是Vue或者是其他框架语言),我们创建一个React组件,并且在App中返回。
安装React环境,创建src/components文件夹,新建home.js文件
cnpm install --save-dev React
mkdir src && cd src && mkdir components && cd components && touch home.js
用jsx编写一个最简单的React组件
import React from 'react'
const home = () => {
return <div> This is a React Component</div>
}
export default home;
并且在app.js中引用
const Koa = require('koa');
const { renderToString } = require('react-dom/server');
const Home = require('./src/components/home');
const app = new Koa();
app.use(async (ctx) => {
ctx.body = renderToString(<Home />)
})
app.listen(8888);
console.log('app is starting at port 8888');
然而这段代码并不会成功运行成功。原因如下
-
当前是在Node环境下,Node不能识别import和export。这二者属于ESM语法,而Node遵循的是common.js规范
-
Node不能识别JSX语法
为了方便起见,我们直接使用Babel的@babel/preset-env和@babel/preset-react预设。Babel的预设是一系列插件的集合。包括了用来转化成Commonjs的 babel-plugin-transform-modules-commonjs 插件,也包括了转化JSX的 babel-plugin-transform-react-jsx 等一系列插件)
@babel/register
这是 koa 官方给出的@babel/register使用方法,当我们引入了@babel/register之后,就会在require方法中注入Babel钩子,并且在运行时进行即时编译。
require('babel-core/register');
// require the rest of the app that needs to be transpiled after the hook
const app = require('./app');
所以需要对我们目前的内容进行改造
app.js
require('@babel/register');
const app = require('./server').default;
app.listen(8888);
console.log('app is starting at port 8888');
新建server.js
const Koa = require('koa');
import React from 'react';
const { renderToString } = require('react-dom/server');
const Home = require('./src/components/home').default;
const app = new Koa();
app.use(async (ctx) => {
ctx.body = renderToString(<Home />)
})
app.listen(8001);
console.log('app is starting at port 8888');
export default app;
在项目根目录上创建babel的配置文件 babel.config.js
module.exports = function(api) {
api.cache(true);
return {
presets: [
['@babel/preset-env', {
targets: {
node: true,
},
modules: 'commonjs',
useBuiltIns: 'usage',
corejs: { version: 3, proposals: true },
}],
'@babel/preset-react',
],
}
}
当然,别忘记了安装相关的依赖
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react @babel/register react-dom
大功告成! 运行 node app.js
3.同构的概念
通过上面的例子,我们已经能够将React组件渲染到页面。为了讲明白同构的概念,下面我们为组件绑定一个点击事件。
import React from 'react';
const Home = () => {
return <div> This is a React Component
<button onClick={()=>{alert('click')}}>click</button>
</div>
}
export default Home;
重新运行代码,刷新页面(由于我们的工程里没有集成热更新,所以每次修改还需要重启并且刷新页面)。我们会发现,点击按钮后onClick事件并不会执行。这是因为renderToString()方法只渲染了组件的内容,并不会绑定事件(DOM的宿主是浏览器)。因此我们需要将React代码在服务端执行一遍,在客户端再执行一遍,这种服务端和客户端公用一套代码的方式就称之为同构。
4.在客户端执行React代码
之前我们说过,React代码在服务端执行的时候只能返回HTML页面,但是不具备任何交互。需要我们将React代码在客户端重新再执行一遍,确保页面能响应onClick等事件。React提供了 hydrate 方法。
ReactDOM.hydrate(element, container[, callback])
新建client-ssr.js
import React from 'react'
import {hydrate} from 'react-dom'
import Home from './src/components/home'
hydrate(
<Home />,
document.getElementById('app')
)
新建template.js
export default function template(content = "") {
let page = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<div id="app">
${content}
</div>
<script src="/asset/client-ssr.js"></script>
</body>
`;
return page;
}
修改server.js 将template的内容作为结果返回
const Koa = require('koa');
import React from 'react';
import template from './template';
const { renderToString } = require('react-dom/server');
const Home = require('./src/components/home').default;
const app = new Koa();
app.use(async (ctx) => {
ctx.body = template(renderToString(<Home />))
})
app.listen(8001);
console.log('app is starting at port 8888');
export default app;
重启,发现报错了!
由于我们的client-ssr中是React的JSX语法,直接返回给浏览器是解析不了的,需要用babel解析成浏览器能识别的JavaScript。
webpack打包
安装webpack依赖和命令行工具
cnpm install --save-dev webpack webpack-cli
新建webpack配置文件,另外我们需要安装babel-loader去解析我们的JSX语法
const path = require('path');
module.exports = {
mode: 'development',
entry: {
client: './client-ssr',
},
output: {
path: path.resolve(__dirname, 'asset'),
filename: "[name].js"
},
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
}
终端运行 npx webpack,打包client-ssr.js文件。
使用koa-static指定根目录
到目前为止,我们的文件已经准备好了。但是在访问的时候会发现,我们在template.js中的/asset/client.js
文件并没有拿到。所以我们需要告诉当前服务,项目的根目录在哪里,服务端才能正确返回我们需要的文件,这里使用 koa-static 中间件。
修改server.js
import Koa from 'koa';
import serve from 'koa-static';
import path from 'path';
import React from 'react';
import template from './template';
import { renderToString } from 'react-dom/server';
import Home from './src/components/home';
const app = new Koa();
app.use(serve(path.resolve(__dirname)));
app.use(async (ctx) => {
ctx.body = template(renderToString(<Home />))
})
export default app;
重新启动服务器,点击click按钮,成功了!再次在客户端渲染后,我们的页面能正常相应click事件了。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!