前端使用ReadableStream.getReader来处理流式渲染


前言

需求:让接口返回的文章根据请求一段一段的渲染,同时可以点击“停止生成”按钮后停止请求。
经历:使用纯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":"来到了这里。"
    }
]

一、纯css

由于内容较多,这里只会说一下思路,具体的内容会提供原文链接进行查看。
将所有的内容用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

二、vue-typed-js插件

官网地址: https://github.com/Orlandster/vue-typed-js

1.安装

npm install --save vue-typed-js

2.注册

import Vue from 'vue'
import VueTypedJs from 'vue-typed-js'
 
Vue.use(VueTypedJs)

3.使用

<vue-typed-js
   :startDelay="1000"
   :cursorChar="'|'"
   :strings="[
     '<span>我</span>',
     '<span>曾满怀激情地</span>',
     '<span>憧憬过,也曾</span>',
     '<span>经历了无数次失望和沮丧,</span>',
     '<span>但最终还是</span>',
     '<span>来到了这里。</span>',
   ]"
   :contentType="'html'"
 >
 </vue-typed-js>

字段详解:
startDelay 输入延迟时间,单位ms
cursorChar设置光标长什么样子
strings 一个数组,里面是要渲染的数据
contentType 输入的是文本还是html

总结

没有什么坑的,直接安装注册使用即可。但这有一个严重问题我解决不了。strings这个渲染的数组,如果你在vue中watch监听这个数据,每次改变的话,你会发现有旧值和新值,但如果是接口返回的数据,就只会返回数组的第一个对象的值,后面的不会返回拼接数据了,也就是strings在vue-typed-js组件中只会触发一次,

三、ReadableStream

ReadableStream MDN 官网介绍: https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream

1.ReadableStream是什么?

Stream API 中的 ReadableStream 接口表示可读的字节数据流。Fetch API 通过 Response 的属性 body (en-US) 提供了一个具体的 ReadableStream 对象。

2.ReadableStream做什么?

我的理解:就是把接口的内容返回 base64 的字节,然后通过getReader()创建一个 reader,并将流锁定。只有当前 reader 将流释放后,其他 reader 才能使用。通过 cancel() 方法返回一个 Promise,这个 promise 会在流被取消的时候兑现。cancel 用于在不再需要来自它的任何数据的情况下(即使仍有排队等待的数据块)完全结束一个流。调用 cancel 后该数据丢失,并且流不再可读。

3.ReadableStream怎么用

(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>Document</title>
</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

猜你喜欢

转载自blog.csdn.net/weixin_44784401/article/details/132206265
今日推荐