VueX
一、 Flux
vuex是一个解决组件之间通信,实现组件之间共享数据的框架。
参考了flux思想实现的框架。
flux实现了单一数据源,数据单向流动等特征。
flux包含了四个模块
action 消息对象
dispatcher 捕获消息的
store 存储数据的
views 组件视图
通信流程
一个组件发布了一个action对象。action被dispatcher捕获,根据消息类型处理数据。store会存储新的数据,并将新的数据传递给另一个组件。
二、 VueX
vuex就是基于flux思想实现的框架,也实现了单一数据源,数据单向流动等特征。
单一数据源:一个应用程序中,只能有一个store对象(用来存储数据的)
数据单向流动:数据始终朝着一个方向传递(形成环路)。
优势: 应用中可能有很多环,但是彼此之间没有联系,所以在一个环中添加一个成员或者删除一个成员,只影响当前的环,不影响其它的环,因此我们可以将系统中的所有环抽象成一个环。
vuex由三部分组成:
action 消息对象
state 用来存储数据的
views 组件视图
通信流程
一个组件发布了消息。
消息被action捕获,并根据消息类型,处理数据
数据改变了,存储在state中,
state数据更新了,将新的数据传递给另一个组件。
2.1 action 分类
在vuex中,action共分两类:
同步action:mutations
处理同步的消息对象(只能执行同步操作)
可以被测试
异步action: actions
处理异步的消息对象(可以执行异步操作)
无法测试,所以为了测试,要再发布一个同步消息。
注意:如果没有异步的操作,我们是可以直接发布同步action的(mutations)。
2.2 store 组成
在vuex中,我们要创建store对象(代表vuex部分)
在一个应用程序中,只能有且只有一个store对象:store由以下几部分组成:
state 用来存储静态数据的。store中的数据放在state中存储。
getters 用来存储计算属性数据的。getters与state的区别与组件中data与computed区别是一样的。
mutations 用来订阅同步消息的。用commit方法提交同步消息。
actions 用来订阅异步消息的。用dispatch方法提交异步消息。
mudules 用来切割store的
规范:只能在mutations中修改state,不能在actions中修改state。
2.3 使用 vuex
安装vuex:我们通过npm安装vuex模块,npm install vuex
使用vuex大致分成五步:
第一步 安装vuex:Vue.use(Vuex)。vue家族的插件,都可以使用Vue.use方法来安装。
注意:在模块化开发中要安装(除了seajs),不使用模块化开发规范,不需要安装
第二步 创建store对象,并传递各个组成部分。创建:new Store()
组成部分:state, getters, mutations, actions, mudules …
第三步 在vue实例化对象中,通过store属性注册store实例化对象。
注册目的:让所有的组件都可以获取$store对象。
第四步 在一个组件中发布消息
第五步 在一个组件中使用数据
2.4 同步消息
同步消息定义在mutations中,是一个对象
key 表示消息名称
value 表示消息回调函数
第一个参数表示state数据对象
第二个参数表示commit方法提交的数据
在方法中,我们用新的数据,更新state数据对象。
commit方法:用来提交同步消息的方法
第一个参数表示消息类型
第二个参数表示提交的数据
注意:数据只能在第二个参数中传递,想传递多个数据,可以放在数组或者对象中。
2.5 异步消息
我们通过actions定义异步消息,是一个对象
key 表示消息名称
value 表示消息回调函数
第一个参数表示store对象
第二个参数表示dispatch方法提交的数据
在方法中,我们实现异步操作,但是不要修改state
dispatch:用来提交异步消息的方法。
第一个参数表示消息名称
第二个参数表示提交的数据
注意:数据只能在第二个参数中传递,想传递多个数据,可以放在数组或者对象中。
2.6 严格模式
vuex的规范:只能在mutations修改state数据,不能在其它的位置修改state数据。
vuex为了严格限制这一规范,提供了严格模式:
在store实例化对象中,设置strict: true。
这样当我们在其它位置修改state数据的时候,就会抛出警告错误。
01 异步消息 + 严格模式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="reduce">减 2</button>
<hr>
<add-num></add-num>
<show-num></show-num>
</div>
<script src="./dist/01.js"></script>
</body>
</html>
01 .js
import Vue from 'vue';
import Vuex,{ Store } from 'vuex';
// 安装 vuex
Vue.use(Vuex);
let store =new Store({
// 进入严格模式
strict:true,
state:{
num:0
},
mutations:{
addNum(state,num){
state.num +=num
},
reduce(state,num){
state.num -=num
},
// 清空数据
resetNum(state,num){
state.num = num
}
},
// 异步的方法
actions:{
clear(store,num){
// console.log('clear',arguments);
// 3 秒之后修改
setTimeout(()=>{
// 提交同步的消息
store.commit("resetNum",num)
// 严格模式下不可以 直接修改
// store.state.num = 100;
},3000)
}
}
})
// 第一个组件
let AddNum = Vue.extend({
template:`
<div>
<button @click="$store.commit('addNum',5)">加5</button>
<hr>
<!-- commit 不能发布异步消息 -->
<!-- <button @click="$store.dispatch('clear',[100,200,false])">3 秒后清零</button> -->
<button @click="$store.dispatch('clear',0)">3 秒后清零</button>
<!-- 严格模式下不乐意这样修改 -->
<!-- <button @click="$store.state.num = 500">3 秒后清零</button> -->
</div>
`
})
// 定义 第二个组件
let ShowNum = Vue.extend({
template:`
<div>
<h6>{{$store.state.num}}</h6>
</div>
`
})
let app = new Vue({
el:"#app",
data:{},
// 注册 state
store,
// 注册组件
components:{
'addNum':AddNum,
'showNum':ShowNum
},
methods:{
reduce(){
this.$store.commit('reduce',2)
}
}
})
效果图
2.7 getters
store中存储的计算属性数据,可以对state中的数据动态处理;
是一个对象
key 表示数据名称
value 计算的方法
第一个参数表示当前的state数据
第二个参数表示当前的getters数据
必须有返回值,表示计算的结果
2.8 mapState
对state数据做处理,并返回映射的结果
参数是对象
key 结果对象的属性名称
value 结果对象的属性值
可以是字符串:与state中的数据名称对应
可以是函数:对state做处理并返回新结果。
第一个参数是state
第二个参数是getters
解决了访问数据的时候,每次都要写this.$store的重复书写问题。简化了对state数据的访问。
02 getters 与 mapState.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="reduce">减2</button>
<hr>
<add-num></add-num>
<hr>
<show-num></show-num>
</div>
<script src="./dist/02.js"></script>
</body>
</html>
02 .js
import Vue from 'vue';
import vuex, { Store, mapState } from 'vuex';
// 安装 vuex
Vue.use(vuex);
// 实例化
let store = new Store({
// 严格模式
strict: true,
state: {
num: 0,
color: 'red'
},
// 同步的方法
mutations: {
addNum(state, num) {
state.num += num
},
reduceNum(state, num) {
state.num -= num
},
// 清空数据
resetNum(state, num) {
state.num = num
}
},
// 异步的方法
actions: {
clear(store, num) {
setTimeout(() => {
store.commit('resetNum', num)
}, 3000)
}
},
// 计算属性数据
getters: {
dateMsg(state, ...args) {
// console.log(state, args);
return state.num * 2
}
}
})
// 第一个子组件
let AddNum = Vue.extend({
template: `
<div>
<button @click="$store.commit('addNum',5)">加5 </button>
<button @click="$store.dispatch('clear',0)">3 秒后数据清零</button>
</div>
`,
})
// 定义第二个子组件
let ShowNum = Vue.extend({
template: `
<div>
<h5>{{$store.state.num}}   ----   {{$store.getters.dateMsg}}  ---   {{$store.state.color}}</h5>
<h4>使用 mapState 的方法 --- {{num}} ---- {{dzxColor}} --- {{dateMsg}} --- {{dealMsg}}</h4>
<hr>
<h4>{{compMsg}}</h4>
</div>
`,
data() {
return {
msg: "dazhaxie"
}
},
// 使用 计算属性数据 中的 mapState 方法 简化 $stor.state.color 打点形式的书写
// computed: mapState({
// num: 'num',
// dzxColor: 'color',
// // 不能映射 getters 中的数据
// // dateMsg:'dateMsg'
// dateMsg(state, getters) {
// // console.log(arguments);
// return getters.dateMsg
// },
// // 处理数据
// dealMsg(state) {
// return this.msg + state.num
// }
// })
// 上面的用法无法继续添加计算属性数据了
computed: {
...mapState({
num: 'num',
dzxColor: 'color',
// 不能映射 getters 中的数据
// dateMsg:'dateMsg'
dateMsg(state, getters) {
// console.log(arguments);
return getters.dateMsg
},
// 处理数据
dealMsg(state) {
return this.msg + state.num
}
}),
// 继续添加其它数据
compMsg(){
return this.msg.toUpperCase()
}
}
})
let app = new Vue({
el: "#app",
data: {},
// 注册 store
store,
// 注册组件
components: {
'addNum': AddNum,
'showNum': ShowNum
},
methods: {
// 减数字
reduce() {
this.$store.commit('reduceNum', 2)
}
},
// created() {
// console.log(this.$store, "11111");
// }
})
效果图
三、模块切割
在一个非常复杂的应用中,共享的数据会有很多,所有的数据都直接写在state中,就可能会产生命名空间冲突的问题,为了解决冲突问题,vue提供了mudules属性,允许我们对模块切割。
属性值是一个对象:key表示命名空间,value表示该命名空间对应的子store对象。
在store中,数据作为state来存储的,因此切割store的本质就是切割state,因此切割后,只有state需要携带命名空间,其它的的数据正常访问。
切割的子store对象理论上也是store对象,因此可以设置store中的所有组成部分:mutations,actions, state, getters, modules等。切割后,各个部分如果重名的影响
1 state:由于具有命名空间,因此不受影响。
2 getters: 各个store之间的getters数据不允许同名。所有的getters都添加在全局。
3 actions和mutations:可以正常访问:全局的消息修改全局的数据,局部的消息修改局部的数据。
03 模块切割.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="reduceNum">减2</button>
<hr>
<add-num></add-num>
<hr>
<shou-num></shou-num>
</div>
<script src="./dist/03.js"></script>
</body>
</html>
03 .js
import Vue from 'vue';
import vuex, { mapState, Store } from 'vuex';
// state: 由于具有命名空间,不受影响
// getters: 各个 store 之间的 getters 数据不允许同名.所有的 getters 都添加在全局
// actions 和 mutations : 可以正常访问:全局的消息修改全局的消息,局部的消息修改局部的数据
Vue.use(vuex);
let store = new Store({
// 开启严格模式 限制异步数据的修改
strict: true,
// 还可以在全局继续定义数据
state:{
num:100
},
// 定义全局的 同步 增加数据
mutations:{
addNum(state,num){
state.num +=num
}
},
// 全局的计算属性数据 不允许和局部的 计算属性数据同名 报错
getters:{
// 不允许重名
// dealMsg(state,...args){
// // console.log(args);
// return state.num *2
// }
},
// 模块的切割
modules: {
// 子 store 的命名空间
dzx: {
state: {
num: 0,
color: 'blue'
},
// 同步的事件
mutations: {
addNum(state, num) {
state.num += num
},
reduceNum(state, num) {
state.num -= num
},
// 3 秒 清空数据
resetNum(state, num) {
state.num = num
}
},
// 异步的事件
actions: {
clear(state, num) {
setTimeout(() => {
state.commit('resetNum', num)
}, 3000)
}
},
// 计算属性数据
getters: {
dealMsg(state, ...args) {
// console.log(args,"1232");
return state.num * 2
}
},
}
}
})
// 定义第一个组件
let AddNum = Vue.extend({
template: `
<div>
<button @click="$store.commit('addNum',5)">加5</button>
<button @click="$store.dispatch('clear',0)">3 秒后数据归零</button>
</div>
`,
})
// 定义第二个组件
let ShouNum = Vue.extend({
// 切割之后,只有 state 需要携带命名空间,其他的直接访问 例如 getters
template: `
<div>
<h6> 命名空间下的数据 ----{{$store.state.dzx.num}} ---- {{$store.getters.dealMsg}} ---- {{$store.state.dzx.color}}</h6>
<hr>
<h6> 全局下可以继续访问全局的数据 --- {{$store.state.num}}</h6>
</div>
`,
// created(){
// console.log(this,"33333");
// }
})
let app = new Vue({
el: "#app",
store,
components: {
'addNum': AddNum,
'shouNum': ShouNum
},
methods: {
reduceNum() {
this.$store.commit('reduceNum', 2)
}
},
})
效果图
四、插件
为了测试vuex,vuex提供了测试插件:vuex/dist/logger.js.
该插件可以帮助我们测试同步消息(mutations),不能测试异步消息(actions)
是一个方法,执行后才能被使用。
使用插件:在store对象中的plugins中定义插件。
使用插件的目的:为了复用插件对store拓展的功能。
subscribe:store对象提供了subscribe方法,可以监听store的变化。
参数是回调函数,表示当发布同步消息的时候,执行的方法
第一个参数表示消息对象:消息类型,传递的数据,
第二个参数表示state对象。
根据state数据来判断执行的结果是否符合预期。
只能监听同步消息,无法监听异步消息。
自定义插件
自定义插件就是让我们将对store的拓展封装起来,在不同的项目中复用。
插件是一个方法,
参数是store对象。
在方法中,我们对store拓展
04 使用插件 和 自定义插件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="reduceNum">减2</button>
<hr>
<add-num></add-num>
<hr>
<shou-num></shou-num>
</div>
<script src="./dist/04.js"></script>
</body>
</html>
04 .js
import Vue from 'vue';
import vuex, { mapState, Store } from 'vuex';
// ⭐ 引入测试的插件
// import logger from 'vuex/dist/logger';
// ⭐⭐⭐ 引入自定义的插件
import dzx from './vuex.plugin';
// state: 由于具有命名空间,不受影响
// getters: 各个 store 之间的 getters 数据不允许同名.所有的 getters 都添加在全局
// actions 和 mutations : 可以正常访问:全局的消息修改全局的消息,局部的消息修改局部的数据
Vue.use(vuex);
let store = new Store({
// 开启严格模式 限制异步数据的修改
strict: true,
// 还可以在全局继续定义数据
state:{
num:100
},
// ⭐ 使用插件
// plugins:[
// // 每一个成员代表一个插件
// logger()
// ],
// // ⭐⭐⭐ 使用自定义的插件
plugins:[
dzx
],
// 定义全局的 同步 增加数据
mutations:{
addNum(state,num){
state.num +=num
}
},
// 全局的计算属性数据 不允许和局部的 计算属性数据同名 报错
getters:{
// 不允许重名
// dealMsg(state,...args){
// // console.log(args);
// return state.num *2
// }
},
// 模块的切割
modules: {
// 子 store 的命名空间
dzx: {
state: {
num: 0,
color: 'blue'
},
// 同步的事件
mutations: {
addNum(state, num) {
state.num += num
},
reduceNum(state, num) {
state.num -= num
},
// 3 秒 清空数据
resetNum(state, num) {
state.num = num
}
},
// 异步的事件
actions: {
clear(state, num) {
setTimeout(() => {
state.commit('resetNum', num)
}, 3000)
}
},
// 计算属性数据
getters: {
dealMsg(state, ...args) {
// console.log(args,"1232");
return state.num * 2
}
},
}
}
})
// 定义第一个组件
let AddNum = Vue.extend({
template: `
<div>
<button @click="$store.commit('addNum',5)">加5</button>
<button @click="$store.dispatch('clear',0)">3 秒后数据归零</button>
</div>
`,
})
// 定义第二个组件
let ShouNum = Vue.extend({
// 切割之后,只有 state 需要携带命名空间,其他的直接访问 例如 getters
template: `
<div>
<h6> 命名空间下的数据 ----{{$store.state.dzx.num}} ---- {{$store.getters.dealMsg}} ---- {{$store.state.dzx.color}}</h6>
<hr>
<h6> 全局下可以继续访问全局的数据 --- {{$store.state.num}}</h6>
</div>
`,
// created(){
// console.log(this,"33333");
// }
})
let app = new Vue({
el: "#app",
store,
components: {
'addNum': AddNum,
'shouNum': ShouNum
},
methods: {
reduceNum() {
this.$store.commit('reduceNum', 2)
}
},
})
// ⭐⭐ 使用自定义的插件
// console.log(store);
// 监听 同步消息
// 将 自定义的监听封装成自定义的插件 创建文件 vuex.plugin.js
// store.subscribe((actions,state,...args)=>{
// console.log(actions.type,actions.payload,state.dzx.num,state.num);
// })
vuex.plugin.js
// 插件是一个方法,参数是 store 对象
export default function (store) {
// 监听同步消息
store.subscribe((actions, state, ...args) => {
console.log(actions.type, actions.payload, state.dzx.num, state.num);
})
}
**效果图 **
五、state代理
当我们在表单中,想对state中的数据实现数据双向绑定,我们可以使用state代理技术。
由于我们不能在store的外部修改state,因此我们要通过计算属性数据代理state;
为计算属性数据定义特性对象
在取值器方法中,获取state数据
在赋值器方法中,修改state数据
05 state 代理.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 开启严格模式 此方法直接修改会报错 -->
<!-- <input type="text" v-model="$store.state.msg"> -->
<!-- 绑定计算属性数据 -->
<input type="text" v-model="dealMsg">
<!-- <h5>{{msg}}</h5> -->
<h5>{{$store.state.msg}}</h5>
<hr>
<home></home>
</div>
<script src="./dist/05.js"></script>
</body>
</html>
05 .js
import Vue from 'vue';
import vuex,{ Store } from 'vuex';
Vue.use(vuex);
let store = new Store({
// 开启严格模式
strict:true,
state:{
msg:"hello dazhaxie"
},
// 同步数据
mutations:{
// 更新 msg
updateMag(state,msg){
// 同步消息中,可以修改 state 数据
state.msg = msg
}
}
})
let Home = Vue.extend({
template:`
<div>
<h6>{{$store.state.msg}}</h6>
</div>
`
})
let app = new Vue({
el:"#app",
store,
data:{
msg:"dazhaxie"
},
// 注册组件
components:{
'home':Home
},
// 计算属性数据
computed:{
// 第一种写法
// dealMsg(){}
// 第二种写法
dealMsg:{
set(value){
// console.log(value);
// 不能在外部直接修改 state 数据,必须发布同步消息
this.$store.commit("updateMag",value)
},
get(){
return this.$store.state.msg
}
}
}
})
效果图