前端四大预请求方案!快来提高你的首屏加载速度吧~

首屏关键接口是影响首屏内容展示的关键因素,以react框架为例,大多数首屏请求都是放在useEffect内来发起的,从浏览器的性能检测工具来看,需要等待框架的静态资源加载完毕之后请求才能发起。这样可能导致白屏时间或者骨架屏过渡时间过程,影响实际内容的展示。本文将分享4种预请求的方案,供大家参考。

客户端预请求

webview初始化数据

如果h5页面是在客户端中,客户端打开一个h5是依托于webview的,webview的初始化是需要一定时间的。下图是美团APP做的数据调研

可以看出APP冷启动打开webview耗时的时间相对而言是很高的。那在这段时间中,我们可以并行去请求接口。

方案介绍

  • 客户端在初始化webview的同时,异步获取接口数据
scala 复制代码
// 客户端在初始化webview的同时,异步获取接口数据
public class WebViewTest extends AppCompatActivity {
    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 初始化 WebView
        webView = findViewById(R.id.webview);

        // 假设预请求react的github信息,请求完该接口时会缓存在内存中
        PreloadManager.getInstance().preloadUrl("https://api.github.com/repos/facebook/react");

        // 简单实现下jsbridge,addJavascriptInterface可以把java对象注入到webview里,让webview调用java方法
        webView.addJavascriptInterface(new WebAppInterface(this, webView), "nativeObject");

        // 加载网页
        webView.loadUrl("http://10.0.2.2:3001");
    }
}
  • h5通过jsbridge获取客户端缓存
javascript 复制代码
useAsyncEffect(() => {
  // jsbridge预请求
  const result = await window.BridgeNameSpace.preFetch({
    url: "https://api.github.com/repos/facebook/react",
  });
  window.BridgeNameSpace.showToast({ title: JSON.stringify(result) });
}, [])

流程可能变成这个样子,减少了一步请求的时间

如果页面不是在自己家客户端内打开,无法借助客户端能力,例如微信分享,外投广告这种场景,有别的方案吗?

ssr服务端渲染预请求

SSR

将组件在服务端直接渲染成 HTML 字符串,作为服务端响应返回给浏览器,最后在浏览器端将静态的 HTML"激活"(hydrate) 为能够交互的客户端应用。

可能带来更快的首屏加载

服务端渲染的 HTML 无需等到所有的 JavaScript 都下载并执行完成之后才显示,所以你的用户将会更快地看到完整渲染的页面。除此之外,数据获取过程在首次访问时在服务端完成,相比于从客户端获取,可能有更快的数据库连接 。这通常可以带来更高的核心 Web 指标、更好的用户体验,而对于那些"首屏加载速度与转化率直接相关"的应用来说,这点可能至关重要。

方案介绍

  • 服务端node代码:express结合react的api,收集挂载在App组件上的请求方法,然后在服务端请求,并注入到window上
ini 复制代码
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App.jsx';

const app = express();
app.use(express.static('public'));

