资讯专栏INFORMATION COLUMN

DIY 一个 Vuex 持久化插件

Half / 1318人阅读

摘要:接下来我们会从上述两个功能点出发,完成一个持久化插件。在我们的持久化插件中,就是在这个函数内部对数据进行持久化操作。而则整个被监听,所以任何对于的改动都会被持久化并能够被恢复。

在做 Vue 相关项目的时候,总会遇到因为页面刷新导致 Store 内容丢失的情况。复杂的项目往往涉及大量的状态需要管理,如果仅因为一次刷新就需要全部重新获取,代价也未免太大了。

那么我们能不能对这些状态进行本地的持久化呢?答案是可以的,社区里也提供了不少的解决方案,如 vuex-persistedstatevuex-localstorage 等插件,这些插件都提供了相对完善的功能。当然除了直接使用第三方插件以外,我们自己来 DIY 一个也是非常容易的。

这个持久化插件主要有2个功能:

能够选择需要被持久化的数据。

能够从本地读取持久化数据并更新至 Store。

接下来我们会从上述两个功能点出发,完成一个 Vuex 持久化插件。

Gist地址:https://gist.github.com/jrain...
在线体验地址:https://codepen.io/jrainlau/p...
一、学习写一个 Vuex 插件

引用 Vuex 官网 的例子:

Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数:

const myPlugin = store => {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次 mutation 之后调用
    // mutation 的格式为 { type, payload }
  })
}

然后像这样使用:

const store = new Vuex.Store({
  // ...
  plugins: [myPlugin]
})

一切如此简单,关键的一点就是在插件内部通过 store.subscribe() 来监听 mutation。在我们的持久化插件中,就是在这个函数内部对数据进行持久化操作。

二、允许用户选择需要被持久化的数据

首选初始化一个插件的主体函数:

const VuexLastingPlugin = function ({
  watch: "*",
  storageKey: "VuexLastingData"
}) {
  return store => {}
}

插件当中的 watch 默认为全选符号 *,允许传入一个数组,数组的内容为需要被持久化的数据的 key 值,如 ["key1", "key2"] 等。接着便可以去 store.subscribe() 里面对数据进行持久化操作了。

const VuexLastingPlugin = function ({
  watch: "*"
}) {
  return store => {
    store.subscribe((mutation, state) => {
      let watchedDatas = {}
      // 如果为全选,则持久化整个 state 
      // 否则将只持久化被列出的 state
      if (watch === "*") {
        watchedDatas = state
      } else {
        watch.forEach(key => {
          watchedDatas[key] = state[key]
        })
      }
      // 通过 localStorage 持久化
      localStorage && localStorage.setItem(storageKey, JSON.stringify(watchedDatas))
    })
  }
}

按照 Vuex 的规范,有且只有通过 mutation 才能够修改 state,于是按照上面的步骤,我们便完成了对数据进行实时持久化的工作。

这里也有一个小问题,就是写入 watch 参数的数组元素必须是 state 当中的最外层 key ,不支持形如 a.b.c 这样的嵌套 key。这样的功能显然不够完善,所以我们希望可以增加对嵌套 key 的支持。

新建一个工具函数 getObjDeepValue()

function getObjDeepValue (obj, keysArr) {
  let val = obj
  keysArr.forEach(key => {
    val = val[key]
  })
  return val
}

该函数接收一个对象和一个 key 值数组, 返回对应的值,我们来验证一下:

var obj = {
  a: {
    name: "aaa",
    b: {
      name: "bbb",
      c: {
        name: "ccc"
      }
    }
  }
}

getObjDeepValue(obj, "a.b.c".split("."))

// => { name: "ccc" }

验证成功以后,便可以把这个工具函数也放进 store.subscribe() 里使用了:

    store.subscribe((mutation, state) => {
      let watchedDatas = {}
      if (watch === "*") {
        watchedDatas = state
      } else {
        watch.forEach(key => {
          // 形如 a.b.c 这样的 key 会被保存为 deep_a.b.c 的形式
          if (data.split(".").length > 1) {
            watchedDatas[`deep_${key}`] = getObjDeepValue(state, key.split("."))
          } else {
            watchedDatas[key] = state[key]
          }
        })
      }
      
      localStorage && localStorage.setItem(storageKey, JSON.stringify(watchedDatas))
    })

经过这一改造,通过 watch 写入的 key 值将支持嵌套的形式,整个插件将会更加灵活。

三、从本地读取持久化数据并更新至 Store

从上面的步骤我们已经能够灵活监听 store 里的数据并持久化它们了,接下来的工作就是完成如何在浏览器刷新之后去读取本地持久化数据,并把它们更新到 store。

为插件添加一个默认为 true 的选项 autoInit,作为是否自动读取并更新 store 的开关。从功能上来说,刷新浏览器之后插件应该自动读取 localStorage 里面所保存的数据,然后把它们更新到当前的 store。关键的点就是如何把 deep_${key} 的值正确赋值到对应的地方,所以我们需要再新建一个工具函数 setObjDeepValue()

function setObjDeepValue (obj, keysArr, value) {
  let key = keysArr.shift()
  if (keysArr.length) {
    setObjDeepValue(obj[key], keysArr, value)
  } else {
    obj[key] = value
  }
}

