1. Vuex
- 状态管理
2.Vuex管理些什么
- 用户登录状态, token , 头像 ,地理位置等
- 商品收藏,购物车中的东西
3. Vuex安装
-
使用npm安装Vuex
npm install vuex --save
-
在项目的src目录下面创建一个store目录,并在该目录下面创建一个index.js
index.js内容如下:
import Vue from 'vue' import Vuex from 'vuex' // 1. 安装插件 Vue.use(Vuex) // 2. 创建对象 const store = new Vuex.Store({ state:{}, mutations:{}, actions:{}, getters:{}, modules:{} }) // 3. 导出store独享 export default store
-
在main.js中挂载vuex
import Vue from 'vue' import App from './App.vue' import store from "./store"; Vue.config.productionTip = false new Vue({ render: h => h(App), store }).$mount('#app')
4. Vuex的基本使用
4.1 state
- 在state中定义一个变量之后,全局都可以使用.而且还是响应式的,使用起来很方便
const store = new Vuex.Store({
state:{
counter: 1000 // 定义一个counter
},
mutations:{},
actions:{},
getters:{},
modules:{}
})
- 在组件中使用counter
<div class="hello">
<h2>{{$store.state.counter}}</h2>
</div>
4.2mutations
-
mutations是对state中的状态数据进行管理.
const store = new Vuex.Store({ state: { counter: 1000 }, mutations: { // 提供一个increment方法,供组件进行调用 increment(state) { state.counter++; }, // 提供一个increment方法,供组件进行调用 decrement(state) { state.counter-- } }, actions: {}, getters: {}, modules: {} })
-
组件调用mutations中的方法
<script> export default { name: 'HelloWorld', props: { msg: String }, methods: { add() { this.$store.commit('increment') // 调用increment方法 }, sub() { this.$store.commit('decrement') // 调用decrement方法 } } } </script>
-
示例2:
-
需求: 对counter每次添加一个任意数
-
mutations方法定义incrementNum
mutations: { // 定义一些方法 increment(state) { state.counter++; }, decrement(state) { state.counter-- }, // 每次添加x incrementNum(state, number) { return state.counter += number } },
-
组件调用mutations中的incrementNum方法
<button @click="addNum(5)">+5</button> <button @click="addNum(10)">+10</button>
methods: { add() { this.$store.commit('increment') }, sub() { this.$store.commit('decrement') }, addNum(number) { this.$store.commit('incrementNum',number) } }
-
看得出来,mutations中方法传参很方便. 当然也可以使用对象传参.
-
-
示例3. 响应式新增/删除属性
-
state: { info: { name: 'jet', age: 19, gender: 'female' } },
-
mutations: { updateInfo(state) { // 给info添加一个address属性. // state.info.address='bj' // 这种方式可以修改state中的info属性,但是info不会响应式更新组件 Vue.set(state.info, 'address', 'Bj') // 使用这种方式,可以响应式的更新info中的属性. // 删除属性 // delete state.info.age // 不是响应式 Vue.delete(state.info,'age') // 响应式的删除info中的age属性 } },
-
4.3 getters
-
如果想要获取state中数据产生的变异结果,就可以使用getters,它相当于vue中的计算属性
-
示例1
const store = new Vuex.Store({ state: { counter: 1000 }, mutations: { increment(state) { state.counter++; }, decrement(state) { state.counter-- } }, actions: {}, getters: { powerCounter(state) { // 定义一个方法 return state.counter * state.counter } }, modules: {} })
-
Vue组件如何调用getters方法
<h2>演示getters方法: {{$store.getters.powerCounter}}</h2>
-
示例2
const store = new Vuex.Store({ state: { counter: 1000, students:[ {id:1,name:'kobe',age:30}, {id:2,name:'jet',age:18}, {id:3,name:'kk',age:39}, {id:4,name:'jj',age:26}, ] }, mutations: { // 定义一些方法 increment(state) { state.counter++; }, decrement(state) { state.counter-- } }, actions: {}, getters: { powerCounter(state) { // 定义一个方法 return state.counter * state.counter }, // 获取年纪大于20的student ageGt20(state) { return state.students.filter(x => x.age > 20); } }, modules: {} })
<h2>HelloWorld,演示getters方法: {{$store.getters.ageGt20}}</h2>
<h1>APP 演示getters{{$store.getters.ageGt20}}</h1>
可以看到在使用getters的好处是,定义一个方法,在项目中任一组件中都可以方便的调用. -
-
示例2也可以使用计算属性进行实现
computed: {
ageMore20() {
return this.$store.state.students.filter(x => x.age > 20)
}
},
<h2>HelloWorld,使用计算属性: {{ageMore20}}</h2>
但是,使用计算属性有个缺点,那就是在哪个组件中声明就只能在那个组件中使用.
-
getters中定义的方法还可以传入getters作为入参
getters: { powerCounter(state) { // 定义一个方法 return state.counter * state.counter }, // 获取年纪大于20的student ageGt20(state) { return state.students.filter(x => x.age > 20); }, // 获取年纪大于20岁的student的个数 ageGt20Count(state,getters) { return getters.ageGt20.length } },
总结 : getters中定义的方法可以有两个入参,第1个是state,第2是getters本身.
-
示例3 (高级版)
-
需求: 获取年纪大于x岁的学生信息
-
vuex中getters中的代码
const store = new Vuex.Store({ state: { counter: 1000, students:[ {id:1,name:'kobe',age:30}, {id:2,name:'jet',age:18}, {id:3,name:'kk',age:39}, {id:4,name:'jj',age:26}, ] }, mutations: { // 定义一些方法 increment(state) { state.counter++; }, decrement(state) { state.counter-- } }, actions: {}, getters: { powerCounter(state) { // 定义一个方法 return state.counter * state.counter }, // 获取年纪大于20的student ageGt20(state) { return state.students.filter(x => x.age > 20); }, // 获取年纪大于20岁的student的个数 ageGt20Count(state,getters) { return getters.ageGt20.length }, // 获取年纪大于x岁的student ageGtx(state) { return function (x) { return state.students.filter(stu=> stu.age > x) } } }, modules: {} })
-
组件调用ageGtx
<h2>HelloWorld,演示getters使用,获取年纪大于x岁的: {{$store.getters.ageGtx(10)}}</h2>
-
说明
-
ageGtx(state) { return function (x) { return state.students.filter(stu=> stu.age > x) } }
看这段代码 ,ageGtx返回的是一个function,且这个function还是一个带参的function
-
{{$store.getters.ageGtx(10)}}
{{$store.getters.ageGtx}}这段代码的结果其实就是ageGtx返回的function, 而(10)就是调用这个function,并且入参是10.
-
通过这种方式实现,感觉很巧妙呀.
-
上面的ageGtx可以用箭头函数进行简写
ageGtx(state) { return x => { return state.students.filter(stu => stu.age > x) } }
-
-
4.4 actions
-
所有的异步操作都在actions中完成.
-
示例1
-
Vue组件---> Actions----> Mutations----> State ---> 响应到Vue组件.
-
Mutations中定义的方法,直接操作State
mutations: { updateInfo(state) { // 给info添加一个address属性. // state.info.address='bj' // 这种方式可以修改state中的info属性,但是info不会响应式更新组件 Vue.set(state.info, 'address', 'Bj') // 使用这种方式,可以响应式的更新info中的属性. // 删除属性 // delete state.info.age // 不是响应式 Vue.delete(state.info,'age') // 响应式的删除info中的age属性 } },
-
Actions中定义一个aUpdateInfo方法,方法内部的逻辑是异步执行.
actions: { aUpdateInfo(context) { // 异步方法 setTimeout(() => { context.commit('updateInfo') // 在actions中调用mutations中的updateInfo方法,对state中的info进行修改 }, 1000); } },
-
Vue组件调用Actions中的方法
methods: { updateInfo() { // this.$store.commit('updateInfo')// 这个通过Mutations操作state this.$store.dispatch('aUpdateInfo') // 这个通过Actions操作Mutations,再通过Mutations操作state } }
-
注意: 操作Mutations中方法用commit方法,操作Actions中方法用dispatch
-
-
示例2(传参)
-
Vue组件在调用Actions中的方法时,传参,跟调用Mutations中的方法一样, 可以直接传对象
-
Vue组件的methods属性中定义方法updateXXX
updateXXX() { this.$store.dispatch('updateXXX', '测试传参');// 调用Actions中的方法 }
-
Vuex中Actions定义的updateXXX
updateXXX(context,msg) { setTimeout(()=>{ console.log(msg); // 测试传参 },1000) }
-
-
示例3(回调)
-
需求: 以示例2为基础,如果Actions中的updateXXX方法,打印msg成功之后,给Vue组件发个通知(回调函数)
-
Vue组件的methods属性中定义方法updateXXX
updateXXX() { this.$store.dispatch('updateXXX', { msg:'测试传参', callback: ()=>{ console.log("传参成功,回调打印...") } }); }
-
Vuex中Actions定义的updateXXX
updateXXX(context,payload) { setTimeout(()=>{ console.log(payload.msg); payload.callback() // 执行回调函数 },1000) }
-
如果不传参,在Vue组件的updateXXX方法中,可以直接传函数
updateXXX() { this.$store.dispatch('updateXXX',()=>{ console.log("传参成功,回调打印...") }) }
-
-
示例4(回调,使用Promise,推荐.)
-
Actions中定义updateXXX方法
updateXXX(context, msg) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(msg); resolve("action方法执行完毕,这是一条回调信息") }, 1000) }) }
-
Vue组件methods中定义调用action的方法
updateXXX() { this.$store .dispatch('updateXXX', '我是一条msg') .then(res=>{ console.log('即将打印action的回调信息'); console.log(res) }); }
-
总结 action中的updateXXX返回的是一个Promise对象,所以调用方使用then去接收回调信息.
-
4.5 module
-
使用示例
-
声明一模块a
// 定义一个模块a const a = { state: { name: 'kobe' }, mutations: {}, actions: {}, getters: {} }
-
将模块a挂到Vuex.Store的module中
modules: { a }
-
Vue组件中如何调用a模块中的name属性呢
<h2>模块a中的name : {{$store.state.a.name}}</h2>
-
-
示例2: 如何操作模块a中的mutations方法
-
先在module a 的mutations中定义一个updateName方法
// 定义一个模块a const a = { state: { name: 'kobe' }, mutations: { // 这个state是模块a的state,并非全局的state updateName(state, payload) { state.name = payload; } }, actions: {}, getters: {} }
-
在Vue组件中的methods中定义一个updateName方法,操作mutations中的updateName方法
updateName() { this.$store.commit('updateName', '小明') }
-
结论: 跟操作全局的mutations方法一样的.
-
-
示例3, 如何操作模块a中的getters方法
-
先在模块a定义一个getters方法 say
getters: { say(state) { return "i like " + state.name } }
-
Vue组件使用say方法
<h2>{{$store.getters.say}}</h2>
-
结论: 跟操作全局的getters方法一样.
-
-
示例4, 操作全局的state
- getters方法可以有3个入参:
- 第1个: 本模块的state
- 第2个: 本模块的getters
- 第3个: 全局的state
getters: { say(state) { return "i like " + state.name }, // state : 本模块的state, getters: 本模块的getters say2(state, getters) { return getters.say + ' en, but --------' }, // 全局的state say3(state, getters, rootState) { return getters.say2 + rootState.students[0].name } }
- getters方法可以有3个入参:
-
示例5, 如何操作模块a中的actions方法
-
先定义一个actions方法
mutations: { // 这个state是模块a的state,并非全局的state updateName(state, payload) { state.name = payload; } }, actions: { aUpdateName(context,param) { setTimeout(()=>{ console.log(context); // 操作本模块mutaions中的updateName方法 context.commit('updateName',param) },1000) } },
-
Vue组件调用模块a中的actions方法
asyncUpdateName() { this.$store.dispatch('aUpdateName','jet') }
-
结论 跟调用全局actions方法一样
-
注意 context与全局的context对像不一样, 可以console.log打印, 可知,模块actions中context包含了rootGetters和rootState
-
5. 核心概念
-
State , 单一状态树 , 一个项目中只建一个store对象.
-
Getters , 相当于计算属性, 用于获取State中变异的数据.
-
Mutations , 操作state状态 , 同步方法. 如果是异步方法,devtools就会监控失效.
-
Action , 类似于Mulations一样,用来替代Mulations, 进行异步操作
-
Module , 模块. Vuex推荐使用单一state, 但是如果state中的数据太多,也可以按模块进行划分. 而每个模块里面又可以包含state,mutations, actions,getters
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
6. 目录结构
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
7.表单处理
-
需求: input框是store的state获取数据显示 ,如果input框中的值发生改变,更新state中的值.
-
解决方案: 使用计算属性
<input v-model="message">
computed: { message: { get () { return this.$store.state.obj.message }, set (value) { // 这个value就是input框中更新的值. this.$store.commit('updateMessage', value) } } }
mutations: { updateMessage (state, message) { state.obj.message = message } }
很赞呀!