app.get('*', async (req, res) => {
  // 1. 检查是否存在静态数据请求方法  
  let pageData = {};
  if (App.fetchData) {
    // 调用静态方法获取数据    
    pageData = await App.fetchData();
  }

  // 2. 渲染组件为字符串  
  const reactHtml = renderToString(<App data={pageData} />);

  // 3. 将数据注入到window对象  
  const injectedData = `
    <script>
      window.__INITIAL_DATA__ = ${JSON.stringify(pageData)};
    </script>
  `;

  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>SSR Data Injection</title>
      </head>
      <body>
        <div id="root">${reactHtml}</div>
        ${injectedData}
        <script src="/client.js"></script>
      </body>
    </html>
  `;

  res.send(html);
});

app.listen(3000);
  • 客户端取服务端注入在window上的接口数据渲染界面
javascript 复制代码
// ------------------ 客户端代码 client.js ------------------
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App.jsx';

// 从window获取服务端注入的数据
const initialData = window.__INITIAL_DATA__ || {};

hydrateRoot(
  document.getElementById('root'),
  <App data={initialData} />
);

// ------------------ 组件 App.jsx ------------------
export default function App({ data }) {
  return (
    <div>
      <h1>User Data:</h1>
      <p>Name: {data.name || 'Loading...'}</p>
      <p>Age: {data.age || ''}</p>
    </div>
  );
}

// 静态数据请求方法(服务端会调用)
App.fetchData = async () => {
  // 模拟API请求
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        name: 'Alice',
        age: 28
      });
    }, 100);
  });
};

流程可能变成这个样子

那没有ssr,只有csr,有别的方案吗?

边缘函数(ER)预请求

CDN

内容分发网络(Content Delivery Network,CDN)是建立并覆盖在承载网上,由不同区域的服务器组成的分布式网络。将源站资源缓存到全国各地的边缘服务器,供用户就近获取,降低源站压力。

CDN将源站资源缓存到阿里云遍布全球的加速节点,当终端用户请求访问和获取源站资源时无需回源,可就近获取CDN节点上已经缓存的资源,提高资源访问速度,同时分担源站压力。目前CDN部分节点已支持通过IPv6访问。

边缘计算

基于遍布全球的节点,DCDN提供了智能弹性的计算和存储服务,即边缘函数和边缘存储。您可以将在线服务或轻量应用直接部署至全球边缘节点,就近处理客户端的请求,以获得更低的延迟。同时,您无需再运维服务器资源,Serverless将自动为您分配足够的计算和存储资源。

边缘函数(EdgeRoutine,简称ER)是一项基于Serverless架构的服务,它允许开发者编写JavaScript代码并在阿里云全球边缘节点上部署和执行,支持ES6语法和标准的Web Service Worker API。通过这种技术,用户的请求可以直接在离用户最近的边缘节点上得到响应处理,从而显著减少延迟、提高响应速度,并实现更低时延的计算体验。

方案介绍

  • html接入时按照约定自定义一个html标签,来标识需要er来处理,attr来代表参数,例如:
ini 复制代码
<x-er type="prefetch" url="https://api.github.com/repos/facebook/react" />
  • er程序,HTMLStream流式传输
javascript 复制代码
addEventListener('fetch', (event) => {
  event.respondWith(handle(event));
});

async function handle(event) {
  // 1.假设exmaple页面返回的是待修改的HTML页面。
  const response = await fetch("http://www.example.com");
  let { readable, writable } = new TransformStream();
  const htmlStream = new HTMLStream(
    response.body, // 放入需要改写的HTML流
    [[
      "x-er",         // 元素选择器,表示选择所有的`x-er`标签。
      {  
        // 注册回调函数对象,名为element的回调函数会在x-er标签(在DOM中为ElementNode)被调用。
        // 调用时可以传入object来更改e的信息。
        element: function(e) {
          // 更改属性href
          const url = e.getAttribute("url");
          fetch(url).then((result) => {
            // 2. 通过流式传输结果
          })
        }
      }
    ]]);

  // 3.返回修改后的请求到浏览器。HTMLStream是个ReadableStream,所以任何能使用
  //    ReadableStream的地方均可使用HTMLStream。
  return new Response(htmlStream);
}

修改后的流程如下:

没有cdn,有别的方案吗?

文档解析head预请求

方案介绍

javascript 复制代码
// head里的script
<script>
  fetch("https://api.github.com/repos/facebook/react")
  .then((res) => res.json())
  .then((data) => {
    window["head-request-facebook/react"] = data;
    window.dispatchEvent(
      new CustomEvent("head-request", {
        detail: {
          url: "https://api.github.com/repos/facebook/react",
          data,
        },
      })
    );
  });
</script>

// 请求
function headPrefetch(url) {
  return new Promise((resolve) => {
    const listener = (e) => {
      const customEvent = e;
      if (customEvent.detail?.url === url) {
        resolve(customEvent.detail?.data);
      }
    };
    window.addEventListener("head-request", listener, {
      once: true,
    });

    const customWindow = window;
    if (customWindow[url]) {
      resolve(customWindow[url]);
    }
  });
}

修改后的流程如下:

总结

上述方案只是简单示例,每种方案都需要再仔细思考下细节和边界条件,比如:客户端预请求如果失败了怎么办?缓存什么时候清空?ssr如果首屏接口耗时长,白屏时间会不会很长?边缘请求登录态怎么带?每个案例可以细化成一个解决方案,抛转引玉,欢迎大家提出更多的方案

相关推荐
Stestack3 分钟前
Python 给 Excel 写入数据的四种方法
前端·python·excel
SRC_BLUE_176 分钟前
[Web 安全] PHP 反序列化漏洞 —— PHP 序列化 & 反序列化
前端·安全·web安全·php
IT猿手28 分钟前
智能优化算法:雪橇犬优化算法(Sled Dog Optimizer,SDO)求解23个经典函数测试集,MATLAB
开发语言·前端·人工智能·算法·机器学习·matlab
windyrain1 小时前
基于 Ant Design Pro 实现表格分页与筛选参数的持久化
前端·javascript·react.js
懒人村杂货铺2 小时前
父子组件事件冒泡和捕获的顺序
前端·javascript·react.js
小刘不知道叫啥2 小时前
React 源码揭秘 | 更新队列
前端·react.js·前端框架
录大大i2 小时前
HTML之JavaScript DOM操作元素(1)
前端·javascript·html
huangkaihao2 小时前
无限滚动优化指南:从原理到实践
前端·面试·设计
橘猫0.o2 小时前
【C语言】结构体字节对齐
linux·c语言·前端·数据结构·单片机·嵌入式硬件·算法