状态管理模式 — Vuex如何使用?

Extract

试想当我们在开发一个Vue应用程序时,如果在一个项目中频繁的使用组件传参的方式来同步data中的值,一旦项目结构变得复杂,管理和维护这些数据将变得十分繁琐,为此,Vue为这些被多个组件共同使用的data提供了一个统一的管理工具—Vuex。

Vuex是专为Vue.js应用程序开发的状态管理模式,集中存储管理应用的所有组件的状态(数据),并以相同的规则保证状态以一种可预测的方式发生变化。

安装

可在项目目录下直接通过npm安装,其他安装方式详见Vuex安装

npm install vuex --save

使用

首先需要创建一个store实例,引入你创建的所有modules

目录结构
/src
|-main.js
|-/store
	|-/modules
	|-index.js
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import team from './modules/team'
import product from "./modules/product";
import chat from "./modules/chat";
import notification from "./modules/notification";

Vue.use(Vuex)

export default new Vuex.Store({
    modules: {
        user,team,product,chat,notification
    },
    strict: true
})

main.js中,引入store实例并暴露出来:

import Vue from 'vue'
import App from './App.vue'
import store from './store'

export default new Vue({
  render: h => h(App),
  store
}).$mount('#app')

State

State是Vuex的基本属性,称为单一状态树,如果熟悉Java面向对象编程的话,我们可以将其类比为成员变量:

public class User {
    //成员变量
    private String name;
    private Integer age;
    private String gender;
}
public class Users {
    private List<User> users;
}
const state = () => ({
    //State
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ]
})

在Vue组件中获得Vuex状态可通过以下两种方式:

//方法一:在根实例中注册store选项,该实例会注入到根组件下的所有子组件中
this.$store.state.name

//方法二:使用mapState辅助函数
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

Getter

getter的使用可类比为Java中的get方法。

public class User {
    //成员变量
    private String name;
    private Integer age;
    private String gender;
}
public class Users {
    private List<User> users;
	
    //get方法
    public List<User> getUsers() {
        return users;
    }
}
//State
const state = () => ({
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ]
})
    
//getter
const getters = {
    users: state => state.users,
}

如果仅仅如此,为何不直接获取state呢?因为有时候我们需要从state中派生出一些状态,例如对列表进行过滤,同样我们可与Java实现类比:

public class User {
    //成员变量
    private String name;
    private Integer age;
    private String gender;
}
public class Users {
    private List<User> users;
	
    //get方法
    public List<User> getUsers() {
        return users;
    }
    
    //返回18岁及以下用户对象
    public List<User> getChildren() {
        List<User> children = new ArrayList<>;
        for (User item: this.users) {
            if (item.age <= 18) {
                children.add(item)
            }
        }
        return children;
    }
}
//State
const state = () => ({
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ]
})
    
//getter
const getters = {
    users: state => state.users,
    //返回18岁及以下用户对象
    children: state => state.users,filter(
    	user => user.age <= 18),
}

在Vue组件中我们可以通过属性访问或者通过mapGetters来获取对象:

import { mapGetters } from 'vuex'
export default {
        computed: {
            ...mapGetters([
                'users',
            ]),
        },
        methods: {
            printUsers() {
                //通过mapGetters访问
                console.log(this.users)
                //通过属性访问
                console.log(this.$store.getters.users)
            }
        }
    }

Mutation

提交mutation是更改Vuex的store中状态的唯一方法,Vuex中的mutation类似于事件:每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的方法,并且他会接受state作为第一个参数。

mutation的实际使用类似于Java中的set方法,是设置state值的唯一方式。

public class User {
    //成员变量
    private String name;
    private Integer age;
    private String gender;
}
public class Users {
    private List<User> users;
    private Boolean status;
	
    //set方法
    public void setStatus() {
     	this.status = true;  
    }
}
//State
const state = () => ({
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ],
    status: null,
})
    
//mutation
const mutations = {
    setStatus(state) {
        state.status = true
    }
}

但不同于Java中set的使用方式,我们不能直接调用一个mutation handler,而是提交一个名为xxxmutation,触发相应的mutation handler执行具体的变更。

Users users = new Users();
users.setStatus();
this.$store.commit("setStatus")

在Java中set函数可以传入参数进而变更成员变量。

public class User {
    //成员变量
    private String name;
    private Integer age;
    private String gender;
}
public class Users {
    private List<User> users;
    private Boolean status;
	
    //set方法
    public void setStatus() {
     	this.status = true;  
    }
    public void setUser(User user) {
        this.users.add(user);
    }
}

调用set函数:

Users users = new Users();
users.setUser(newUser);

在Vuex中我们也可以通过提交载荷(Payload)的方式向store.commit传入额外的参数,即mutation的载荷。

//State
const state = () => ({
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ],
    status: null,
})
    
//mutation
const mutations = {
    setStatus(state) {
        state.status = true
    },
    setUser(state, user) {
        state.users.push(user)
    }
}

