h5数据预取方案

背景

在页面的性能优化中,资源加载是决定页面展示时间的重中之重。为了减少资源的请求,有两个常见的解决方案:

  1. 一个是SSR服务端渲染页面,这样可以减少页面的初始化数据请求,页面在服务端接受到请求时进行数据请求与渲染页面,最终返回给客户端一个可以直接展示的html页面(直出)。但这个方案需要服务器的支持。
  2. 另一方是SSG服务端生成页面。与SSR的区别是,SSG在构建时就会生成html文件,后续的数据获取需要客户端加载了页面资源后再进行。SSG的优势是首屏加载速度快,可以直接分发资源到CDN,减少服务器压力。更进一步可以使用离线包方案,客户端使用本地资源打开页面,最大化减少网络的影响。

目前我们采用了第二种方案:SSG与离线包相结合。但在动态页面中,页面快速展示后,可以看到明显的页面卡顿,这是由于页面需要从服务端获取动态数据。本文便是为了解决这一问题。

期望效果

期望能结合目前的离线包方案在页面展示时,没有卡顿,能直接获取到动态数据并展示。

注意事项:

  1. 目前项目中存在离线包与在线包两种环境的页面,离线包方式是通过客户端代理页面的方式实现,两个包的域名不同,所以在获取动态数据时,需要注意不同包的数据获取。
  2. 部分页面的数据与当前时间状态深度绑定,这种情况下应避免得页面预取数据与页面展示相隔太久。

解决方案

  1. 例如百度(CloudHybrid)、腾讯(VasSonic)等大厂都在公司内部研发了相应的框架,特点是客户端在请求页面时,提前缓存页面与请求服务端获取动态数据,再根据获取到的数据与缓存页面进行水合。但该方案需要全端配合,复杂度较高。
  2. 最简单的方案就是在客户端初始化时,后台加载一个预取数据页面,这个页面会将数据存储起来,后续前端使用时直接调用。这个方案大幅前置了数据预取的过程,所以在页面首屏加载后,仍需要再次请求接口更新数据,防止期间数据变动,且发生数据变动时会导致页面数据闪动。

由于人力与成本考虑,项目使用第二种方案,下面介绍详细流程:

  1. 客户端在用户打开APP,初始化应用时,在后台加载预取数据页面。
  2. 预取数据页面从服务端获取动态数据。
  3. 将数据存储至本地缓存。这里建议使用localforage库,该库会自动选择IndexedDB、WebSQL、localStorage中的一种存储方式,且自动进行类型转换。
  4. 进入应用后,用户访问功能页面。
  5. 页面先使用本地缓存数据展示页面。这里注意由于页面使用ssg的方案,如果展示加载的骨架屏,依然会出现数据从无到有的闪动出现。所以需要直接将页面首屏置空,等js加载完成获取到本地数据后再显示页面(使用useEffect触发)。
  6. 由于动态数据可能更新,所以在页面加载后,需要再次请求服务端接口,更新页面(如数据变动,会造成页面闪动)。
  7. 最后将更新后的数据再次存入本地存储。
h5数据预取方案.png

参考

alloyteam - h5秒开方案大全

百度APP-Android H5首屏优化实践

附录

// 结合localforage的hooks
import { useCallback, useEffect, useRef, useState } from 'react';
import localforage from 'localforage';

let EventMap = new Map<String, Function[]>();
class EventEmitter {
  static on<T>(key: string, callback: (value: T) => void) {
    if (EventMap.has(key)) {
      EventMap.get(key)?.push(callback);
    } else {
      EventMap.set(key, [callback]);
    }
    return () => {
      const funcList = EventMap.get(key);
      EventMap.set(
        key,
        funcList!.filter((func) => func !== callback),
      );
    };
  }
  static emit<T>(key: string, value: T) {
    if (EventMap.has(key)) {
      EventMap.get(key)?.forEach((func) => {
        func(value);
      });
    }
  }
}

export function useLocalStorage<T>(
  key: string,
  defaultValue: T,
  pathname?: string,
) {
  const refKey = useRef(key);
  const refInit = useRef(false);

  function getStoredValue() {
    return new Promise<T>((resolve) => {
      localforage
        .getItem(refKey.current)
        .then((raw) => {
          if (typeof raw !== 'undefined' && raw !== null) {
            resolve(raw as T);
          } else {
            resolve(defaultValue);
          }
        })
        .catch((e) => {
          console.error(e);
          resolve(defaultValue);
        });
    });
  }

  const [state, setState] = useState<T>(defaultValue);
  const [initSetList, setInitSetList] = useState<Function[]>([]);

  useEffect(() => {
    const path = pathname || location.pathname.replace(/\//g, '_');
    refKey.current = `${path}-${key}`;
    getStoredValue().then((value) => {
      setState(value);
      if (initSetList.length) {
        initSetList.forEach((func) => func());
      }
    });
    refInit.current = true;
  }, [key]);

  useEffect(() => {
    const handleEventEmitter = (eventValue: T) => {
      if (JSON.stringify(eventValue) !== JSON.stringify(state)) {
        updateState(eventValue, true);
      }
    };
    const removeHandler = EventEmitter.on<T>(key, handleEventEmitter);
    return () => {
      removeHandler();
    };
  }, [state]);

  const updateState = useCallback(
    (value: T, isEmit?: boolean) => {
      function updateForageState(currentState: T) {
        setState(currentState);

        if (typeof currentState === 'undefined') {
          localforage.removeItem(refKey.current);
        } else {
          localforage
            .setItem(refKey.current, currentState)
            .then(() => {
              if (!isEmit) {
                console.log('emit');
                EventEmitter.emit(key, currentState);
              }
            })
            .catch((e) => {
              console.error(e);
            });
        }
      }
      if (!refInit) {
        setInitSetList((list) => [
          ...list,
          updateForageState.bind(useLocalStorage, value),
        ]);
      } else {
        updateForageState(value);
      }
    },
    [refKey, refInit],
  );

  return [state, updateState] as const;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 数据实时:即数据库中的数据得到更新,页面立刻就想得到更新并展示最新的数据状态。通常使用在大数据可视化分析,运营数据...
    果汁凉茶丶阅读 6,091评论 1 5
  • H5性能优化意义 对于一个H5的产品,功能无疑很重要,但是性能同样是用户体验中不可或缺的一环。原本H5的渲染性能就...
    7ece657ee3b6阅读 1,574评论 0 2
  • H5性能优化意义 对于一个H5的产品,功能无疑很重要,但是性能同样是用户体验中不可或缺的一环。原本H5的渲染性能就...
    望月成三人阅读 955评论 1 10
  • H5性能优化方案 H5性能优化意义 对于一个H5的产品,功能无疑很重要,但是性能同样是用户体验中不可或缺的一环。原...
    自然心情阅读 8,026评论 0 14
  • 前言 现在许多app都嵌入了H5页面, 然而WebView加载速度慢这个问题却一直影响着用户的体验, 所以本文就如...
    迷途小码农h阅读 9,000评论 0 18