之前写了两个 demo 讲解了如何实现 SSRSSG,今天再写个 demo 说在 ISR 如何实现。

什么是 ISR

ISRIncremental Static Regeneration 增量静态再生,是指在 SSG 的前提下,可以在收到请求时判定页面是否需要刷新,如果需要则重新构建该页面,这样既拥有了静态页面的优势又可以避免页面长时间未更新导致信息过时。且由于在页面维度验证,所以每次可以只构建特定的页面。

ISR 一般适用于符合 SSG 场景,但是却对页面的时限性有一定要求时。

如何实现

简单的 ISR 实现也很简单,只需要在收到页面请求时按照更新策略判断是否需要需要重新生成页面,如果需要触发页面的构建更新。需要注意一般情况下生成页面不会影响页面的响应,而是后台去做构建。

现在就基于之前写的 SSG demo,做一下改造让其支持 ISR

修改构建脚本

由于 ISR 构建会同时在构建脚本和服务器中触发,所以需要对之前的代码做一些小小的改动。

首先抽离出一个通用的构建函数(由于服务器会使用到尽量避免同步代码):

import fs from 'fs/promises';
import { renderToString } from 'react-dom/server';
import React from 'react';
import Post from './ui/Post';
import List from './ui/List'; async function build(type: 'list'): Promise<void>;
async function build(type: 'post', name: string): Promise<void>;
async function build(type: 'list' | 'post', name?: string) {
if (type === 'list') {
const posts = await fs.readdir('posts');
await fs.writeFile(
'dist/index.html',
`<div id="root">${renderToString(
<List
list={posts.map(post => {
delete require.cache['posts/' + post];
return { ...require('./posts/' + post), key: post.replace('.json', '') };
})}
/>
)}</div>`
);
} else {
delete require.cache['posts/' + name];
const postInfo = require('./posts/' + name);
const fileName = `dist/posts/${name}.html`;
await fs.writeFile(fileName, `<div id="root">${renderToString(<Post data={postInfo} />)}</div>`);
}
} export default build;

这样就可以通过 build 函数来构建指定的 post 或者 list 页面。

然后再将原先的构建脚本做一下简单的修改:

import fs from 'fs';
import build from './build-util'; // make sure the dir exists
if (!fs.existsSync('dist')) {
fs.mkdirSync('dist');
}
if (!fs.existsSync('dist/posts')) {
fs.mkdirSync('dist/posts');
} // get all the files in posts
const posts = fs.readdirSync('posts'); (async () => {
for await (const post of posts) {
await build('post', post.replace('.json', ''));
}
await build('list');
})();

服务器

由于 ISR 需要在请求时做是否构建的判定,所以原先的静态服务器方案无法继续使用,我们换成 express 来实现:

import express from 'express';
import path from 'path';
import fs from 'fs';
import build from '../build-util'; const app = express(); const expiresTime = 1000 * 60 * 10; app.use(function (req, res, next) {
setTimeout(() => {
const filename = req.path.indexOf('.html') >= 0 ? req.path : req.path + 'index.html'; // get the file's create timestamps
fs.stat(path.join('./dist', filename), function (err, stats) {
if (err) {
console.error(err);
return;
}
if (Date.now() - +stats.mtime > expiresTime) {
console.log(filename, 'files expired, rebuilding...');
if (filename === '/index.html') {
build('list');
} else {
build('post', path.basename(filename).replace('.html', ''));
}
}
});
}); next();
}); app.use(express.static('dist')); app.listen(4000, () => {
console.log('Listening on port 4000');
});

我们增加一个 express 的中间件,让其来判定文件是否过期,这里以十分钟为例,实际场景可按需定义过期判定。这里过期后就会调用 build 文件来重新构建该文件。要注意此处先返回再构建,所以用户不会等待构建,并且此次访问依旧是旧的内容,构建完成后访问的才是新的内容。

更多细节

  • 注意给构建任务加锁,避免一个页面过期后多个请求同时触发多个同样的构建任务
  • 给构建任务加队列,避免请求过多时同时出现过多的后台构建任务导致服务器资源问题
  • 可以为每个文件制定特定的过期判定条件,比如 post 源文件的修改时间等等

总结

ISR 对比 SSG 可以有效的控制页面的时效性,但也要付出额外的代价:

  • 需要额外的开发成本
  • 需要额外的服务器资源投入
  • 无法使用一般的静态文件服务器

没有最佳,只有最适合,所以实际场景下还是按需选用。

最后

本文的 demo 代码放置在 React ISR Demo 中,可自行取阅。