在组件中提交携带载荷的mutation

this.$store.commit('setUser', user)

或者使用mapMutations映射出来:

import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations([
      'setUser',
    ]),
      appendUser(user){
          this.setUser(user)
      },
  }
}

综上看来,mutation的使用与set函数的目的是相同,但mutation在使用中最大的原则 — 必须是同步函数。

mutation中混合异步调用会导致你的程序很难调试,当我们调用了两个包含异步回调的mutation来改变状态,我们无法知道什么时候回调以及哪个先回调,因此在Vuex中,mutation都是同步事务

所以为了处理异步操作,Vuex引入了action这一概念。

Action

action类似于mutation是可“调用”的方法,两者不同点在于:

  • action提交mutation,而不直接变更状态;
  • action可以包含任意异步操作
//State
const state = () => ({
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ],
    status: null,
})

//action
const actions = {
    updateUser({commit}) {
		request({
            url: '/user/getNewUser'
            method: 'get'
        }).then(res => {
            commit('setUser', res.data)
        })
    }
}
    
//mutation
const mutations = {
    setStatus(state) {
        state.status = true
    },
    setUser(state, user) {
        state.users.push(user)
    }
}

这段代码中我们注册了一个简单的异步action,我们通过request向后端发送请求,请求新用户,然后我们在回调函数中提交mutation变更状态。

action通过store.dispatch触发:

this.$store.dispatch('updateUser')

action同样可以通过提交载荷的方式进行分发:

//State
const state = () => ({
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ],
    status: null,
})

//action
const actions = {
	appendUser({commit}, user) {
        commit('setUser', user)
    }
}
    
//mutation
const mutations = {
    setStatus(state) {
        state.status = true
    },
    setUser(state, user) {
        state.users.push(user)
    }
}
this.$store.dispatch('appendUser', newUser)

或者使用mapAction映射出来:

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'appendUser',
    ]),
      
      test() {
          this.appendUser(user)
      }
  }
}

Module

使用单一状态树,应用的所有状态都会集中到一个较大的对象,随着应用迭代变得越来越复杂,store对象会变得越来越臃肿。为了解决以上问题,Vuex允许我们将对象模块(Module)化,每个模块拥有自己的statemutationactiongetter甚至嵌套子模块。

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 的状态

在默认情况下,模块内部的actionmutationgetter是注册在全局命名空间的,这样使得多个模块能够对同一mutationaction作出响应。

如果你希望你的模块具有更高的封装度和复用性,可以通过添加``namespaces: true`的方式使其成为带命名空间的模块。

下面我们重新复习下Vuex的目录结构:

目录结构
/src
|-main.js
|-/store
	|-/modules
		|-user.js
		|-team.js
		|-product.js
		|-chat.js
		|-notification.js
	|-index.js

user.js为例,让我们看一下一个完整的Module是怎样的。

//user.js
import {request} from '../../lib/network/request'

//State
const state = () => ({
    users: [
        {
    		name: ...,
    		age: ...,
    		gender: ...,        
        },
        ...
        ...
        ...
    ],
    status: null,
})

//action
const actions = {
    updateUser({commit}) {
		request({
            url: '/user/getNewUser'
            method: 'get'
        }).then(res => {
            commit('setUser', res.data)
        })
    }
}

//getter
const getters = {
    users: state => state.users,
    //返回18岁及以下用户对象
    children: state => state.users,filter(
    	user => user.age <= 18),
}
    
//mutation
const mutations = {
    setStatus(state) {
        state.status = true
    },
    setUser(state, user) {
        state.users.push(user)
    }
}

export default {
    state,
    getters,
    actions,
    mutations
}

然后需要在/modules目录下的index.js中将各个module注册到store对象中:

//index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import team from './modules/team'
import product from "./modules/product";
import chat from "./modules/chat";
import notification from "./modules/notification";

Vue.use(Vuex)

export default new Vuex.Store({
    modules: {
        user,team,product,chat,notification
    },
    //严格模式:无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
    strict: true
})

然后在main.js中将store放进我们的Vue应用程序中:

//main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'

export default new Vue({
  render: h => h(App),
  store
}).$mount('#app')

至此,这便是一个完整的Vuex的使用实例,虽然在Vue中我们也可以通过属性传递的方式在不同组件之间传递data,但是当同一个data需要被多个组件同时调用,数据的一致性便很难保证,Vuex的引入则很好的解决了这一问题,Vuex中状态的变化是全局的,是实时计算的,当我们getter的计算依托于多个state时,当我们提交了新的commit变更状态,相应的getter返回值也会变化,这让我们不用过多分心于数据的一致性。

Vuex可以帮助我们管理共享状态,在应用并不复杂的情况下使用Vuex可能会有些多余,但是当构建一个复杂应用程序时,Vuex对于管理共享状态会是一个很好的选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值