【初学者笔记】一文学会使用Vuex

2022-12-10 11:17:15 浏览数 (1)


简介,安装与初始化

什么是vuex

VueX是适用于在Vue项目开发时使用的状态管理工具。Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——VueX。在具有VueXVue项目中,我们只需要把这些值定义在VueX中,即可在整个Vue项目的组件中使用。

如何安装vuex

npm安装

代码语言:javascript复制
npm i vuex -s

如何使用vuex

在项目的根目录下新增一个store文件夹,在该文件夹内创建index.js

此时项目的src文件夹是这样的

代码语言:javascript复制
│  App.vue
│  main.js
│
├─assets
│      logo.png
│
├─components
│      HelloWorld.vue
│
├─router
│      index.js
│
└─store
       index.js

在store.js文件中,引入vuex并且使用vuex,这里注意变量名是大写Vue和Vuex

代码语言:javascript复制
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
//创建VueX对象
const store = new Vuex.Store({
    state:{
        //存放的键值对就是所要管理的状态
        name:'helloVueX'
    }
})
export default store

将store挂载到当前项目的Vue实例当中去

代码语言:javascript复制
//main.js
import store from './store'
new Vue({
  el: '#app',
  router,
  store,  // 和router一样,将我们创建的Vuex实例挂载到这个vue实例中
  render: h => h(App)
})

在组件中使用Vuex

例如在App.vue中,我们要将state中定义的name拿来在h1标签中显示

代码语言:javascript复制
<template>
    <div id='app'>
        name:
        <h1>{{ $store.state.name }}</h1>
    </div>
</template>

或者要在组件方法中使用

代码语言:javascript复制
methods:{
    add(){
      console.log(this.$store.state.name)
    }
},

具体的使用方法下面会详细介绍

注意:vuex的出现是为了解决组件间的通信问题,如果某个操作或者数据不涉及到公共操作,只是单一组件操作,不要把这些状态值或者function存储到vuex中,因为vuex会把自身挂载到所有组件上,不管当前组件是否用到里面的东西,因此这事实上肯定增加了性能的损耗的.

VueX中的核心内容

vuex中,有默认的五种基本的对象:

  • state:存储状态(变量)
  • getters:对数据获取之前的再次编译,可以理解为state的计算属性。
  • mutations:修改状态,并且是同步的。这个和我们组件中的自定义事件类似。
  • actions:异步操作。
  • modulesstore的子模块

拆分成单文件

如果项目中的状态和方法过多,index.js文件看起来就会很臃肿并且不好维护,这个时候我们就可以把state,getters,mutations,actions拆分成单个文件,有利于进行管理

此时目录结构是这样的

代码语言:javascript复制
store
│      
│
├─index.js
│      
│      
├─state.js
│      
│
├─getters.js
│      
│
├─mutations.js
│      
│
└─actions.js

index.js

代码语言:javascript复制
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,
  mutations,
  actions,
  getters,
});

其他的文件中只需要export导出即可

state.js

代码语言:javascript复制
export default {
  name:'hzw'
};

mutations.js

代码语言:javascript复制
export default {
 changeName(state, name) {
    state.name = name;
  },
};

getters.js

代码语言:javascript复制
export default {
 realName(state) {
    return "姓名:"   state.name
  },
};

actions.js

代码语言:javascript复制
export default {
 changeName({ commit }, name) {
        return commit('changeName', name)
    }
};

这样看起来就更有结构感,也更易于维护了

state以及mapState

什么是state

state(vuex) ≈ data (vue)

vuexstatevuedata有很多相似之处,都是用于存储一些数据,或者说状态值.这些值都将被挂载数据和dom的双向绑定事件,也就是当值改变的时候可以触发dom的更新.

我们在state.js中声明一个状态count,初始值为0,然后在组件中输出它

代码语言:javascript复制
// state.js 
export default {
  count:'0'
};
代码语言:javascript复制
//组件中
<template>
  <div class="hello">
    <h3>{{$store.state.count}}</h3>
  </div>