React ISR 如何实现 - 最后的 Demo的更多相关文章

  1. angular开发者吐槽react+redux的复杂:“一个demo证明你的开发效率低下”

    曾经看到一篇文章,写的是jquery开发者吐槽angular的复杂.作为一个angular开发者,我来吐槽一下react+redux的复杂. 例子 为了让大家看得舒服,我用最简单的一个demo来展示r ...

  2. 一个webpack,react,less,es6的DEMO

    1.package.json如下 { "name": "demo", "version": "1.0.0", " ...

  3. 学习react,动手实现一个小demo(仿知乎问答)

    学习react也有一周的时间,最近自己做了个仿知乎问答的小demo,项目源码在github上:https://github.com/yang302/reactQa 使用技术:bower+gulp+re ...

  4. 用react系列技术栈实现的demo整合系统

    引子 学生时代为了掌握某个知识点会不断地做习题,做总结,步入岗位之后何尝不是一样呢?做业务就如同做习题,如果‘课后’适当地进行总结,必然更快地提升自己的水平. 由于公司采用的react+node的技术 ...

  5. 【温故知新】—— React/Redux/React-router4基础知识&独立团Demo

    前言:React专注View层,一切皆组件:全部使用ES6语法,最新版本为React16. Redux是专注于状态管理的库,和react解耦:单一状态,单向数据流.[独立团github地址] 一.Re ...

  6. react介绍、环境搭建、demo运行实例

    React官网:https://reactjs.org/docs/create-a-new-react-app.html cnpm网址:http://npm.taobao.org/ 1.react介绍 ...

  7. 基于react实现无限分级菜单

    在开发CMS(内容管理系统)系统时,一般都会用到一个侧边栏或者顶部的二级或者三级菜单,当点击或者鼠标悬浮时,菜单能够随之展开或收起. 本文纯粹为了练习一下react,因此我会在react环境下实现这么 ...

  8. 使用React并做一个简单的to-do-list

    1. 前言 说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本<React:引领未来的用户界面开发框架 >拜读.React的轻量组件化的思想及其virtual ...

  9. React Native 项目运行在 Web 浏览器上面

    React Native 的出现,让前端工程师拥有了使用 JavaScript 编写原生 APP 的能力.相比之前的 Web app 来说,对于性能和用户体验提升了非常多. 但是 React Nati ...

  10. [转] 三步将你的 React Native 项目运行在 Web 浏览器上面

    React Native 的出现,让前端工程师拥有了使用 JavaScript 编写原生 APP 的能力.相比之前的 Web app 来说,对于性能和用户体验提升了非常多. 但是 React Nati ...

随机推荐

  1. python入门教程之十八正则表达式

    re.match函数 re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none. 函数语法: re.match(pattern, string, ...

  2. pandas之使用自定义函数

    如果想要应用自定义的函数,或者把其他库中的函数应用到 Pandas 对象中,有以下三种方法: 1) 操作整个 DataFrame 的函数:pipe() 2) 操作行或者列的函数:apply() 3)  ...

  3. 11.spring security 认证和授权简单流程了解

    1.总结:昨天主要是对WebSecurityConfigurerAdaptor的三个函数的区分以及了解了spring security的认证和授权流程:再就是动手使用了下thymeleaf和freeM ...

  4. stable diffusion打造自己专属的LORA模型

    通过Lora小模型可以控制很多特定场景的内容生成. 但是那些模型是别人训练好的,你肯定很好奇,我也想训练一个自己的专属模型(也叫炼丹-_-). 甚至可以训练一个专属家庭版的模型(family mode ...

  5. 虚拟机安装linux操作系统中IP设置

    问题描述:用虚拟机安装linux操作系统时,不选择分配IP,系统默认会分配动态IP,如果是临时搭建,就可以默认动态IP,也可以使用连接工具连接虚拟机.但如果是长期使用,选择使用固定的IP会更好,需要手 ...

  6. 特性介绍 | MySQL 测试框架 MTR 系列教程(一):入门篇

    作者:卢文双 资深数据库内核研发 去年年底通过微信公众号[数据库内核]设定了一个目标--2023 年要写一系列 特性介绍+内核解析 的文章(现阶段还是以 MySQL 为主). 虽然关注者很少,但本着& ...

  7. [MAUI]模仿网易云音乐黑胶唱片的交互实现

    @ 目录 创建页面布局 创建手势控件 创建影子控件 唱盘拨动交互 唱盘和唱针动画 项目地址 用过网易云音乐App的同学应该都比较熟悉它播放界面. 这是一个良好的交互设计,留声机的界面隐喻准确地向人们传 ...

  8. Costura.Fody 使用问题

    1. Costura.Fody 引用后,未能正常合并资源文件.用着用着就不行了 解决方案:在csproj所在的文件目录,找到FodyWeavers.xml,添加<Costura/> 1 & ...

  9. 使用Kepserver 自带 DataLogger 功能 实现工控数据转储关系型数据库

    本文以 Mysql数据库为例,介绍使用 kepserver 的datalogger 功能转储数据到 mysql 第一步:下载安装 Mysql ODBC 数据库驱动前往 官网下载ODBC驱动https: ...

  10. Win10系统Anaconda下tensorflow的GPU环境搭建

    我的环境:Win10 + Anaconda + tensorflow-gpu1.14 + CUDA10.0 + cuDNN7.6 + python3.6 注意:tensorflow版本.CUDA版本. ...