页面刷新数据丢失

在vue中data、vuex store等都数据都是在内存当中的,页面一旦刷新,这些数据就会丢失(或者说被重置为初始值),在某些时候很影响用户体验。

缓存,恢复

要想使数据刷新不丢失,就得监听页面刷新事件,在刷新前将数据缓存到本地存储,页面刷新后再将数据从本地存储恢复。目前较普遍的做法是类似这样:

//App.vue的created():
 created() {
    //在页面加载从本地读取状态数据并写入vuex
    if (sessionStorage.getItem("store")) {
      this.$store.replaceState(
        Object.assign(
          {},
          this.$store.state,
          JSON.parse(sessionStorage.getItem("store"))
        )
      );
    }

    //页面刷新前将vuex里的状态数据保存到sessionStorage
    window.addEventListener(
    	"beforeunload",()=>{
    	sessionStorage.setItem("store",JSON.stringify(this.$store.state));
    });
  }

这样做是直接将一整个store保存到本地,但是很多时候我们需要保存到本地的只是一些关键性数据,这样做缺少灵活性,而且对于那些并不想放在vuex中的数据极不友好。

改进:dataCache.ts

基于上面将数据缓存到本地存储然后页面加载时恢复的思想,我对上面的方法做了一些改进:

let caches: Record<string, { [index: string]: unknown }> = {};

let isFirst = false;
let baseId = 0;

const generateId = () => baseId++;

export default {
    /**
     * 将data注册进缓存,可指定自定义的id(通常不推荐这样做),可指定需要进行缓存的属性
     * @param data 需要注册的数据对象
     * @param id 为需要注册的数据对象的id,id最好不要是纯数字,并且必须保证id的唯一性
     * @param propertyKeys 指定需要将data的哪些属性进行缓存
     */
    register(data: { [index: string]: unknown }, id?: string, ...propertyKeys: PropertyKey[]): number | string {
        if (id === undefined) {
            id = (generateId()).toString();
        } else {
            const nid = Number(id);
            //字符串的值为数字,为了防止与默认的id生成策略冲突,将其转化成 id+'_'+generateId() 的形式
            if (!isNaN(nid)) {
                id += '_' + generateId();
            }
        }

        const hasId = Reflect.has(caches, id);

        //第一次渲染就id就已经存在,说明提供了重复的id
        if (isFirst && hasId) {
            throw new Error('Duplicate id:\'' + id + '\'');
        }
        //不是首次渲染且id存在,则对data执行属性值的注入 
        else if (hasId) {
            const cache = Reflect.get(caches, id as PropertyKey);

            if (cache instanceof Array) {
                for (let i = 0; i < cache.length; i++) {
                    data[i] = cache[i];
                }
            } else {
                const keys = propertyKeys.length < 1 ? Reflect.ownKeys(cache) : propertyKeys;

                for (const key of keys) {
                    Reflect.set(data, key, Reflect.get(cache, key));
                }
            }
        }

        //将data放进缓存区
        Reflect.set(caches, id, data);

        return id;
    },
    /**
     * 初始化data-caches,在App.vue的created钩子中调用此函数即可
     * @param itemKey 在sessionStorage中存取caches时的key
     */
    init(itemKey: string): void {
        // 页面加载时读取sessionStorage中的数据
        const jsonString = sessionStorage.getItem(itemKey);

        // jsonString为null说明是第一次渲染页面
        if (jsonString === null) {
            isFirst = true;
        } else {
            caches = JSON.parse(jsonString);
        }

        // 监听页面刷新,刷新时将数据保存到sessionStorage
        window.addEventListener("beforeunload", () => {
            sessionStorage.setItem(itemKey, JSON.stringify(caches));
        });
    }
}

用法

第一步:初始化

将上述代码保存到一个文件名为 dataSessionCache.js 的文件里,
然后在App.vue的created钩子中调用 init 函数进行初始化,需要提供一个itemName:

//App.vue
	import dc from "./ts/dataCache";

    export default {
        created() {
        	/*参数 itemKey 将作为 sessionStorage 进行
        	 getItem、setItem 操作时的 key,需要保证其唯一性.*/
            dc.init('data-caches');
        },
        //others
        ......
    };

第二步:注册

对于vue组件中的数据

对于vue组件中的数据例如data,将需要缓存的数据写到一个对象里面,然后再created钩子中进行注册,例如:

import dc from "@/js/dataCache.ts";

 export default {
	data: () => ({	
		//need cache
		cached: {
        	num : 134,
        	str: 'str'
        	......
        }
        //other needn't cache
        ......
    }),

	created(){
		//第二个参数是待缓存的数据对象,必须是一个对象或者数组
		sc.register(this.$data.cached);
	}
 };
对于VueX store

对于vuex store中的数据也可以用类似的方法:

//store中:
export default new Vuex.Store({
    state: {
        cached: {
            key: 'data'
        }
        //others
        ......
    },
    //others
    ......
})
//App.vue中
import dsc from "@/js/dataCache.ts";

export default {
	created() {
    	//别忘了init
       dsc.init('data-caches');
       //注册
       dsc.register(this.$store.state.cached);
    },
    //others
    ......
};

缓存对象的非全部属性

register的原型是register(id, obj, ...propertyKeys),第三个参数的作用是指定需要进行缓存的属性,如果propertyKeys为空则会缓存其全部属性。例如,当我们想 在不改变原有代码的情况下 缓存一个单文本组件的部分data时,可以这样写:

export default {
	data: () => ({
            a:1,
            b:'b'
        }),
        
	created() {
		//只缓存属性a
    	dsc.register('this.data,undefined,'a');
    },
    //others
    ......
}

好处

  1. 在页面刷新前后进行数据的保存/恢复工作,性能开销少
  2. 对原有代码仅需少量修改,或者根本不需要修改原有代码
  3. 简单易用,灵活方便,初始化、注册两步就OK,并且可以灵活的指定需要缓存的数据

git仓库

https://gitee.com/szw-yunie/data-session-cache.js