该函数接收一个对象,一个 key 值数组,和一个 value ,设置对象对应 key 的值,我们来验证一下:

var obj = {
  a: {
    name: "aaa",
    b: {
      name: "bbb",
      c: {
        name: "ccc"
      }
    }
  }
}

setObjDeepValue(obj, ["a", "b", "c"], 12345)

/**
obj = {
  a: {
    name: "aaa",
    b: {
      name: "bbb",
      c: 12345
    }
  }
}
*/

有了这个工具方法,就可以正式操作 store 了。

    if (autoInit) {
      const localState = JSON.parse(storage && storage.getItem(storageKey))
      const storeState = store.state
      if (localState) {
        Object.keys(localState).forEach(key => {
          // 形如 deep_a.b.c 形式的值会被赋值到 state.a.b.c 中
          if (key.includes("deep_")) {
            let keysArr = key.replace("deep_", "").split(".")
            setObjDeepValue(storeState, keysArr, localState[key])
            delete localState[key]
          }
        })
        // 通过 Vuex 内置的 store.replaceState 方法修改 store.state
        store.replaceState({ ...storeState, ...localState })
      }
    }

上面这段代码会在页面初始化的时候读取 storage 的值,然后把形如 deep_a.b.c 的值提取并赋值到 store.state.a.b.c 当中,最后通过 store.replaceState() 方法更新整个 store.state 的值。这样便完成了从本地读取持久化数据并更新至 Store 的功能。

四、案例测试

我们可以写一个案例,来测试下这个插件的运行情况。

在线体验:https://codepen.io/jrainlau/p...

App.vue



store.js
import Vue from "vue"
import Vuex from "vuex"
import VuexPlugin from "./vuexPlugin"

Vue.use(Vuex)

export default new Vuex.Store({
  plugins: [VuexPlugin({
    watch: ["a.b.c", "x"]
  })],
  state: {
    a: {
      name: "aaa",
      b: {
        name: "bbb",
        c: {
          name: "ccc"
        }
      }
    },
    x: {
      name: "xxx"
    }
  },
  mutations: {
    updateA (state, val) {
      state.a = val
    },
    updateX (state, val) {
      state.x = val
    }
  }
})

从案例可以看出,我们针对 state.a.b.c 和 state.x 进行了数据持久化。在整个 state.a 都被修改的情况下,仅仅只有 state.a.b.c 被存入了 localStorage ,数据恢复的时候也只修改了这个属性。而 state.x 则整个被监听,所以任何对于 state.x 的改动都会被持久化并能够被恢复。

尾声

这个 Vuex 插件仅在浏览器环境生效,未曾考虑到 SSR 的情况。有需要的同学可以在此基础上进行扩展,就不再展开讨论了。如果发现文章有任何错误或不完善的地方,欢迎留言和我一同探讨。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109347.html

相关文章

  • Vuex久化插件-解决刷新数据消失的问题

    摘要:可以进行全局的状态管理,但刷新后刷新后数据会消失,这是我们不愿意看到的。怎么解决呢,我们可以结合本地存储做到数据持久化,也可以通过插件。 vuex可以进行全局的状态管理,但刷新后刷新后数据会消失,这是我们不愿意看到的。怎么解决呢,我们可以结合本地存储做到数据持久化,也可以通过插件-vuex-persistedstate。 欢迎来点点我的个人博客showImg(https://user-...

    adie 评论0 收藏0
  • vuex久化插件-解决浏览器刷新数据消失问题

    摘要:众所周知,的一个全局状态管理的插件,但是在浏览器刷新的时候,内存中的会释放,通常的解决办法就是用本地存储的方式保存数据,然后再初始化的时候再赋值给,手动存再手动取会觉得很麻烦,这个时候就可以使用的插件插件地址欢迎插件原理有一个方法每次在 众所周知,vuex的一个全局状态管理的插件,但是在浏览器刷新的时候,内存中的state会释放,通常的解决办法就是用本地存储的方式保存数据,然后再vue...

    walterrwu 评论0 收藏0
  • vue.js+socket.io打造一个好玩的新闻社区

    摘要:云新闻云新闻收藏的使用需要注意的地方提交的是,而不是直接的状态变更可以包含任意异步操作。的使用利用实现了简单的聊天功能,在同一个服务器下。 title: Socket.io+vue打造新闻社区date: 2017-06-12 20:19:05 tags: [vue.js,javascript,socket.io] vue2.0 + socket.io打造一个DIY新闻社区(web第一...

    xiguadada 评论0 收藏0
  • 深入学习Vuex

    摘要:深入学习作为配合使用的数据状态管理库,针对解决兄弟组件或多层级组件共享数据状态的痛点问题来说,非常好用。至此,构造函数部分已经过了一遍了。 深入学习Vuex vuex作为配合vue使用的数据状态管理库,针对解决兄弟组件或多层级组件共享数据状态的痛点问题来说,非常好用。本文以使用者的角度,结合源码来学习vuex。其中也参考了许多前辈的文章,参见最后的Reference Vue加载Vuex...

    codercao 评论0 收藏0

发表评论

0条评论

Half

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<