</template>

结果如下图所示

注意:虽然state和data有很多相似之处,但state在使用的时候一般被挂载到子组件的computed计算属性上,这样有利于state的值发生改变的时候及时响应给子组件.如果用data去接收store.state,也是可以接收到值的,但是由于这只是一个简单的赋值操作,所以state中的状态改变的时候不能被vue中的data监听到.也可以通过watch store去解决这个问题,但是稍微有点麻烦.

所以,最好还是用computed去接收state,如下,修改state的方法后面会学习,这里先进行展示.

代码语言:javascript复制
//mutations.js
export default {
  add(state, n = 0) {
    return (state.count  = n)
  },
  reduce(state, n = 0) {
    return (state.count -= n)
  }
}
代码语言:javascript复制
//组件中
<template>
  <div class="hello">
    <h3>{{$store.state.count}}</h3>
    <div>
      <button @click="add(10)">增加</button>
      <button @click="reduce(10)">减少</button>
      <div>computed:{{dataCount}}</div>
        <div>data: {{count}}</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
   data () {
    return {
      dataCount: this.$store.state.count //用data接收
    }
  },
  computed:{
    count(){
      return this.$store.state.count //用computed接收
    }
  },
  methods: {
    add(n){
      this.$store.commit('add',n);
    },
   reduce(n){
      this.$store.commit('reduce',n);
    }
  }
}
</script>

然后我们点击增加按钮,看看会发生什么变化,结果如下

可以看到,用data接收的值不能及时响应更新,用computed就可以.

知识点:Propsmethods,datacomputed的初始化都是在beforeCreatedcreated之间完成的。

什么是mapState

表面意思:mapStatestate的辅助函数

实际作用:当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,可以使用 mapState 辅助函数帮助生成计算属性

使用方法:先要导入这个辅助函数.

代码语言:javascript复制
import { mapState } from 'vuex'

然后就可以在computed中使用mapState

mapState等这种辅助函数的时候,如果组件内部的命名vuex内的命名是一致的,可以简写成数组方式。

代码语言:javascript复制
//state.js
export default {
    nickname:'Simba',
    age:20,
    gender:'男'
};
代码语言:javascript复制
//computed
computed: mapState(['nickname','age','gender'])
//上面的一句代码就相当于下面这些 是不是简洁了很多
computed:{
  nickname(){return this.$store.state.nickname}
  age(){return this.$store.state.age}
  gender(){return this.$store.state.gender}
}

如果需要自定义一个计算属性,需要es6中的展开运算符:**...**

代码语言:javascript复制
data(){
  return{
    count:14
  }
}
computed: {   
  value(){
   return "姓名:"   this.coount/7
},
  ...mapState(['nickname','age','gender'])
}

getters以及mapGetters

什么是getters

getters:对数据获取之前的再次编译,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。说白了就约等于vuecomputed,可以像使用computed一样去使用getters,当然二者还是有区别的.

如何使用getters

getters中的方法有两个默认参数

  • state 当前VueX对象中的状态对象
  • getters 当前getters对象,用于将getters下的其他getter拿来用
代码语言:javascript复制
//state.js
export default {
  name:'simba',
  age:'20'
};
//getters.js
export default {
  // 第一个参数是state
  realName(state) {
    return "姓名:"   state.name
  },
  // 第二个参数可以访问getters
  nameAndAge(state, getters) {
    return "年龄:"   state.age  ";"  getters.realName
  }
};

如何访问getters

通过属性访问

getter 会暴露为 store.getters 对象,我们可以以属性的形式访问这些值:

代码语言:javascript复制
store.getters.realName// -> 姓名:simba

注意:getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。

通过方法访问

我们可以通过让 getters 返回一个函数,来实现给 getters 传参。这样在对 store 里的数组进行查询时非常有用。

