xinglie / xinglie.github.io

blog
https://xinglie.github.io
153 stars 22 forks source link

告别loading,让web换一种方式等待接口 #72

Open xinglie opened 3 years ago

xinglie commented 3 years ago

本文不讨论manifest,servicework等技术,但本文给出的方案不与manifest,servicework等冲突,可以一起使用,从多维度给web加速

资源的分与合

开发阶段我们通常根据自己的需要,把前端文件进行拆分,以进行更好的逻辑组合。 上线时,我们拆分的这些文件,有些打包工具会默认合并,比如webpack,而某些打包工具不会合并,比如magix-composer

我们的这些资源js、css等文件,到底是合并还是拆分需要具体到应用场景,不过从整体看,pc更适合拆分的方式,而移动更适合合并的方式,本文也不再深入讨论为什么pc适合拆分,移动适合合并。

因个人长期从事pc端的开发,因此接下来我们讨论的场景基于pc端,资源不合并的情况下。

loading场景

在pc端因为资源不合并,我们从任意一个入口进入时,即打开某个页面,比如首页、报表或列表等,可以最小化的加载所需要资源,让页面展示更快。

当我们从某个页面进入时,需要先加载资源,再请求接口,该过程会有一个稍长的loading过程

当我们从某个页面切换到其它页面时,仍然是加载所需要资源,再进行接口请求,最后才能渲染显示页面,该过程的loading由于某些资源在之前页面可能加载过,会比之前稍快一些,但仍旧较长,我们能否做到点击页面后就能立即显示,避免loading?

资源加速

我们默认并不会对所有资源进行打包,或分功能打包,开发时什么样,上线后仍然保持什么样的拆分粒度。 但我们可以使用离线编译工具对整个项目中的文件进行扫描处理,生成一份资源清单,比如这样

//app/files.js
export default [
    "~mp/app/components/xy-btn",
    "~mp/app/dragdrop",
    "~mp/app/inner",
    "~mp/app/keyboard",
    "~mp/app/test/z-inner",
    "~mp/galleries/mx-table/index"
]

因为该文件位置固定,我们在页面空闲时,可以获取到该文件。然后使用加载器,逐一加载清单中的资源,这样在用户切换到未来某个页面时,该页面所需要的资源已经加载好了,只需要等待接口数据的请求即可。

接口加速

目前大多数的web都是使用http的方式与后端交互,如果需要实时性更高的方案,可以参考我的另一篇:[基于websocket的前后端分离](https://github.com/xinglie/xinglie.github.io/issues/71) 对于http接口,我们不能缓存,绝大多数接口都需要每次请求后端来拿到最新的数据。但我们可以换另一种方式进行缓存

核心思路为:普通的请求过程是 前端展示loading->请求后端接口->获取到数据->隐藏loading-> 展示界面 升级思路为:无缓存的情况 前端请求接口-> 数据层无缓存-> 展示loading -> 获取数据 -> 缓存数据 -> 隐藏loading -> 展示界面 有缓存的情况 前端请求接口-> 数据层有缓存-> 展示界面 -> 获取数据 -> 缓存数据 -> 新数据与旧数据不同 -> 展示界面

我们可以看到,在首次场景下,因为数据层为空,我们依然需要展示loading,毕竟首次未拿到数据的场景下,展示空状态的界面也不合适。但在后续有缓存的场景下,我们可以直出界面,后台发送一个请求,如果返回的数据与界面展示的不同,则再重新更新一下界面即可。

想象场景如下: 某个列表,用户首次进入时,展示loading并请求缓存数据,后续再进入时,因为缓存中有列表数据,可以直接展示,即使这个列表数据在其它页面中有添加或删除,因为我们后台会有一个请求再去获取一次数据,因此在返回后依然可以更新界面,确保数据一致性。

实践代码

// datasource-http.ts 实现一个文件管理器,用于在web中管理远程服务器上的文件import Magix from 'magix5';
let { mix, config, toUrl, Event, Cache } = Magix;
let lastKey;
let dataCache = new Cache();
let request = async (part, params) => {//使用fetch发送请求
    let options = {
        method: 'get',
        credentials: 'include',
        headers: {
            'Accept': 'application/json',
            'Content-type': 'application/x-www-form-urlencoded;charset=utf-8',
        }
    } as RequestInit;
    let url = config<string>('server');
    return await fetch(toUrl(url + part, params), options);
};
let send = async (key, cached, entity, action, path) => {
    let r = await request(action, {
        path
    });
    let text = await r.text();
    let json = JSON.parse(text);
    if (cached) {
        if (lastKey == key &&
            text != cached['@:{text}']) {//如果数据与缓存中的不同,则更新缓存,并派发事件
            dataCache.set(key, {
                '@:{text}': text,
                '@:{json}': json
            });
            entity.fire('@:{event#fv.list.path}');
        }
    } else {
        dataCache.set(key, {
            '@:{text}': text,
            '@:{json}': json
        });
        return json;
    }
};
export default mix({
    async '@:{send}'({ action, path }) {
        let key = action + '~' + path;
        let cached = dataCache.get(key);
        lastKey = key;
        let sendPromise = send(key, cached, this, action, path);
        if (cached) {//如果有缓存,直接返回更新界面
            return cached['@:{json}'];
        } else {//等待请求完成
            return await sendPromise;
        }
    }
}, Event);

在某个view中使用

import Magix from 'magix5';
import DataSource from './ds';

let { View } = Magix;
export default View.extend({
    tmpl: '@:./index.html',
    init() {
        let update = this.render.bind(this);
        DataSource.on('@:{event#fv.list.path}', update);
    },
    async render() {
        let r = await DataSource['@:{send}']({
            action: 'fv/list.json',
            path: '/abc/def'
        });
        console.log(r);
        this.digest({files:r});
    }
})

加入缓存后,从原来的请求等待->渲染界面到 直接渲染界面->请求数据。实现了依赖的反转,避免了loading。

同类natvie应用

以iCloud为例,我们在pc端放入了新的文件,在移动端打开app时,app会直接先展示之前的文件与文件夹,然后再同步数据,如果有变化则显示新的文件和文件夹,native应用并不有像我们之前先展示loading,请求数据返回后再展示。

使用indexDB或localStorage增强缓存

之前的方案使用内存进行缓存,如果用户刷新页面,则每次都需要先展示loading,如果我们能够使用indexDB或localStorage进行数据缓存,这样在indexDB或localStorage有数据的情况下,刷新页面也不需展示loading,更类似native应用了。