谈谈 Vuex 中 State 的持久化方案,例如使用 localStorage 或 sessionStorage。

各位观众老爷们,大家好!今天咱来聊聊 Vuex 里 State 那些事儿,特别是怎么让它“持久”一点,别一刷新就全没了。

开场白:State 的“短暂人生”

Vuex 的 State 就像我们程序里的“记忆”,用来存储应用的数据。但是,这“记忆”有个毛病,就是太短暂了!页面一刷新,或者浏览器一关,State 里的数据就灰飞烟灭了。这在很多场景下可不行,比如用户登录信息、购物车数据、上次浏览的商品等等,都得记住才行啊。

那咋办呢?我们需要给 State 找个“硬盘”,让它把数据存起来,下次启动的时候再读回来。这个“硬盘”就是浏览器提供的存储机制,比如 localStoragesessionStorage

第一章:localStoragesessionStorage:两位好基友

这两位都是浏览器提供的存储 API,用来在客户端存储数据。它们的主要区别在于数据的生命周期:

  • localStorage 永久存储,除非用户手动清除,否则数据会一直存在。适合存储用户设置、登录信息等长期保存的数据。
  • sessionStorage 会话存储,当浏览器窗口关闭时,数据会被清除。适合存储临时数据,比如购物车信息,避免用户下次打开时看到过期数据。

咱们可以用表格来总结一下:

特性 localStorage sessionStorage
生命周期 永久 会话
数据共享 同域共享 同窗口共享
使用场景 用户设置、登录信息 购物车信息、临时数据

第二章:手动实现 State 持久化:原始人的智慧

最简单粗暴的方法,就是每次 State 改变的时候,手动把数据存到 localStoragesessionStorage 里,然后在 Vuex 初始化的时候,从存储里读取数据。

2.1 初始化 State

首先,在你的 Vuex store.js 文件里,定义一个函数来初始化 State:

// store.js
function initialState() {
  try {
    // 尝试从 localStorage 中读取数据
    const storedState = localStorage.getItem('my-app-state');
    if (storedState) {
      return JSON.parse(storedState);
    }
  } catch (e) {
    // 如果读取或解析失败,返回默认的 state
    console.error('Failed to load state from localStorage:', e);
  }

  // 默认的 state
  return {
    user: null,
    cart: [],
    settings: {
      theme: 'light',
      language: 'en'
    }
  };
}

export default new Vuex.Store({
  state: initialState(),
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    addToCart(state, item) {
      state.cart.push(item);
    },
    setTheme(state, theme) {
      state.settings.theme = theme;
    }
  },
  actions: {
    login({ commit }, user) {
      commit('setUser', user);
    },
    addItemToCart({ commit }, item) {
      commit('addToCart', item);
    },
    changeTheme({ commit }, theme) {
      commit('setTheme', theme);
    }
  }
});

这段代码首先尝试从 localStorage 中读取名为 my-app-state 的数据,如果存在,就用 JSON.parse 解析成 JavaScript 对象,作为 State 的初始值。如果读取失败或者 localStorage 里没有数据,就使用默认的 State。

2.2 监听 State 变化并保存

接下来,我们需要在每次 State 发生变化时,把新的 State 保存到 localStorage 里。我们可以使用 Vuex 的 subscribe 方法来实现:

// store.js
const store = new Vuex.Store({
  state: initialState(),
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    addToCart(state, item) {
      state.cart.push(item);
    },
    setTheme(state, theme) {
      state.settings.theme = theme;
    }
  },
  actions: {
    login({ commit }, user) {
      commit('setUser', user);
    },
    addItemToCart({ commit }, item) {
      commit('addToCart', item);
    },
    changeTheme({ commit }, theme) {
      commit('setTheme', theme);
    }
  }
});

// 监听 state 的变化
store.subscribe((mutation, state) => {
  try {
    localStorage.setItem('my-app-state', JSON.stringify(state));
  } catch (e) {
    console.error('Failed to save state to localStorage:', e);
  }
});

export default store;

store.subscribe 接收一个回调函数,这个回调函数会在每次 mutation 完成后被调用。回调函数接收两个参数:mutationstate。我们只需要把当前的 stateJSON.stringify 转换成字符串,然后保存到 localStorage 里就可以了。

2.3 简单示例

现在我们来个简单的例子:

<template>
  <div>
    <p>用户名: {{ user ? user.name : '未登录' }}</p>
    <button @click="login">登录</button>
    <p>当前主题: {{ theme }}</p>
    <button @click="toggleTheme">切换主题</button>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState(['user', 'settings']),
    theme() {
      return this.settings.theme;
    }
  },
  methods: {
    ...mapActions(['login', 'changeTheme']),
    login() {
      const mockUser = { id: 1, name: '张三' };
      this.login(mockUser);
    },
    toggleTheme() {
      const newTheme = this.theme === 'light' ? 'dark' : 'light';
      this.changeTheme(newTheme);
    }
  }
};
</script>

