localStorage,sessionStorage和cookie的区别
生命周期
cookie
- 具有max-age属性: 存入内部的数据, 只要没有超过最大存活时间, 就会永久保留
- 不具有max-age属性: 这类cookie被称为会话级cookie, 当用户关闭标签页或者浏览器, 数据就会丢失
localStorage(持久化存储) : 只要不主动去删除, 就会永久保存
sessionStorage(会话级存储) : 在关闭标签页或者关闭浏览器的时候, 就会丢失
存储位置
cookie
- 有max-age属性: 存储在硬盘里
- 无max-age属性: 存储在内存中
localStorage: 存储在硬盘中
sessionStorage: 存储在内存中
存储大小
- cookie: 4KB
- localStorage: 一般5MB, IE是3012KB
- sessionStorage: 一般5MB, IE是3012KB
作用范围
cookie , 受到两个属性的控制
- domain属性: 比如domain为".baidu.com", 表示只有该域名以及它的子域名可以读取
- path属性: 比如path为"/a", 表示只有该路由以及它的子路由可以读取
localStorage: 只要地址相同, 哪怕是不同的标签页,都可以同步更新
sessionStorage: 地址相同, 不同的标签页可以获取数据, 但是这个数据不是同步更新的, 也就是修改了一个标签页, 另一个不会更新
与服务器的关系
cookie
- cookie更像是被服务器借用的本地存储
- 浏览器会自动存储cookie, 还会自动发送cookie
- 服务器如果想要返回cookie, 那么就会在响应头中, 添加属性setCookie属性, 用于返回数据
- 浏览器如果想要返回cookie数据给服务器, 那么就会在请求头中, 添加Cookie属性用于发送数据
localStorage: 他是HTML5新特性,与服务器不熟
sessionStorage: 他是HTML5新特性,与服务器不熟
使用场景
- cookie: 可以节省服务器的成本
- localStorage: 如果是关闭项目之后还要使用的数据, 选择使用localStorage存储
- sessionStorage:如果有一个数据, 只在项目关闭之前使用, 考虑到运算速度, 会使用sessionStorage4
怎么实现一个登录页面
在api目录下面,建立一个login.js文件, 配置三个发送axios请求的函数并导出
- 获取token
- 使用token兑换info
- 退出登录
// login.js // 获取axios实例request import request from '@/util/request'; // 登录,获取token const reqLogin = (username, password) => request.post('/admin/login', { username, password }); // 使用token兑换info const reqGetInfo = () => request.post('/admin/getinfo'); // 退出登录 const reqLogout = () => request.post('/admin/logout'); // 导出 export { reqLogin, reqGetInfo, reqLogout };在utils目录里面, 创建一个user.js文件夹, 用来存放一些token工具
- 添加token
- 获取token值
- 移除token
// user.js // 添加token export function setToken(token) { localStorage.setItem('shopAdmin-token', token); } //获取token export function getToken() { return localStorage.getItem('shopAdmin-token'); } // 移除token export function removeToken() { return localStorage.removeItem('shopAdmin-token'); }在stores目录下面, 新建一个pinia仓库loginStore.js , 用做登录数据的存储库
- state: ①token ②info
- actions: ①获取token ②token兑换info ③登出并删除token和info
// loginStore.js import { defineStore } from 'pinia'; import { setToken, getToken, removeToken } from '@/util/user.js'; import { reqLogin, reqGetInfo, reqLogout } from '@/api/login.js'; export const useLoginStore = defineStore('login', { state: () => { return { token: getToken() || '', info: '', }; }, actions: { // 1. 获取token async getAndSaveToken({ username, password }) { try { const { token } = await reqLogin(username, password); console.log(token); // 存一份到localStorege setToken(token); // 本仓库存一份 this.token = token; } catch (error) { // console.log(error); return Promise.reject(error); } }, // 2.使用token兑换info async getInfoByToken() { try { const info = await reqGetInfo(); this.info = info || {}; } catch (error) { // console.log(error); return Promise.reject(error); } // 3.派发退出登录actions logout() { // 不在意请求的响应结果 reqLogout(); // 清空本仓库的token和info this.token = ''; this.info = {}; // 清空localStorege removeToken(); }, }, });? 为什么前两个actions都要tryCatch一下, 并返回一个错误的promise?
这是为了别的页面调用这个actions也能tryCatch处理错误
在pages目录下面的单文件组件中, 比如login.vue, 在表单验证成功之后, 处理以下逻辑
- 表单验证通过
- 获取并存储token
- 使用token兑换info
- 去往首页
// login.vue // 引入登录页的store和vue-router import { useLoginStore } from '@/stores/loginStore.js'; import { useRouter } from 'vue-router'; // 创建两个对象 const router = useRouter(); const loginStore = useLoginStore(); ...... 假设上面已经表单校验通过 // 在表单验证成功之后: // 1. 获取并存储token try { await loginStore.getAndSaveToken({ username: form.username, password: form.password, }); } catch (err) { alert('用户名或密码错误'); return; } // 2. 使用token兑换info try { await loginStore.getInfoByToken(); } catch (error) { alert('获取用户信息失败'); return; } // 3. 去往首页 router.push('/');将token通过请求拦截器, 添加在请求头里面
引入loginStore
通过loginStore获取token
将token添加在请求头中
注意 :尽量从pinia仓库获取token和info数据, 从localStorege获取性能很差
import { useLoginStore } from '@/stores/loginStore.js'; ...... request.interceptors.request.use((config) => { const token = useLoginStore().token; if (token) { // 将token设置在请求头中 config.headers.token = token; } return config; }); ......配置全局前置守卫
- 定义白名单
- 从loginStore里面获取token和info
- 根据不同情况, 定义不同规则
// 定义白名单页面数组 const whiteList = ['/login']; router.beforeEach(async (to, from, next) => { // 引入登录的store const loginStore = useLoginStore(); // 得到token和info里面的username const token = loginStore.token; const username = loginStore.info.username; // 如果没有token if (!token) { // 是白名单的页面 if (whiteList.includes(to.path)) { // 放行 next(); } else { // 不是白名单页面, 直接回到登录页 alert('请登录!'); next('/login'); } } else { // 如果有token, 想从别的页面去login if (to.path == '/login') { // 不让去登录页 alert('不可直接去登录页,请先退出登录'); next(false); } else { // 如果去的不是登录页 // 如果有info信息 if (username) { next(); } else { try { // 如果没有info信息, 派发actions让仓库重新获取info await loginStore.getInfoByToken(); next(); } catch (error) { // 获取失败,视为token过期 // 派发退出登录action loginStore.logout(); alert('登录状态过期,请重新登录'); next('/login'); } } } } });
上述的路由规则逻辑如下