代码语言:javascript复制
state:{
  todos:[
    {
      id:2,
      text:'…',
      done: false
    }
  ]
},
getters: {
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: ‘…’, done: false }

注意:getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

在组件中使用

我们在computed中通过this.$store.getters.xxx来绑定一个计算属性

代码语言:javascript复制
//组件中
<template>
  <div class="hello">
    <div>
        <div>{{message}}</div>
        <div>{{message2}}</div>
    </div>
  </div>
</template>
computed:{
   message(){
     return this.$store.getters.realName 
   },
   message2(){
     return this.$store.getters.nameAndAge; 
   }
},

结果如下:

什么是mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

使用方法:先要导入这个辅助函数.

代码语言:javascript复制
import { mapGetters } from 'vuex'

然后就可以在computed中使用mapGetters

代码语言:javascript复制
computed: {
  ...mapGetters({
    message: "realName",
    message2: "nameAndAge"
  })
},

是不是简洁了很多,如果计算属性的名getters的名字相同,还可以使用数组简写形式

代码语言:javascript复制
computed: {
  ...mapGetters(["realName","nameAndAge"])
},

mutation以及mapMutation

什么是mutation

mutation是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。mutation中通常存放一些同步修改状态的方法.

注意:更改 Vuexstore 中的状态的唯一方法是提交 mutation

如何使用mutation

mutations方法都有默认的形参:mutation([state] [,payload])

  • state 当前VueX对象中的state
  • payload 载荷(该方法在被调用时传递的参数)
代码语言:javascript复制
//state.js
export default {
  name:'韩志伟'
};
//mutations.js
export default {
 changeName(state, name) {
    state.name = name;
  },
};

我们需要这样去调用mutation

代码语言:javascript复制
this.$store.commit('changeName','吴彦祖')

例如我们在组件的methods中修改一下name属性

代码语言:javascript复制
methods: {
    changeName(name){
      this.$store.commit('changeName',name);
    },
}
//调用changeName方法
mounted(){
  this.changeName('吴彦祖')
}

当需要多参提交时,可以把他们放在一个对象中

代码语言:javascript复制
this.$store.commit('changeName',{firstName:'han',lastName:'zhiwei'})

也可以用另外一种传参的方式

代码语言:javascript复制
this.$store.commit({
    type:'changeName',
    payload:{
        firstName:'han',
        lastName:'zhiwei'
    }
})

什么是mapMutation

mapMutation辅助函数仅仅是将 store 中的 mutation 映射到组件methods

使用方法:先要导入这个辅助函数.

代码语言:javascript复制
import { mapMutation} from 'vuex'

然后就可以在methods中使用mapMutation

代码语言:javascript复制
methods:{
 ...mapMutations({
      changeName:'changeName',
    })
}

这个代码等同于下面这段

代码语言:javascript复制
changeName(payLoad){
  this.$store.commit('changeName',payLoad)
}

如果方法名mutation名字一样 可以简写成下面这样

代码语言:javascript复制
methods:{
 ...mapMutations(['changeName'])
}

还可以使用常量替代mutations事件类型

store文件夹下面新建mutation-types.js文件

代码语言:javascript复制
//mutation-types.js
export const ADD_AGE = 'ADD_AGE'
//mutations.js
import * as types from './mutation-types';
export default {
  [types.ADD_AGE](state, payLoad) {
    state.age  = payLoad.number
  }
}
//组件中js部分
 ...mapMutations([types.ADD_AGE]),

但是这个不是很常用,知道有这个知识点就可以了

增删state中的成员

既然讲到了如何修改state的值,顺便提一下如何增删state中的成员

Vue.set 为某个对象设置成员的值,若不存在则新增

代码语言:javascript复制
Vue.set(state,"age",22)

Vue.delete 删除成员

代码语言:javascript复制
Vue.delete(state,'age')

actions以及mapActions

什么是actions

由于直接在mutation方法中进行异步操作,可能会引起数据失效。所以提供了Actions来专门进行异步操作,类似于axios请求,最终通过提交mutation方法来修改state中的值。