在这个例子里,我们有两个按钮:一个用来模拟登录,另一个用来切换主题。每次点击按钮,都会触发对应的 action,然后更新 State,最后 State 的变化会被 store.subscribe 监听到,并保存到 localStorage 里。

刷新页面,你会发现用户名和主题都保持不变,因为它们已经被持久化了。

第三章:Vuex Plugins:优雅的解决方案

手动实现 State 持久化虽然简单,但是比较繁琐,每次都要写 localStorage.setItemJSON.parse。而且,如果你的 State 结构比较复杂,手动处理起来就更麻烦了。

这时候,Vuex Plugins 就派上用场了。Vuex Plugins 允许我们扩展 Vuex 的功能,比如我们可以写一个 Plugin 来自动处理 State 的持久化。

3.1 创建一个 Vuex Plugin

首先,创建一个名为 vuex-persist.js 的文件:

// vuex-persist.js
export default function createPersistedState(options = {}) {
  const {
    key = 'vuex', // localStorage 的 key
    storage = localStorage, // 使用的 storage 对象,默认为 localStorage
    reducer = state => state, // 用于转换 state 的函数,默认为 identity function
    rehydrated = () => {} // state 加载完成后的回调函数
  } = options;

  return store => {
    // 初始化时,从 storage 中读取 state
    try {
      const storedState = storage.getItem(key);
      if (storedState) {
        store.replaceState(JSON.parse(storedState));
      }
    } catch (e) {
      console.error('Failed to load state from storage:', e);
    } finally {
      rehydrated();
    }

    // 监听 mutations,保存 state 到 storage
    store.subscribe((mutation, state) => {
      try {
        storage.setItem(key, JSON.stringify(reducer(state)));
      } catch (e) {
        console.error('Failed to save state to storage:', e);
      }
    });
  };
}

这个 Plugin 接收一个 options 对象,可以用来配置 localStorage 的 key、使用的 storage 对象、以及一些回调函数。

Plugin 的主要逻辑是:

  1. 在初始化时,从 storage 中读取 State,并使用 store.replaceState 替换 Vuex 的 State。
  2. 监听 mutations,每次 mutation 完成后,把新的 State 保存到 storage 里。

3.2 使用 Vuex Plugin

在你的 store.js 文件里,引入并使用这个 Plugin:

// store.js
import Vuex from 'vuex';
import createPersistedState from './vuex-persist';

export default new Vuex.Store({
  state: {
    user: null,
    cart: [],
    settings: {
      theme: 'light',
      language: 'en'
    }
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    addToCart(state, item) {
      state.cart.push(item);
    },
    setTheme(state, theme) {
      state.settings.theme = theme;
    }
  },
  actions: {
    login({ commit }, user) {
      commit('setUser', user);
    },
    addItemToCart({ commit }, item) {
      commit('addToCart', item);
    },
    changeTheme({ commit }, theme) {
      commit('setTheme', theme);
    }
  },
  plugins: [
    createPersistedState({
      key: 'my-app-state', // localStorage 的 key
      storage: localStorage // 使用 localStorage
    })
  ]
});

只需要在 plugins 数组里添加 createPersistedState,并传入一些配置项,就可以自动处理 State 的持久化了。

3.3 进阶用法:Partial Persistence

有时候,我们只需要持久化 State 的一部分,而不是全部。比如,我们只想持久化 usersettings,而不想持久化 cart

这时候,我们可以使用 reducer 函数来过滤 State:

// store.js
import Vuex from 'vuex';
import createPersistedState from './vuex-persist';

export default new Vuex.Store({
  state: {
    user: null,
    cart: [],
    settings: {
      theme: 'light',
      language: 'en'
    }
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    addToCart(state, item) {
      state.cart.push(item);
    },
    setTheme(state, theme) {
      state.settings.theme = theme;
    }
  },
  actions: {
    login({ commit }, user) {
      commit('setUser', user);
    },
    addItemToCart({ commit }, item) {
      commit('addToCart', item);
    },
    changeTheme({ commit }, theme) {
      commit('setTheme', theme);
    }
  },
  plugins: [
    createPersistedState({
      key: 'my-app-state', // localStorage 的 key
      storage: localStorage, // 使用 localStorage
      reducer: (state) => { // 只保留 user 和 settings
        return {
          user: state.user,
          settings: state.settings
        };
      }
    })
  ]
});

reducer 函数里,我们只返回了 usersettings,这样只有这两个属性会被持久化。

第四章:第三方库:站在巨人的肩膀上

除了自己写 Plugin,我们还可以使用一些现成的第三方库,它们提供了更丰富的功能和更灵活的配置。

