VUEX Note

VUEX的使用总结

1、为什么要使用vuex

官方说法:当多个组件共享数据状态时,单向数据流非常容易被影响甚至破坏,因此需要创建一个全局单例模式的管理方式,用以解决多个视图依赖于统一状态,以及来自不同视图的行为需要变更为同一状态等问题。

真实体验:因为父组件与子组件的交互规则,父组件想调用子组件中定义的方法并修改子组件的数据非常麻烦,所以当父子组件交互频繁的时候,非常需要一种机制来处理二者的关系。

2、开始

1)在自己的项目中创建一个store文件夹,如图:

图片

modules文件夹用来放置store分割成的各个模块,每个模块都有单独的state、mutation、action、getter等;mutation-types.js是一个执行动作类型的集合,里面变量一般要大写;index.js则是将各个module中的store整理到一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue';
import Vuex from 'vuex';

import keywordList from './modules/keywordList';
// import ...

Vue.use(Vuex);

const debug = process.env.NODE_ENV !== 'production';

export default new Vuex.Store({
	modules: {
		keywordList,
		// ...
	},
	strict: debug,
});

这种方式,向外暴露一个Vuex.Store实例。

知识点1:strict代表是否设置为严格模式,在严格模式下,如果state发生变化不是有mutation函数引起的,将会抛出错误,用以保证所有状态都能被调试工具跟踪到。

知识点2:通过module维护各个store,优点是当项目比较大型时能够非常清晰的管理和维护数据。

2)我们在创建vue项目时,src文件夹下有一个main.js,在这个文件中引入./store,在实例化vue对象时注入store对象:

1
2
3
4
5
6
7
8
9
10
11
12
import App from './App'
import Vue from 'vue'
import Vuex from 'vuex'
import router from './router'
import store from './store'

new Vue({
  el: '#app',
  store,
  router,
  render: h => h(App)
});

以上的引用方式,保证了我们在各个子组件中能通过this.$store的方式访问到状态树中的数据。

3、详解各核心概念

1)state

官方名称叫”单一状态树”,它作为一个”唯一数据源”存在。这意味着,每个应用将仅仅包含一个store实例。

vuex的状态存储有以下几个特点:

a.响应式存储:也就是说,当state中的某个数据发生变化时,我们通过计算属性能够轻易的获取其变化后的值。
b.要尽可能的在state中声明我们需要的所有变量,方便后面mutation访问和修改。

2)mapState

由于在组件中我们需要频繁的声明和计算state重的变量,那么就会产生一下写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
hasWJC() {
    return this.$store.state.optimiseDetail.hasWJC;
},
wjcWords () {
    return this.$store.state.optimiseDetail.wjcWords;
},
checkDetail() {
    return this.$store.state.optimiseDetail.checkDetail;
},
scoreMsg() {
    return this.$store.state.optimiseDetail.scoreMsg;
},
resultLists() {
    return this.$store.state.optimiseDetail.resultLists;
}
// ...

这样代码即复杂又冗余,以下方法完全可以将其替代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapState } from 'vuex'
// ...
computed: mapState({
    // 箭头函数可使代码更简练
    hasWJC: state => state.optimiseDetail.hasWJC,

    // 传字符串参数 'hasWJC' 等同于 `state => state.optimiseDetail.hasWJC`
    hasWJCS: 'hasWJC',

    // ...
    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    productDetail(state) {
        let res = state.optimiseDetail.productDetail;
        return res;
    },
}),

mapState和对象展开运算符结合的写法:

1
2
3
4
5
6
7
computed: {
    localComputed () { /* ... */ },
    // 使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
        // ...
    })
}

上述代码这种写法,将state状态和其他的计算变量集合到一起,并将其最终传给computed,通过this.localComputed和this.xxx访问。

3)getter

在真正的项目开发中,经常会遇到,vuex中的变量要进行一些计算然后复用到多个地方,这时,getter就派上了用场:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ...
const titleOptimise = {
	state: {
		isFirst: false,
		hasResult: false,
		// ...
	},
	getters: {
	    isFirst: state => state.hasResult == null,

        doneTodosCount: (state, getters) => {// Getter 也可以接受其他 getter 作为第二个参数
            return getters.doneTodos.length
        },

        getTodoById: (state) => (id) => {// 当通过方法访问时,每次都获取新计算出来的值,而不是缓存
            return state.todos.find(todo => todo.id === id)
        }
	},
	// ...
};

export default titleOptimise;

