需求:让接口返回的文章根据请求一段一段的渲染,同时可以点击“停止生成”按钮后停止请求。
经历:使用纯css,vue-typed-js插件,ReadableStream 三种,虽然前两种并非很完美,但这种一步步排错并解决问题的思路在我看来更为重要。如果你急需判断是否有效果,也可以直接看第三步。
接口返回的数据:由于是一段一段渲染,所以是数组套对象,如下
,完整的句子是“我曾满怀激情地憧憬过,也曾经历了无数次失望和沮丧,但最终还是来到了这里。”
[
{
"header":{
"code":"200"
},
"chunk":"我"
},
{
"header":{
"code":"200"
},
"chunk":"曾满怀激情地"
},
{
"header":{
"code":"200"
},
"chunk":"憧憬过,也"
},
{
"header":{
"code":"200"
},
"chunk":"曾经历了无数次失望"
},
{
"header":{
"code":"200"
},
"chunk":"和沮丧,但最终还是"
},
{
"header":{
"code":"200"
},
"chunk":"来到了这里。"
}
]
由于内容较多,这里只会说一下思路,具体的内容会提供原文链接进行查看。
将所有的内容用p标签包裹,默认是隐藏的。然后加上动画,第一个0.5s显示,第二个1s显示,第三个1.5s显示…以此类推。
缺点:需要手动给n个p标签加上样式,而且内容要是固定的才可以,若返回的是不同数据就无法使用了。当然了,博客的title也写的很清除了,让文字一行一行出现或者显示。
参考文章:
css动画让文字一行一行出现: https://www.5axxw.com/questions/simple/dkog6u
css动画让文字一行一行逐渐显示: https://www.5axxw.com/questions/simple/aawr87
https://www.5axxw.com/questions/simple/vy1p34: https://www.5axxw.com/questions/simple/vy1p34
官网地址: https://github.com/Orlandster/vue-typed-js
npm install --save vue-typed-js
import Vue from 'vue'
import VueTypedJs from 'vue-typed-js'
Vue.use(VueTypedJs)
<vue-typed-js
:startDelay="1000"
:cursorChar="'|'"
:strings="[
'我',
'曾满怀激情地',
'憧憬过,也曾',
'经历了无数次失望和沮丧,',
'但最终还是',
'来到了这里。',
]"
:contentType="'html'"
>
</vue-typed-js>
字段详解:
startDelay 输入延迟时间,单位ms
cursorChar设置光标长什么样子
strings 一个数组,里面是要渲染的数据
contentType 输入的是文本还是html
没有什么坑的,直接安装注册使用即可。但这有一个严重问题我解决不了。
strings这个渲染的数组,如果你在vue中watch监听这个数据,每次改变的话,你会发现有旧值和新值,但如果是接口返回的数据,就只会返回数组的第一个对象的值,后面的不会返回拼接数据了,也就是strings在vue-typed-js组件中只会触发一次,
ReadableStream MDN 官网介绍: https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream
Stream API 中的 ReadableStream 接口表示可读的字节数据流。Fetch API 通过 Response 的属性 body (en-US) 提供了一个具体的 ReadableStream 对象。
我的理解:就是把接口的内容返回 base64 的字节,然后通过getReader()创建一个 reader,并将流锁定。只有当前 reader 将流释放后,其他 reader 才能使用。通过 cancel() 方法返回一个 Promise,这个 promise 会在流被取消的时候兑现。cancel 用于在不再需要来自它的任何数据的情况下(即使仍有排队等待的数据块)完全结束一个流。调用 cancel 后该数据丢失,并且流不再可读。
(1)你可以将以下代码通过设置了跨域的谷歌浏览器的打开
若你没有设置了跨域的谷歌浏览器,请查看我写的博客【谷歌(Chrome)浏览器的设置跨域】来创建一个跨域的浏览器:https://blog.csdn.net/weixin_44784401/article/details/131111077
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script>
fetch("https://www.example.org")
.then((response) => response.body)
.then((rb) => {
const reader = rb.getReader();
return new ReadableStream({
start(controller) {
function push() {
reader.read().then(({ done, value }) => {
if (done) {
console.log("done111", done);
controller.close();
return;
}
controller.enqueue(value);
console.log("done222", done, value);
push();
});
}
push();
},
});
})
script>
body>
html>
(2)效果
由上图可验证: reader.read().then(({ done, value })中的value表示ReadableStream可读的字节数据流。
(3)结合实际情况进行流式渲染数据的伪代码,我的是vue2 项目
// 注意:以下代码是伪代码,需要你结合自身需求去修改
let _this = this; // 存储this
let data = '';
fetch("https://www.example.org", {
method: 'POST',
body: JSON.stringify(data)
})
.then((response) => {
const reader = response.body.getReader(); // 创建一个读取器并将流锁定于其上。一旦流被锁定,其他读取器将不能读取它,直到它被释放
const decoder = new TextDecoder(); // TextDecoder 接口表示一个文本解码器,一个解码器只支持一种特定文本编码,例如 UTF-8、ISO-8859-2、KOI8-R、GBK,等等。解码器将字节流作为输入,并提供码位流作为输出。
console.log('000', response.body, decoder);
// 按顺序读取每个分块,并传递给 UI,当整个流被读取完毕后,从递归方法中退出,并在 UI 的另一部分输出整个流。
function read() {
// read() 返回了一个 promise
// done - 当 stream 传完所有数据时则变成 true
// value - 数据片段。当 done 为 true 时始终为 undefined
return reader.read().then(({ done, value }) => {
if (done) {
return;
}
let chunkData = ""; // 设置一个存储当前数组对象中数据的字符串
let chunkJson = JSON.parse(decoder.decode(value)); // decoder.decode(编码)返回一个字符串,其中包含使用特定 TextDecoder 对象的方法解码的文本。(就是将原本变成字节流后把它变成文本)
// 如果code不是200,说明请求失败,后面不执行
if (chunkJson.header.code != "200") {
return;
}
chunkData = JSON.parse(decoder.decode(value)).chunk;
_this.render.contentText += chunkData; // 请求成功拼接字符串
// reader.cancel()是reader停止请求的方法,停止请求后就不请求了(有一个停止生成的按钮,点击后就停止请求)
// render.loading 是判断是否正在请求的loading,以此来判断是否继续递归请求后面的数据
return _this.render.loading ? read() : reader.cancel();
});
}
return read();
})
ReadableStream 参考链接
(1)MDN ReadableStream : https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream
(2)MDN ReadableStream getReader() 方法:https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream/getReader
(3)MDN ReadableStream cancel() 方法:https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream/cancel
(4)MDN TextDecoder: https://developer.mozilla.org/zh-CN/docs/Web/API/TextDecoder