4.1 vuex-persistedstate

vuex-persistedstate 是一个非常流行的 Vuex State 持久化库,它支持多种 storage 对象(localStoragesessionStorage、Cookie 等),以及多种配置选项。

安装:

npm install vuex-persistedstate

使用:

// store.js
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';

export default new Vuex.Store({
  state: {
    user: null,
    cart: [],
    settings: {
      theme: 'light',
      language: 'en'
    }
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    addToCart(state, item) {
      state.cart.push(item);
    },
    setTheme(state, theme) {
      state.settings.theme = theme;
    }
  },
  actions: {
    login({ commit }, user) {
      commit('setUser', user);
    },
    addItemToCart({ commit }, item) {
      commit('addToCart', item);
    },
    changeTheme({ commit }, theme) {
      commit('setTheme', theme);
    }
  },
  plugins: [
    createPersistedState({
      key: 'my-app-state',
      storage: window.localStorage,
      reducer(state) {
        return {
          user: state.user,
          settings: state.settings
        };
      }
    })
  ]
});

vuex-persistedstate 提供了很多配置选项,比如:

  • key:localStorage 的 key。
  • storage:使用的 storage 对象,默认为 localStorage
  • paths:一个数组,指定要持久化的 State 路径。
  • reducer:一个函数,用于转换 State。
  • subscriber:一个函数,用于自定义 State 的保存逻辑。

4.2 vuex-persist (另一个库)

注意,这里还有一个名为 vuex-persist 的库,但它跟我们前面自己写的 Plugin 同名,容易混淆。这个库也提供 State 持久化功能,但 API 和用法略有不同。

安装:

npm install vuex-persist

使用:

// store.js
import Vuex from 'vuex';
import VuexPersist from 'vuex-persist';

const vuexPersist = new VuexPersist({
  key: 'my-app-state',
  storage: window.localStorage,
  reducer: state => ({
    user: state.user,
    settings: state.settings
  })
});

export default new Vuex.Store({
  state: {
    user: null,
    cart: [],
    settings: {
      theme: 'light',
      language: 'en'
    }
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    addToCart(state, item) {
      state.cart.push(item);
    },
    setTheme(state, theme) {
      state.settings.theme = theme;
    }
  },
  actions: {
    login({ commit }, user) {
      commit('setUser', user);
    },
    addItemToCart({ commit }, item) {
      commit('addToCart', item);
    },
    changeTheme({ commit }, theme) {
      commit('setTheme', theme);
    }
  },
  plugins: [
    vuexPersist.plugin
  ]
});

这个库需要先创建一个 VuexPersist 实例,然后把它的 plugin 属性添加到 Vuex 的 plugins 数组里。

第五章:注意事项:别踩坑里了!

  • 数据大小限制: localStoragesessionStorage 都有数据大小限制,一般是 5MB 左右。如果你的 State 非常大,可能会超出限制,导致数据无法保存。这时候,可以考虑只持久化 State 的一部分,或者使用 IndexedDB 等更大的存储方案。
  • 数据类型: localStoragesessionStorage 只能存储字符串。所以,你需要使用 JSON.stringify 把 JavaScript 对象转换成字符串,然后再保存。读取的时候,需要使用 JSON.parse 把字符串转换回 JavaScript 对象。
  • 数据安全性: localStoragesessionStorage 存储的数据是明文的,任何人都可以读取。所以,不要存储敏感数据,比如用户密码、银行卡号等。如果需要存储敏感数据,应该进行加密处理。
  • 异步操作: 如果你的 State 里有异步操作的结果,比如 Promise,那么在持久化的时候可能会出现问题。因为 Promise 在序列化成 JSON 字符串时,会丢失其状态。所以,在持久化之前,需要把 Promise 转换成普通的值,比如使用 await 等待 Promise 完成。
  • 兼容性: 某些浏览器可能不支持 localStoragesessionStorage。在使用之前,最好先检测一下浏览器是否支持这些 API。

总结:选择适合你的方案

今天我们聊了 Vuex State 的持久化方案,从最原始的手动实现,到使用 Vuex Plugins,再到使用第三方库,每种方案都有其优缺点。

方案 优点 缺点 适用场景
手动实现 简单易懂,灵活可控 繁琐,容易出错,代码冗余 简单的项目,或者只需要持久化少量数据
Vuex Plugin 优雅,可复用,可定制 需要自己编写 Plugin,有一定的学习成本 中小型项目,需要持久化部分 State
第三方库 功能丰富,配置灵活,社区支持良好 引入第三方依赖,可能会增加项目体积 大型项目,需要持久化大量数据,或者需要更高级的功能

选择哪种方案,取决于你的项目规模、需求复杂度和个人偏好。希望今天的讲座能对你有所帮助。

各位观众老爷们,下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注