如何使用actions

Actions中的方法有两个默认参数: Action([context ] [,payload])

  • context 上下文对象 包含dispatch commit state getters rootState 可以使用es6的解构赋值看起来更明确{ commit }
  • payload 载荷(该方法在被调用时传递的参数) 看一个例子,一秒钟以后提交mutation修改state中的name属性
代码语言:javascript复制
//state.js
export default {
  name:'韩志伟'
};
//mutations.js
export default {
 changeName(state, name) {
    state.name = name;
  },
};  
//actions.js
export default {
 asyncChangeName({ commit } ,name) {
   setTimeout(() => {
     commit('changeName',name);
  }, 1000);
  },
};

我们需要这样去调用action

代码语言:javascript复制
this.$store.dispatch('asyncChangeName','吴彦祖')

例如我们在组件的methods中修改一下name属性

代码语言:javascript复制
methods: {
    changeName(name){
      this.$store.dispatch('asyncChangeName',name);
    },
}
//调用changeName方法
mounted(){
  this.changeName('吴彦祖')
}

action中也可以调用另一个action

代码语言:javascript复制
//actions.js
export default {
 asyncChangeName({ dispatch }) {
   setTimeout(() => {
     dispatch('anotherAction');
  }, 1000);
  },
 anotherAction(){
   console.log('另一个action被调用了')
 }
};

action中也可以传入state,以及rootState,至于什么是rootState,下面学习模块化modules的时候就知道了

代码语言:javascript复制
//actions.js
export default {
 action({ state }) {
   setTimeout(() => {
      console.log(state.name)
  }, 1000);
  },
 anotherAction({ rootState }){
   setTimeout(() => {
     console.log(rootState.name);
  }, 1000);
 }
};

至于actions的传参就与mutation一样了

代码语言:javascript复制
this.$store.dispatch('changeName',{firstName:'han',lastName:'zhiwei'})

什么是mapActions

mapActions辅助函数仅仅是将 store 中的 actions 映射到组件methods

使用方法:先要导入这个辅助函数.

代码语言:javascript复制
import { mapActions} from 'vuex'

然后就可以在methods中使用mapActions

代码语言:javascript复制
methods:{
 ...mapActions({
      changeName:'changeName',
    })
}

这个代码等同于下面这段

代码语言:javascript复制
changeName(payLoad){
  this.$store.dispatch('changeName',payLoad)
}

如果方法名actions名字一样 可以简写成下面这样

代码语言:javascript复制
methods:{
 ...mapActions(['changeName'])
}

modules模块化

什么是modules

当项目庞大,状态非常多时,可以采用模块化管理模式Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter

初始化modules

前面我们学习了如何将vuexindex.js文件拆分成单个文件进行管理,所以我们依然对所有的模块进行单文件拆分管理,目录结构如下

代码语言:javascript复制
store
│      
├─index.js
│            
├─state.js
│      
├─getters.js     
│
├─mutations.js      
│
├─actions.js        
│
└─modules
      │
      ├─moduleA // moduleA的结构与moduleB相同
      │
      └─moduleB
            │ 
            ├─index.js
            │            
            ├─state.js
            │      
            ├─getters.js     
            │
            ├─mutations.js      
            │
            └─actions.js

1.首先根index.js中除了引入自身的state,getters,mutations,actions之外,还要引入两个模块的index.js并在export中导出modules

代码语言:javascript复制
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 moduleA  from './modules/moduleA/index';
import moduleB  from './modules/moduleB/index';
Vue.use(Vuex);
export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters,
  modules: {
    moduleA,
    moduleB,
  },
});

2.在 moduleA 的index.js中导入moduleA的state,getters,mutations,actions. moduleB同理

注意:gettermutationaction 他们默认都是注册在全局命名空间的,所以我们默认是可以和使用根状态一样去使用他们,这样就失去了模块化的意义,所以我们要在模块的index.js中添加namespaced: true使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。

