Lmagic16/blog

从零开始一步步搭建 React+Koa SSR 应用

Opened this issue · 0 comments

从零开始一步步搭建 React+Koa SSR 应用

真正地从零开始,一步一步搭建~ 笔记有点啰嗦有点长...

首先,来拆分一下目标,每一步都是可运行的项目。

demo地址:https://github.com/Lmagic16/react-koa-ssr

每一步都是一个分支,checkout 到对应的分支即可体验到对应的 demo。

  1. 新建一个前端仓库,引入 React demo
  2. 配置 webpack 构建,本地可访问构建好的 html 文件(CSR 客户端渲染)
  3. 引入 Koa demo,本地可访问 Node 服务
  4. 将 React demo 组件在 Node 环境中渲染,本地访问可直接返回已渲染好的完整 html(SSR 渲染)
  5. 增加服务端 AJAX 数据请求(数据同构)
  6. 增加需要校验 cookie 的 ajax 数据请求(axios的cookie设置和格式问题)
  7. 针对有 Redux 状态管理的配置
  8. 针对有 React Router 路由(路由同构)

思考:Next.js 是如何实现 SSR 的?

前言

在搭建 SSR 应用之前,先来分析下 CSR 与 SSR,看看两者之间的区别与适用场景。以及简单介绍下 SSR 的思路。

1. 什么是 CSR 客户端渲染?

CSR 即 client side render,用户访问时通过浏览器发出 http 请求,服务器会返回一个HTML DOCUMENT 文件。这里从服务端拿到的 HTML 只是一个简单的模板,页面的主要内容是通过加载 JS 后执行 JS 来渲染的。这里的服务器只要能返回静态 HTML 资源就可以了,无需做其他额外的工作,apach 或者 Nginx 都可以。

2. 什么是 SSR 服务端渲染?

SSR 即 serverless side render,这里不同于 CSR 的是,从服务端拿到的 HTML 是已经渲染好的完整的 HTML。

![image-20200627204411810](/Users/qianyuliu/Library/Application Support/typora-user-images/image-20200627204411810.png)

3. SSR 大致的思路?

SSR 的大致思路就是利用 Koa 搭建一个 Node 服务器,在接收到 http 请求后,在服务端通过 axios 库拉取 ajax 数据,将数据通过 Props 方式传入 React 组件,然后通过 React 提供的 Rendertostring 方法将 React 组件渲染为 html 字符串的形式,最后拼装好完整的 html 文档并返回。当前页面还有一些交互的逻辑,只有 html 还不行,还需要加载 JS ,这个 JS 主要是用于绑定交互事件的,让页面可交互。使用 React.hyd 方法可以标记服务端返回的 html 标签,不用重新渲染,只是执行事件绑定。

4. 项目的技术栈和依赖模块

  • React16:前端 UI 框架

  • Koa2:服务器端 Node 框架

  • Webpack4:静态模块打包工具

  • Babel7:编译器,用于转换 JS 和 JSX 语法

一、新建 React 项目(CSR)

目标:实现一个可以客户端渲染的 React demo。
实现方式:引入 react,配置 webpack、babel。
所在分支:feature/react

  1. 从头开始打造工具链,在空文件夹 react-koa-ssr 下安装各依赖模块:
 $ npm init  // 初始化项目
 $ npm install --save react react-dom
 $ npm install --save-dev webpack webpack-dev-server webpack-cli html-webpack-plugin
 $ npm install --save-dev commitizen // 安装 commitizen 工具
 $ npm install --save-dev babel-loader @babel/preset-react @babel/core @babel/preset-env
  1. 编写 react 组件

    新增 src 文件夹,在 src 文件夹新增 App.jsx 和 index.jsx 文件, 参考<https://zh-hans.reactjs.org/docs/hooks-intro.html >编写 react hook 组件 demo。

  2. babel 配置

    新增 .babelrc 文件配置 babel。可参考官网进行配置: https://babeljs.io/

其中 Babel 能够转换 JSX 语法,将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法。babel-loader 会允许你使用 Babel 和 webpack 转译 JavaScript 文件。

  1. webpack 配置

    新增webpack.config.js文件,进行 webpack 构建配置。可参考:https://webpack.js.org/

    配置项 entry 为打包的入口,也是起点。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

    配置项 output 为打包的输出,可以配置打包的结果放到哪个路径下以及命名。

    配置项 mode 可以选择 development 或者 production,webpack 有内置相应环境下的优化。

    配置项 devServer 是 webpack-dev-server 模块的配置项,相当于一个本地静态资源服务器,可以配置本地服务器的资源根目录。

    配置项 module 可以配置如何解析各种文件类型。webpack 开箱自带的能力只有理解 JavaScript 和 JSON 文件,其余的文件类型需要通过 loader。针对 jsx 文件,我们采用 babel-loader。

    配置项 plugins

  2. 其他配置

    package.json 文件中配置 commitizen,使得 commit 提交规范化。具体可参考:https://juejin.im/post/5afc5242f265da0b7f44bee4

    新增 .gitignore文件忽略某些文件的提交。

二、引入 Koa,返回服务端渲染的 html(SSR)

目标:实现一个本地启用 koa Node 服务,该服务可提供静态资源访问和服务端渲染,在服务端渲染 React 组件后返回对应的 html。
实现方式:引入 koa
所在分支:feature/koa

  1. 安装相关依赖
