第一步:vuex是什么
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式 。它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测 的方式发生变化。
第二步:了解vuex
-
何时使用
如果你不打算构建大型项目的单页应用,使用Vuex可能会变得很繁琐,对于大型项目,可以使用Vuex作为不同组件之间的状态管理,而对于小型的项目,推荐使用HTML5特有的属性,localStroage 和sessionStroage 作为数据之间的传递就可以。
-
应用场景如下 :
如果你的项目里有很多页面(组件/视图),并且页面之间存在多级的嵌套关系,此时,这些页面假如都需要共享一个状态的时候,此时就会存在以下两个问题:
👿. 多个(组件/视图)依赖同一个状态 👿. 来自不同(组件/视图)的行为需要变更同一个状态 -
那怎么解决呢(这里先不要考虑vuex) : ??:可以使用父子传参以及bus传参还有路由传参一系列的解决办法,但是如果多级嵌套的话,那就意味着你可能要写很多的重复代码,而且如果项目是大项目,一旦公用状态发生改变,那后期维护起来可能会很麻烦。 ??:可以通过父子组件传参直接引用,或者根据事件变更同步此状态,然后多份copy,但是这种模式很脆弱,同样后期维护起来可能会很麻烦。 -
那以上两种方案均不可行,这里就需要考虑使用vuex 的思路解决此问题: 😃. 我们可以考虑有没有一种机制,可以将组件/视图依赖的这同一个状态给存起来,然后全局使用单例模式进行管理 😃. 这种机制,任何组件都可以访问这个同一状态,或者当此状态发生改变时,所以组件都获得更新 -
这时候,Vuex诞生了!
这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux。与其他模式不同的是,Vuex 是专门为 Vue 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新
第三步 : 安装vuex
npm install vuex --save
第四步 :初始化配置vuex
-
这里我是直接创建了一个新的vue项目,所以默认关于store文件夹下面的index.js文件以及main.js文件已经初始化配置完成,这里不做描述。直接使用: -
?? store/index.js: import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userName:'admin',
userAge:'24',
},
mutations: {},
actions: {},
modules: {}
})
-
😃 然后home.vue获取userName属性: <template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
</div>
</template>
<script>
export default {
name: 'Home',
mounted() {
console.log('userName------',this.$store.state.userName);
},
}
</script>
-
?? 执行npm run serve启动项目,此时可以在控制台输出刚才我们定义在store中的userName的值,如图: -
😃 当然官网上针对获取store中的state下的全局属性,最好放在computed计算属性中,因此我们也可以这样实现 export default {
name: 'Home',
computed:{
getUserName(){
return this.$store.state.userName
}
},
mounted() {
console.log('计算属性获取 userName -------',this.getUserName)
},
}
😃 此时可以得到和上面一样的执行结果。 -
?? 如果需要获取userName以及userAge这两个属性,那么重复写两遍this.$store.state.xxx略显麻烦,这里我们可以采用mapState <script>
import { mapState } from 'vuex';
export default {
name: 'Home',
computed:{
...mapState(['userName','userAge'])
},
mounted() {
console.log('userName -------',this.userName)
console.log('userAge -------',this.userAge)
},
}
</script>
-
😃 执行npm run serve启动项目,结果如图: -
😃 你甚至可以在解构的时候给它赋别名,取外号,就像这样 ...mapState({ name: 'userName',age:'userAge' }),
第五步: 了解读取修饰器 - Getter
-
😈 加入现在你讲userName属性已经取出来并且展示在所有的页面上了,但是现在项目经理突然告诉你说在人员模块取的name前面需要拼接一下 ‘hello’ -
😈 这时候,你第一想到的是怎么加呢,emm… 在需要修改的每个页面上,使用this.$store.state.userName 获取到值之后,进行遍历,前面追加"hello"即可。 解决是解决了,但是这样处理效果很不好,原因如下: -
😈 假如你在A、B、C三个页面都用到了name,那么你要在这A、B、C三个页面都修改一遍,多个页面你都需要修改,造成代码冗余 -
😈 如果下次项目经理让你把 “hello” 改成 “fuck” 的时候,你又得把三个页面都改一遍,啊这就。。。。。。 -
?? 那既然我们介绍到了getter,就得考虑如何用getter修改此问题: -
?? 首先,在index.js中添加getters属性 export default new Vuex.Store({
state: {
userName:'admin',
userAge:'24',
},
getters: {
getName(state) {
return `hello${state.userName}`;
},
},
})
-
?? 获取修改之后的值,这里我们还是直接采取mapGetters的方法获取 <script>
import { mapState,mapGetters } from 'vuex';
export default {
name: 'Home',
computed:{
...mapState(['userName','userAge']),
...mapGetters(['getName'])
},
mounted() {
console.log('userName -------',this.userName)
console.log('userAge -------',this.userAge)
console.log('拼接 hello ------ ',this.getName)
},
}
</script>
-
😃 然后查看控制台的输出: -
😃 好了, 至此读取值的操作我们有 原生读(state)” 和 “修饰读(getters) ,接下来就要介绍怎么修改值了!
第六步:如何修改值 - Mutation
-
👿 如果要修改store中的变量,不能直接 this.$store.state.xxx=xxxx 修改,这是错误的,因为在vuex中,我们不能直接修改仓库里的值,必须用vuex自带的方法去修改,这时候就得用 mutations -
😃 mutations 的原理是用来触发事件,相当于方法。用户需要通过触发这个方法,借此来保存数据,参数的话,第二个参数就是用户传入的值,然后在方法中赋值给state中的变量 -
?? 假如我们现在有一个需求:用户登录进来之后,我们需要将用户名获取并且保存,然后相关接口传参的时候都得将获得的用户名传值过去。 -
😃 首先,index.js代码如下 import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userName:'',
},
mutations: {
getUserName(state,data){
state.userName=data.userNamee)
}
},
})
-
😃 对应home.vue在获得用户名之后调用getUserName方法将值保存在vuex,这里有两种方案可以调用mutations中的方法 -
?? 第一种: this.$store.commit(‘对应方法名’,‘需要保存的用户名’) <script>
export default {
name: 'Home',
mounted() {
this.getLogin()
console.log('新值 username ------ ',this.$store.state.userName)
},
methods:{
getLogin(){
this.$store.commit('getUserName',{userName:'0817'})
}
}
}
</script>
-
?? 第二种:利用mapMutations引入,只不过这里不再是解构在计算属性中,而是直接将方法映射在methods中 <script>
import { mapMutations } from 'vuex';
export default {
name: 'Home',
mounted() {
this.getLogin()
console.log('新值 username ------ ',this.$store.state.userName)
},
methods:{
...mapMutations(['getUserName']),
getLogin(){
this.getUserName({userName:'0817'})
}
}
}
</script>
-
😃 以上两种方法均可以,实现效果一致,然后运行项目查看控制台输出如图: -
且记:Mutations里面的函数必须是同步操作,不能包含异步操作! -
那总结到这里,mutations算是总结完了,但是刚才提醒了Mutations里面的函数必须是同步操作,那什么可以进行异步呢,这里,就得引出action
第七步:异步操作:Actions
-
😃 Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions -
😃 假设现在我们模拟一个异步操作 import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userName:'0817',
},
mutations: {
getUserName(state,data){
state.userName=data.userName
}
},
actions: {
setName(content,payload) {
return new Promise(resolve => {
content.commit('getUserName',payload);
resolve();
});
},
},
})
-
同理,也有两种方式可以调用action中的方法 -
😃 第一种:直接利用**this.$store.dispatch(‘对应action中药调用的方法名’,‘payload参数’) ** <script>
export default {
name: 'Home',
mounted() {
console.log('旧值 username ------ ',this.$store.state.userName)
this.$store.dispatch('setName',{userName:'csAdmin'})
console.log('新值 username ------ ',this.$store.state.userName)
},
}
</script>
-
😃 第二种:利用mapActions引入,只不过这里不再是解构在计算属性中,而是直接将方法映射在methods中 <script>
import { mapActions} from 'vuex';
export default {
name: 'Home',
methods:{
...mapActions(['setName'])
}
mounted() {
console.log('旧值 username ------ ',this.$store.state.userName)
this.setName({userName:'csAdmin'})
console.log('新值 username ------ ',this.$store.state.userName)
},
}
</script>
-
?? 以上两种方法均可以,实现效果一致,然后运行项目查看控制台输出如图: -
?? ?? ?? 至此,整个vuex基本概念加如何使用已总结完毕。 这里附上vuex的运行过程,官网的图片: -
😃 组件派发任务到actions,actions触发mutations中的方法,然后mutations来改变state中的数据,数据变更后响应推送给组件,组件重新渲染 😃 -
?? 接下来想象一下,以上介绍的store/index.js里面的内容是非常少的,如果你是一个稍微有些规格的项目,那么你将会得到一个成百上千行的index.js,然后查找修改一些东西就会非常费劲,因此我们考虑一下如何优化store。这里常用的方案有两种:
第八步:按照属性拆分index.js
-
😃 正如我们所知道的store对象中包含四个属性,如图: -
😃 因此,我们可以考虑在store文件夹下创建四个对应的js文件夹: -
?? 拆出来state放在state.js中 export const state = {
userName: '0817',
};
-
?? 拆出来getters放在getters.js中 export const getters = {
getName(state) {
return `hello${state.userName}`;
},
};
-
?? 拆出来mutations放到mutations.js中: export const mutations = {
getUserName(state,data){
state.userName=data.userName
}
};
-
?? 拆出来actions放到actions.js中: export const actions = {
setName(content,payload) {
return new Promise(resolve => {
content.commit('getUserName', payload);
resolve
});
},
}
-
😃 然后将以上四个文件在组装到主文件index.js里面 import Vue from 'vue'
import Vuex from 'vuex'
import { state } from './state';
import { getters } from './getters';
import { mutations } from './mutations';
import { actions } from './actions';
Vue.use(Vuex)
export default new Vuex.Store({
state: state,
getters: getters,
mutations: mutations,
actions: actions
})
-
?? 然后对应的页面调用或者获取state中的变量时,还是按照上面的方式获取调用。
第九步:按功能模块进行拆分 - Module+命名空间的概念
-
😃 假如我们现在有一个商品模块,但是刚才的store中存的都是user相关的信息,如果再加入商品对应的信息,难免有点不好管理,这样我们可以直接考虑增加商品模块 -
?? 创建store2.js模块 export const store2 ={
namespaced:true,
state:{
money:''
},
getters:{},
mutations:{
getMoney(state,data){
state.money=data.money
}
},
actions:{}
}
-
?? 然后在index.js中引入我们新创建的store2模块: import Vue from 'vue'
import Vuex from 'vuex'
import { state } from './state';
import { getters } from './getters';
import { mutations } from './mutations';
import { actions } from './actions';
import {store2} from './store2';
Vue.use(Vuex)
export default new Vuex.Store({
state: state,
getters: getters,
mutations: mutations,
actions: actions,
modules:{
store2
}
})
-
?? 获取模块2中对应的变量 <script>
import { mapState,mapMutations } from 'vuex';
export default {
name: 'Home',
computed:{
...mapState({
money:state=>state.store2.money
}),
},
methods:{
...mapMutations(['store2/getMoney'])
},
mounted() {
console.log('旧值 money ------ ',this.money)
this['store2/getMoney']({money:'8989'})
console.log('新值 money ------ ',this.money)
},
}
</script>
-
?? 查看控制台输出: -
😃 到这里,模块化开发的基本实现算是总结完了,但是这里有个属性 namespaced,它的取值会影响我们如何获取对应模块的变量以及方法,因此以下对此属性做一个简单的概述。 -
😃 namespaced: true 保证内部模块的高封闭性; -
?? 命名空间的概念: -
?? 默认情况下,模块内部的 action、mutation 和 getter 是 注册在全局命名空间 的, 可以直接调用(除了访问state以及getter中内容需要加模块名,其他访问模块中的内容直接访问,不需要加模块名 ), 这样一来,不仅容易和其他模块, 同名state或者函数发生冲突,也很难让人直观得看出具体是在哪个子模块调用的。 -
😃 所以在子模块的配置项目中, 必须添加 namespaced: true 属性来开启命名空间, 便于区分和其他模块及主模块中的同名状态或者函数, 防止冲突,开启后需要访问子模块中的内容就需要带模块名。 -
😃 开启命名空间后如何使用? -
?? state
- 插值表达式使用 :
$store.state.模块名.模块属性 - 映射为辅助函数 - 数组格式:
...mapState('模块名', ['属性名']) - 映射为辅助函数 - 对象格式:
...mapState({属性名:state=>store.模块名.属性名}) - 在methods中调用
this.$store.state.模块名.模块属性 -
😃 getters
- 插值表达式使用 :
$store.getter.[模块名/模块属性] - 在methods中调用:
this.$store.getters.[模块名/模块属性] - 映射为辅助函数 - 数组格式:
...mapGetters('模块名', ['属性名']) - 注意:这里是通过
[模块名/模块属性] 获取,和上面的state是有区别的 !! -
?? mutations
- 触发方法 :
this.$store.commit('模块名/mutations中的方法名', '实参') - 映射为辅助函数 - 数组格式:
...mapMutations(['模块名/mutations中的方法名']) - 映射为辅助函数 - 方法调用 :
this['模块名/mutations中的方法名'] (参数) -
😃 actions
- 触发方法 :
this.$store.dispatch('模块名/actions中的方法名', '实参') - 在标签的事件上(如点击、滑动)触发 :
$store.dispatch('模块名/actions中的方法名', '实参') - 映射为辅助函数 - 数组格式:
...mapActions(['模块名/actions中的方法名']) - 映射为辅助函数 - 方法调用 :
this['模块名/actions中的方法名'] (参数)
|