代码语言:javascript复制
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
const moduleA = {
  namespaced: true,
  state: state,
  getters: getters,
  mutations: mutations,
  actions: actions,
};
export default moduleA ;

3.moduleA下的state,getters,mutations,actions就和之前学习的一样导出就可以了

代码语言:javascript复制
//state.js
export default {
  name:'hzw'
};
//mutations.js
export default {
 changeName(state, name) {
    state.name = name;
  },
};  
//以此类推

如何在模块化中进行定义

state

正常写在各自的state.js中即可

getter

getter的话,他会有三个参数,第一个是模块内的 state,第二个是 模块内的 getters,第三个是根节点状态 rootState

代码语言:javascript复制
//getters.js
export default {
  nameAndAge(state, getters, rootState) {
    return "年龄:"   state.age  ";"  getters.realName   ""   rootState.name
  }
};

mutation

mutation 传入的第一个参数也是模块内的 state,其实就和根状态定义 mutation 的时候一样

代码语言:javascript复制
export default {
//这里的state是模块的局部状态
 changeName(state, name) {
    state.name = name;
  },
};

actions

action 的话,他传入还是只有 context 对象,这个对象里面的 state 属性指模块内的状态,rootState 指根状态,如下

代码语言:javascript复制
export default {
 changeName({ state,rootState }) {
        console.log(state.name)
        console.log(rootState .name)
    }
};

如何在模块化中进行开发

1. state 获取

这个要在原来状态名前面加一个模块名才能取到到模块内的对象。

代码语言:javascript复制
this.$store.state.moduleA.name;

辅助函数也一样,name 前面加个模块名

代码语言:javascript复制
...mapState({     
  name: state => state.moduleA.name, 
})
//简写
...mapState('moduleA',['name']),

获取根节点的状态还是和以前一样,不需要加模块名,也不需要加root

代码语言:javascript复制
...mapState(['name']),

2. getters获取

这个同样要在原来状态名前面加一个模块名才能取到到模块内的对象。

在获取根状态下的getters不需要加模块名

代码语言:javascript复制
store.getters.moduleA.realName
//map函数的第一个参数也同样需要加模块名
computed: {
  //获取moduleA下的getters
  ...mapGetters("moduleA",["realName","nameAndAge"])
  //获取根状态下的getters
  ...mapGetters(["realName"])
},

3.调用mutation以及action

根据stategetters推算,在调用模块内mutationaction的时候肯定也需要加模块名

在调用根状态中的mutationaction的时候不需要加模块名

代码语言:javascript复制
methods:{
//调用模块A下的action
 ...mapActions('moduleA',['changeName'])
//调用模块A下的mutation
 ...mapMutation('moduleB',['changeName'])
//调用根状态下的action
 ...mapActions(['changeName'])
//调用根状态下的mutation
 ...mapMutation(['changeName'])
}

4.需要特别注意的是,在模块中的action下调用根状态中的action和mutation需要将{root:true}作为第三个参数传入

代码语言:javascript复制
//moduleA下的actions.js
export default {
 AsyncChangeName({ commit } ,name) {
   setTimeout(() => {
     //调用的是根状态下的mutation
     commit('changeName',name,{ root: true });
     //调用的是根状态下的action
    dispatch('changeName',name,{ root: true });
    }, 1000);
  },
};

5.将模块中的action注册为全局

这个感觉和模块化的设计有点冲突,并且也不常用,知道有这个知识点即可,在声明action的时候,添加root:true并将 action 的定义放到 hanler 函数中,具体如下:

代码语言:javascript复制
//actions.js
export default {
 globalAction:{
  root:true,
  handler({ commit } ,name) {
   setTimeout(() => {
     commit('changeName',name);
   }, 1000);
  },
 }
};

到这里就完全可以使用vuex进行开发任务了!

打个广告

这是我一个开源的收藏网址的项目

项目地址

0 人点赞