背景
我们在使用vue去写前端的时候,肯定遇到过对不同组件之间传递变量值的需求,但是我们又不能很高校地去解决。vuex就是用来解决这种需求的,让我们的组件们拥有共享变量。
vuex 作用
vuex 优点
为什么我要用超级全局变量来说呢?因为它和全局变量是不一样的:
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。【从java的角度理解就是这些变量都是私有private,只能set()get()方法去修改,不能直接赋值】
ps:一定要注意,vuex在刷新页面即销毁,涉及刷新页面的数据需要重新赋值给vuex。可以多尝试在当前vue组件的mounted钩子函数去进行vuex数据初始化,保证使用vuex的时候一定有值。
安装
script : 不常用
npm :
# 注意!!vue2要安装vuex3.x
npm install [email protected] --save
# 如果是这样写,会默认安装vuex4.x,是面向vue3的,无法使用
# npm install vuex --save
yarn :
# 同理要装vuex3.x
yarn add [email protected]
我们前面讲到 Vuex 的核心就是 store,所以对于我们来说,store就能够满足小项目的需求了。
目录准备
我们一般会在src下建立store目录,在里面创建一个index.js文件来使用vuex。
当然我们也可以在main.js中使用,但是考虑到代码耦合度,分开使用更佳
当然官方有最规范的目录结构,因为在一个完整项目中store内容一定很多,把整个store放在index.js中是不合理的,所以需要拆分。
store:.
│ actions.js
│ getters.js
│ index.js
│ mutations.js
│ mutations_type.js ##该项为存放mutaions方法常量的文件,按需要可加入
│
└─modules
Astore.js
简单使用
store/index.js中定义:
//导入这个是为了Vue.use(Vuex) 挂载vuex对象用来访问
import Vue from 'vue'
//导入vuex包
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
// 创建一个新的 store 实例
export default new Vuex.Store({
state: {
//存放的键值对就是要管理的状态
user: {
account: "window.localStorage.getItem()"
},
},
//方法,一般包含功能性方法和状态变量的set()get()方法
mutations: {
loginSuccess (state, account) {
state.user.account = account
window.localStorage.setItem('account',JSON.stringify(account))
},
//修改状态变量只能通过这种方式
setAccount (state, account) {
this.user.account = account
}
}
})
mian.js中配置:
import store from './store'
new Vue({
el: '#app',
router,
store,
components: { App },
template: ' '
})
vue组件中使用:
// 调用访问状态变量
console.log(this.$store.state.user.account)
// 调用访问状态方法 第一个实参是方法名,第二个实参开始是参数列表
this.$store.commit('loginSuccess',this.account)
this.$store.commit('setAccount','xxx')
//注意,只读状态变量可以直接this.去读,但是修改一定要设置一个方法,然后通过访问方法的格式去访问setXX的方法去修改状态变量【状态变量也就是共享变量】
参考文章:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage
window.localStorage 和 window.sessionStorage 都可以用来存放全局变量。但是区别就在于window.sessionStorage在页面关闭的时候就会被回收,而window.localStorage是直接存放在本地浏览器的cookie里,每次vue启动都会扫描本地浏览器的cookie去找有没有要找的属性值。
另外,localStorage 中的键值对总是以字符串的形式存储。 (需要注意, 和js对象相比, 键值对总是以字符串的形式存储意味着数值类型会自动转化为字符串类型).
一般写window.localStorage更好,当然不加window应该也没问题
下面的代码片段访问了当前域名下的本地 Storage对象,并通过 Storage.setItem() 对象,并通过 Storage.setItem()增加了一个数据项目。这个数据会直接被添加到本地浏览器的cookie中
放置localStorage 项
localStorage.setItem('myCat', 'Tom');
获取 localStorage 项
let cat = localStorage.getItem('myCat');
移除指定 localStorage 项
localStorage.removeItem('myCat');
移除所有的 localStorage 项
localStorage.clear();
参考文章:https://www.jianshu.com/p/ddcb7ba28c5e
导航表示路由正在从单个页面跳转到另一个页面的过程,这个过程可以添加操作来保证用户是否有权限进行跳转路由。
路由钩子函数一共三种:
全局钩子,一般可以写在main.js下方或者路由index.js下方:beforeEach、afterEach、beforeResolve
单个路由进入的钩子:beforeEnter
进入组件的钩子:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
无论访问哪一个路径,都会触发全局的钩子函数,位置是调用router的方法
router.beforeEach() 进入前触发
router.afterEach() 进入后触发
当前项目仅依赖router.beforeEach()
/**
* @Param to : 即将要进入的目标路由对象
* @Param from : 正在离开的路由
* @Param next : 必须要调用的方法,next()表示允许进入to这个路由对象,
* next(false)表示不能进入,next({path:'/'})定向到某地址
*/
router.beforeEach((to,from,next)=>{
xxxx
})
若没有调用next() 则beforeEach钩子不能被resolved调用
解决刷新页面不执行beforeEach()拦截
原因是router的beforeEach方法定义在vue渲染router之后,如果router.beforeEach放在new Vue之后,因为vue渲染在刷新页面的时候不重复,所以不会重新渲染router,也就无法进入router的钩子函数。因此要修改成下面的顺序:路由钩子在前,vue渲染在后。
//在每次访问路由前调用
router.beforeEach((to,from,next)=>{
if (to.meta.requireAuth) {
if(store.state.user.account ) {
next()
} else {
next({
name: 'login',
query: {redirect: to.fullPath}
})
}
} else {
next()
}
})
//每次调用beforeEach后再渲染页面
new Vue({
el: '#app',
router,
store,
components: { App },
template: ' '
})
npm install vuex --save
参考文章:Vue + Spring Boot 项目实战(六):前端路由与登录拦截器_Evan-Nightly的博客-CSDN博客
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//创建store对象
export default new Vuex.Store({
state: {
//如果本地浏览器有登录信息就直接获取
user: {
//默认是空
account: "",
},
},
//方法
mutations: {
//成功登录才会进来这里
loginSucess (state, account) {
//先把登录信息放进store.state的状态变量中方便后面调用
state.user.account = account
//把登录信息放进localStorage,让vue放进浏览器cookie中
window.localStorage.setItem('account',account)
},
//用来改变account的值的方法
//外界只能通过该方法更改account的值
initAccount(state, account){
state.user.account = account
}
}
})
在自己的登录组件login.vue中:
login() {
if (this.isAccount && this.isPwd) {
var salt = bcrypt.genSaltSync(11)
this.password = bcrypt.hashSync(this.password, salt)
//在请求判断是否为网站合法用户的时候加入判断如果成功,调用store的成功登录方法,把登录信息放入cookie中
this.$http.post("/user/login", {
account: this.account,
password: this.password
}).then((res) => {
//如果成功登录就调用loginSuccess方法
if(res.data == "OK"){
this.$store.commit('loginSuccess',this.account)
}
})
}
},
router.beforeEach((to,from,next)=>{
//如果当前要跳转的路由需要认证,则认证
if (to.meta.requireAuth) {
//获取当前浏览器cookie是否存在账号
let curAccount = window.localStorage.getItem('account')
//如果cookie中保存有登录信息,就说明登陆过,放行
if(curAccount) {
next()
//如果没有登录到,就强制重定向到login
} else {
next({
name: 'login',
//下面的query参数会让地址栏多一个重定向提示
//可以注释掉,这样地址栏会简洁一点
query: {redirect: '/login'}
})
}
//如果不需要认证就放行
} else {
next()
}
})
未能成功登录的情况
刷新页面后有缓存响应并成功获取
为了保证用户登录状态安全,一般登录后的cookie缓存会保持一段时间然后自动清除,需要重新登陆以防止被恶意使用。
1.登录请求部分调用store的成功状态方法
login() {
if (this.isAccount && this.isPwd) {
var salt = bcrypt.genSaltSync(11)
this.password = bcrypt.hashSync(this.password, salt)
this.$http.post("/user/login", {
account: this.account,
password: this.password
}).then( (res) =>{
if(res.data == 'OK'){
//console.log(res.data)
//如果登录成功就调用成功状态的方法
this.$store.commit('loginSuccess',this.account)
}
})
}
},
2.store的mutation修改loginSuccess方法
需要在setItem写cookie的时候多写一个当前时间戳
mutations: {
loginSuccess (state, account) {
state.user.account = account
//注意,因为多了个参数组合成json格式,要string化才能写进cookie
window.localStorage.setItem('account', JSON.stringify({'account':account,'startTime':Date.now()}))
},
initAccount (state, account){
state.user.account = account
}
}
3.main.js的beforeEach函数修改
//设定12小时清一次cookie,单位是ms毫秒
//const DEADTIME = 43200000
//测试的时候使用的时间短一点
const DEADTIME = 10000
//在每次访问路由前调用
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
//初始化account,如果account令牌存在,则判断是否过期
let curAccount = window.localStorage.getItem('account')
if (curAccount) {
let startTime = JSON.parse(curAccount).startTime
//如果现在时间的时间戳 - 当前这个令牌创建的时间戳的结果超期了,就清掉token重新登录
if (new Date().getTime() - startTime > DEADTIME) {
//清掉cookie,并且初始化store的account值
window.localStorage.removeItem('account')
store.commit('initAccount', "")
//同时跳转回/login登录页面
next({
name: 'login',
//query: { redirect: '/login' }
})
//如果没超期,直接放行
} else {
next()
}
} else {
next({
name: 'login',
//下面这行会让浏览器多一个重定向提示
//query: { redirect: '/login' }
})
}
//如果当前要进入的路由不需要验证,则直接放行
} else {
next()
}
})
参考文章:https://www.cnblogs.com/lbzli/p/12873502.html
错误原因
异步请求中的then后使用function(){},this不指向全局vue
}).then(function(res){
if(res.data == 'OK'){
console.log(res.data)
this.$store.commit('loginSuccess',this.account)
}
})
解决方法
then后使用 (res)=>{}
}).then( (res) =>{
if(res.data == 'OK'){
console.log(res.data)
this.$store.commit('loginSuccess',this.account)
}
})
意思是在Vue中为了节约资源,一样的内容不会重复加载(浏览器如果发现请求地址一样也不会跳)。所以当你请求跳转相同的路径时,不会直接拼接上去URL上,给你一个警告。
解决方法:判断当前路由位置即可
//我们刚刚编写的beforeEach函数
router.beforeEach((to, from, next) => {
//可以看到有to和from,他们都是router对象
//那我们可以判断他们两者不同的时候再放行即可
//把所有的next()改为下面:
if(to.path != from.path) {
next();
}
//这样就不会加载重复的路由了。
检查方法名是否一致。
报错不影响功能,凑合着吧
login() {
if (this.isAccount && this.isPwd) {
var salt = bcrypt.genSaltSync(11)
this.password = bcrypt.hashSync(this.password, salt)
this.$http.post("/user/login", {
account: this.account,
password: this.password
}).then( (res) =>{
if(res.data == 'OK'){
//console.log(res.data)
this.$store.commit('loginSuccess',this.account)
}
})
}
},
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//创建store对象
export default new Vuex.Store({
state: {
//存放的键值对就是要管理的状态
user: {
account: "",
},
},
//方法s
mutations: {
loginSuccess (state, account) {
state.user.account = account
window.localStorage.setItem('account', JSON.stringify({'account':account,'startTime':Date.now()}))
},
initAccount (state, account){
state.user.account = account
}
}
})
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import axios from 'axios'
Vue.config.productionTip = false
Vue.prototype.$http = axios
//12小时清一次,ms为单位
//const DEADTIME = 43200000
const DEADTIME = 10000
//在每次访问路由前调用
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
//初始化account,如果account令牌存在,则判断是否过期
let curAccount = window.localStorage.getItem('account')
if (curAccount) {
let startTime = JSON.parse(curAccount).startTime
//如果现在时间的时间戳 - 当前这个令牌的开始时间戳 超期 了,就清掉token重新登录
if (new Date().getTime() - startTime > DEADTIME) {
window.localStorage.removeItem('account')
store.commit('initAccount', "")
next({
name: 'login',
//query: { redirect: '/login' }
})
//如果没超期,直接放行
} else {
next()
}
} else {
next({
name: 'login',
//下面这行会让浏览器多一个重定向提示
//query: { redirect: '/login' }
})
}
} else {
next()
}
})
new Vue({
el: '#app',
router,
store,
components: { App },
template: ' '
})
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/mySpace',
name: 'myspace',
component: MySpace,
//重点参数
meta: {
requireAuth: true
}
}
]
})