React ISR 如何实现 - 最后的 Demo
之前写了两个 demo 讲解了如何实现 SSR 和 SSG,今天再写个 demo 说在 ISR 如何实现。
什么是 ISR
ISR 即 Incremental 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的更多相关文章
- angular开发者吐槽react+redux的复杂:“一个demo证明你的开发效率低下”
曾经看到一篇文章,写的是jquery开发者吐槽angular的复杂.作为一个angular开发者,我来吐槽一下react+redux的复杂. 例子 为了让大家看得舒服,我用最简单的一个demo来展示r ...
- 一个webpack,react,less,es6的DEMO
1.package.json如下 { "name": "demo", "version": "1.0.0", " ...
- 学习react,动手实现一个小demo(仿知乎问答)
学习react也有一周的时间,最近自己做了个仿知乎问答的小demo,项目源码在github上:https://github.com/yang302/reactQa 使用技术:bower+gulp+re ...
- 用react系列技术栈实现的demo整合系统
引子 学生时代为了掌握某个知识点会不断地做习题,做总结,步入岗位之后何尝不是一样呢?做业务就如同做习题,如果‘课后’适当地进行总结,必然更快地提升自己的水平. 由于公司采用的react+node的技术 ...
- 【温故知新】—— React/Redux/React-router4基础知识&独立团Demo
前言:React专注View层,一切皆组件:全部使用ES6语法,最新版本为React16. Redux是专注于状态管理的库,和react解耦:单一状态,单向数据流.[独立团github地址] 一.Re ...
- react介绍、环境搭建、demo运行实例
React官网:https://reactjs.org/docs/create-a-new-react-app.html cnpm网址:http://npm.taobao.org/ 1.react介绍 ...
- 基于react实现无限分级菜单
在开发CMS(内容管理系统)系统时,一般都会用到一个侧边栏或者顶部的二级或者三级菜单,当点击或者鼠标悬浮时,菜单能够随之展开或收起. 本文纯粹为了练习一下react,因此我会在react环境下实现这么 ...
- 使用React并做一个简单的to-do-list
1. 前言 说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本<React:引领未来的用户界面开发框架 >拜读.React的轻量组件化的思想及其virtual ...
- React Native 项目运行在 Web 浏览器上面
React Native 的出现,让前端工程师拥有了使用 JavaScript 编写原生 APP 的能力.相比之前的 Web app 来说,对于性能和用户体验提升了非常多. 但是 React Nati ...
- [转] 三步将你的 React Native 项目运行在 Web 浏览器上面
React Native 的出现,让前端工程师拥有了使用 JavaScript 编写原生 APP 的能力.相比之前的 Web app 来说,对于性能和用户体验提升了非常多. 但是 React Nati ...
随机推荐
- python入门教程之十函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可以自己创建函数,这 ...
- [UML]PlantUML安装使用指南
1 概述 PlantUML 支持在多个平台上安装使用,比如 Eclipse,NetBeans,oneline servlet 等,它也支持多种语言的编辑,例如 C/C++, PHP,Java ...
- 阿里版ChatGPT:通义千问pk文心一言
随着 ChatGPT 热潮卷起来,百度发布了文心一言.Google 发布了 Bard,「阿里云」官方终于也宣布了,旗下的 AI 大模型"通义千问"正式开启测试! 申请地址:http ...
- XXL-JOB定时任务框架(Oracle定制版)
特点 xxl-job是一个轻量级.易扩展的分布式任务调度平台,能够快速开发和简单学习.开放源代码并被多家公司线上产品使用,开箱即用.尽管其确实非常好用,但我在工作中使用的是Oracle数据库,因为xx ...
- 【SpringMVC】(三)
HTTPMessageConverter HttpMessageConverter报文信息转换器,将请求报文转换为java对象,或将java对象转换为响应报文. 1 @ResquestBody Res ...
- 在Jupyter Notebook,沉浸式体验ChatGPT
大家好,我是章北海mlpy 写代码,修Bug是 ChatGPT 目前最擅长的领域之一 今天向大家推荐一个刚刚开源的Python包 安装后可以直接在IPython和Jupyter Notebook中直接 ...
- MySQL 主从延迟的常见原因及解决方法
承蒙大家的支持,刚上市的<MySQL实战>已经跃居京东自营数据库图书热卖榜第 1 名,收到的反馈也普遍不错.对该书感兴趣的童鞋可通过右边的链接购买.目前,京东自营有活动,只需 5 折. 主 ...
- css 利用 linear-gradient 实现条纹背景
1. 水平条纹背景 当给背景设置渐变效果时,默认的渐变方向是垂直由上到下的,效果如下: { background: linear-gradient(#aaa, #ddd); } 尝试拉近色标的距离,会 ...
- linux syslog.d日记操作记录-小节
以下记录在学习LDD3时调试处理打印的一些操作 syslog 不同的发行版,不同的脚本文件,如fedora18中为rsyslog的名称 1:配置文件 /etc/syslog.conf(fedora r ...
- vCenter报错:Log Disk Exhaustion on 10
vCenter报错:Log Disk Exhaustion on 10 1.问题现象: 巡检时发现 vCenter Server 中,错误显示为:Log Disk Exhaustion on 10(字 ...