$ npm install --save koa koa-router koa-static
$ npm install --save nodemon
$ npm install --save @babel/register

koa: 基于 Node.js 的 web 开发框架

nodemon:node服务自动重启工具,可以监听代码文件的变动,当代码改变之后,自动重启。

koa-router:服务端路由

koa-static:通过 koa-static 中间件加载某个目录下的静态资源。感觉和 webpack-dev-server 的功能类似。

koa-mount(暂未使用):koa-mount 是将 koa-static 中的静态资源挂载到某个特殊路由上。

@babel/register : 实时转码,具体见 https://www.babeljs.cn/docs/babel-register

  1. 服务端渲染 react 组件

1)将 src 目录下的 index.js 文件重命名为 client.js git mv ./src/index.js ./src/client.js,webpack.config.js 中的构建入口也同步修改下。

2)将模板 html 代码中添加一个 DOM 节点 <div id="server-side-render"></div>,用于服务端渲染替换。

3)在 src 目录下新建一个 server.js 文件,该文件核心内容(完整内容在分支 feature/koa 中):

async function getServerHtml() {
  const appHtml = ReactDOMServer.renderToString(<App />) //渲染HTML
  const file = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf8'); //先去读取index.html的内容
  const serverHtml = file.replace('<div id="server-side-render"></div>', appHtml); //然后将index.html里面的特殊字段用react渲染好的dom字符串替换
  return serverHtml;
}
// 路由规则
let router = new Router();
router.get('/', async (ctx) => {
  ctx.body = await getServerHtml();
});

4)将文件 src/client.js 中的 ReactDOM.render 方法替换为 ReactDOM.hydrate。

5)在根目录下新增文件 index.js,使用 @babel/register 进行实时转码。

require("@babel/register");
require( "./src/server" );
  1. 运行

$ npm run build构建浏览器端运行所需的 bundle.js,$node index.js开启本地 koa node服务。

React 提供了在 Node 端使用的 ReactDOMServer 对象,其 renderToString() 方法可以将 React 组件渲染为初始 html,返回 html 字符串。我们在服务端使用 ReactDOMServer.renderToString() 方法生成 html,这里的 html 在渲染的时候 DOM 节点会打上标记 data-reactroot。如果在浏览器端在被标记的节点上调用 ReactDOM.hydrate() 方法渲染组件,React 将会保留该节点且只进行事件处理绑定,从而让你有一个非常高性能的首次加载体验。

具体可参考 ReactDOMServer 介绍:https://zh-hans.reactjs.org/docs/react-dom-server.html

效果:

![image-20200630210839205](/Users/qianyuliu/Library/Application Support/typora-user-images/image-20200630210839205.png)

这里需要注意 node 端没有 document、window 对象,如果组件用到了这些对象会报错,需要组件内做适配,另外组件的生命周期里的代码在 node 端不会执行,只会执行初始渲染。

三、服务端 ajax 请求数据(数据同构)

目标:在第二步 koa Node 服务返回 React 渲染后的 html 的基础上,增加服务端 ajax 数据请求。
实现方式:引入 axios 异步请求数据 + cookie 传递 + 数据注水/脱水
所在分支:feature/axios-server

客户端请求数据的版本所在分支:feature/axios-client

上面的是极简版的 demo,没有和服务器的 ajax 数据请求。而一般的页面是需要通过 ajax 请求数据来渲染页面的,在 CSR 渲染时我们一般是在 componentDidMount 生命周期里去请求数据,那么 SSR 里是需要在 node 端去请求数据然后渲染返回 html。 axios 和 isomorphic-fetch 都可以在浏览器环境和 node 环境运行。我们选择 axios 库。

  1. 安装相关依赖

    $ npm install --save axios
  2. 先在 feature/axios-client 分支实现一下 客户端 axios 数据请求。这里我们拿 github api 进行测试。主要代码如下,完整代码在分支 feature/axios-client。

  const [repoData, setRepoData] = useState([]);

  // https://api.github.com/ 非AUTH认证请求会有限速,若希望不限速可以通过后面拼接参数走 OAuth2 key/secret 认证方式,拼接的参数为:client_id=YOUR-CLIENT-ID&client_secret=YOUR-CLIENT-SECRET
  const getGithubRepos = () => {
    axios({
      method: 'get',
      url: 'https://api.github.com/users/Lmagic16/repos',
    }).then(function (response) {
        setRepoData(response.data);
      });
  }

  useEffect(() => {
    getGithubRepos();
  }, []);
  1. 这一步将客户端的数据请求挪到服务端来。主要代码如下,完整代码在分支:feature/axios-server

![image-20200704215659999](/Users/qianyuliu/Library/Application Support/typora-user-images/image-20200704215659999.png)

数据注水 + 数据脱水

cookie 如何传递?

服务器端渲染获取数据

异步数据服务器渲染 : loadData方法

参考:

axios 用法: https://github.com/axios/axios

github REST API:https://docs.github.com/en/rest

四、Redux 状态管理的配置

创建Store代码的复用

构建Redux代码结构

五、路由同构

服务器端渲染中的路由

多页面路由跳转

使用Link标签串联起整个路由流程

多级路由问题的处理

六、serverless side render(Serverless + SSR)

接入腾讯云函数部署

接入docker部署