上述代码在store中定义了getter,就像computed一样,getter定义的值会被缓存起来,当它依赖的数据发生变化时,才会随之更新。

在组件中通过this.$store.getters.xxx访问。

4)mapGetters

与 mapState() 类似 mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

1
2
3
4
5
6
7
8
computed: {
    ...mapGetters([ // 参数为数组时,可以将同名的属性映射
        'count'
    ]),
    ...mapGetters({ // 参数为对象时可以设置映射的名称
        count2: 'changeCount'
    }),
   },

5)mutation

以上state和getter都是用来获取store的数据状态的,而mutaion是可以修改store中的数据的。每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。回调函数就是真正要去修改store数据的地方,并接受state作为第一个参数。

提交 mutation 需要用到 store 的 store.commit() 方法。

注意:mutation函数必须是同步的,原因是是我们不知道异步的毁掉函数什么时候被调用,对修改store中数据会造成很严重的影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import * as types from '../mutation-types';
// ...
const titleOptimise = {
    state: {
        isChecking: false,
        // ...
    },
    mutations: {
        // 使用常量替代 Mutation 事件类型,把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然
        [types.TITLE_OPTIMISE_ACTION](state, data) {
            // 第一个参数为 state 用于变更状态 第二个参数为提交的参数,参数类型视提交方式而定
            state.isChecking = data;
        },
    },
    // ...
};

export default titleOptimise;

也可以在组件中使用 this.$store.commit(‘xxx’) 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ...
export default {
    // ...
    methods: {
        handleClick() {
            // 普通提交 第二个参数为参数
            this.$store.commit('TITLE_OPTIMISE_ACTION', true);
            // 可以将参数包装到一个对象中提交
            this.$store.commit('TITLE_OPTIMISE_ACTION', { isChecking: true });
            // 可以直接提交一个对象,对象中 type 属性对应 事件类型,其他属性, 成为事件处理函数的第二个参数对象中的属性
            this.$store.commit({
                type: 'TITLE_OPTIMISE_ACTION',
                isChecking: true
            });
        },
    },
}

6)mapMutation

同 mapState 和 mapGetters 一样,mapMutations 可以节约代码,使代码更加简洁,它将会在组件的 methods 属性中完成映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { mapMutations } from 'vuex'

export default {
    // ...
    methods: {
        // 将 this.changePageNo({}) 映射为 this.$store.commit('CHANGE_PAGENO', {})
        ...mapMutations({
            'changePageNo': 'CHANGE_PAGENO',
        }),
        // 参数为数组表示函数名必须保持不变
        ...mapMutations([
            'SET_LOADING'
        ]),
    },
}

7)action

action跟mutation很类似,但有两点不同之处:

第一,action支持异步。

第二,action只能提交mutation,而不能直接操作state。

action 函数接受一个与 store 实例具有相同方法和属性的 context 对象(之所以这样讲,是因为modules的缘故),因此你可以调用 context.commit 提交一个 mutation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import * as types from '../mutation-types';
import request from '../../api/request';
import apiConfig from '../../api/apiConfig';
// ...
export default {
    // ...
    actions: {
        async titleOptimise({commit}, data) {//这里第一个参数接受的是context,但很多时候我们只用到commit,因此采用解构赋值的写法
            const res = await request(apiConfig.titleOptimise, 'post', data);
            if (res.data.result === '100') {
                newThis.$message.success('正在全店诊断中请稍等...');
                commit(types.TITLE_OPTIMISE_ACTION, res.data.data);
            } else {
                newThis.$message.error(res.data.message);
            }
        },
    }
}

在action中可以提交多次 mutation。

在子组件中,我们一般采用store.dispatch(‘xxx’)的方式触发action:

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
    // ...
    methods: {
        handleTitleOptimise() {
            store.dispatch('titleOptimise', params);
            // 注意如果使用这种方式,titleOptimise 接受的 payload 中是包含 type: 'titleOptimise' 字段的 commit 的时候要去除
            this.$store.dispatch({
                type: 'titleOptimise',
                params: params
            });
        }
    },
}

8)mapAction

与 mapMutation 类似,用于将 action 映射到组件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { mapActions } from 'vuex';

export default {
    // ...
    methods: {
        ...mapActions([
            'updateTitle',
        ]),
        ...mapActions({
            'checkTitle': 'checkNewTitle',
        }),
    },
}

action 可以组合使用,也就是在一个 action 中调用另外一个 action,在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

参考文献:

vuex 官方文档

vuex 使用详解