Vuex
集中式状态管理
使用时机:
每一个组件都拥有当前应用状态的一部分,整个应用的状态是分散在各个角落的。然而经常会需要把把状态的一部分共享给多个组件。
Vuex:一个专门为Vue.js 应用设计的状态管理架构.
状态管理:统一管理和维护各个vue组件的可变化状态(可以理解成vue组件里的某些data数据,全局变量)
出现背景:
追踪自定义事件NaN,这个事件由那个组件触发的,谁在监听它。
业务逻辑遍布各个组件,导致各种意想不到的问题。
由于要显式的分发和监听,父组件和子组件强耦合
Vuex 核心概念:
状态树:包含所有应用层级状态。意味着,每个应用将仅仅包含一个store实例。单状态树能够直接定位任意特定的状态片段。
Getters:在Vue组件内部获取stroe中状态/数据的函数
Mutations:通过事件回调函数来修改store中的状态的变化.
Actions:在组件内部使用函数,分发mutations事件的函数.
为什么需要有action
每一个web应用都至少对应一个数据结构,而导致这个数据结构状态更新的来源很丰富;光是用户对view的操作(dom事件)就有几十种,此外还有ajax获取数据、路由/hash状态变化的记录和跟踪。
来源丰富不是最可怕的,更可怕的是每个来源提供的数据结构并不统一。DOM事件还好,前端可以自主控制与设计;ajax获取的数据,其结构常常是服务端开发人员说了算,面对业务场景跟前端并不相同,往往会为了自己的便利,给出在前端看来很随意的数据结构。
web应对中所有的数据与状态的变化,几乎都来自[事件],DOM事件,AJAX成功或失败事件,路由change事件,setTimeout定时器事件,以及自定义事件。任意时间都可能产生需要合并全局数据对象里的新数据或者线索。
是event响应函数里主动调用了action函数,并且传入它需要的数据。
action 作用:
action 是专门用来被component调用的函数
action函数能够通过分发相应的mutation函数, 来触发对stroe的更新
action 可以先从HTTP后端 或 store 中读取其它数据之后再分发更新事件
Vuex把状态分为:
组件本地状态
仅在一个组件内使用的状态(data字段)应用层级状态(应用级的状态不属于任何特定的组件,但每一个组件仍然可以监视其变化从而响应式的更新DOM)
组件内部使用的状态,通过配置选项传入Vue组件内部的意思。
应用级别状态
多个组件共用的状态
同时被多个组件共享的状态层级
简单使用
Vuex 应用的核心是store(仓库)
理解成项目中使用的数据的集合。 包含着大部分的状态(即state)
Vuex和单纯的全局对象:
Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化。
-
不能截至改变store中的状态。改变store中的状态的唯一途径就是显示地分发 状态变更事件
作用:方便的跟踪每一个状态的变化。
vuex 把应用的数据和修改的数据的方法,放在了一个 sotre
对象里面统一管理,对数据的获取和操作则分别通过 vm新增的配置属性 vuex
的 getters
和 actions
来进行
整个APP的数据就是存放在state对象里,随取随用.
定义一个mutations
对象。可以把mutations
理解为“用于改变state状态的一系列方法”
在vuex的概念里,state仅能通过mutations
修改。
好处:能够更直观清晰的集中管理应用的状态。
数据流动是单向
组件可以调用
actions
Actions
是用来分发mutations
只有
mutations
可以修改状态store
是反应式(状态的变化会在组件内部得到反映)
sotre只需要在最顶层的组件声明一次
在入口文件加入:
var store = new Vuex.Store({
state: {
a: false,
money: 0
},
mutations: {
increment( state ) {
store.state.a;
}
}
});
组件中使用:
computed: {
a: function() {
return store.state.a;
}
}
使用Vuex遵守规则:
应用层级的状态应该几种到单个sotre对象中。
提交mutation是更改状态的唯一方法,并且这个过程是同步的。
异步逻辑都应该封装到action里面。
State
单一状态树
Vuex 使用 单一状态树。 使用一个对象包含全部的应用层级状态(数据)。把它作为一个唯一数据源提供方存在(全局变量)。
每个应用程序仅仅包含一个store
实例。单状态数能够直接定位任意特定的状态片段,在调试过程中能够轻易的获取整个当前应用状态。(单状态树和模块化并不冲突)
Vuex 中数据都是单向的,Vue组件只能从 store
获取。
如何在Vue组件中获得Vuex状态
由于 Vuex 的状态存储是响应式的,从 store 实例中 读取状态在计算属性computed
中返回某个状态。
new Vue({
el: '.app',
computed: {
count: function () {
return stroe.state.count;
}
}
});
store.state
特性:
每当
store.state.count
变化的时候,都会重新求取计算属性,并且触发更新相关的DOM。导致组件过度依赖全局状态单例。
每个模块化的组件中,需要频繁导入,并且在测试组件时需要模拟状态。
组件仍然保有局部状态
使用Vuex并不意味着需要将所有的状态放入Vuex。
优点:把所有状态放入Vuex会是的状态变化更显式和易调试。
缺点:代码变得冗长和不直观。
如果有些状态严格属于单个组件,最好还是作为组件的局部状态。
Getters
需要对数据进行第二次加工处理,全局函数处理,可以在 store中定义getters
(可以认为store的计算属性)。
定义
const store = new Vue({
state: {
list: [{id: 1, text: 'a'}, {id: 2, text: 'b'}]
},
getters: {
done: state => {
return state.todos.filter(todo => todo.done);
}
}
});
调用
store.getters.lenCount
Mutations
更改Vuex的store中的状态的唯一方法是提交 mutation.
Vuex中的mutations非常类似于事件:每个mutations都有一个字符串的 事件类型(type) 和 一个回调函数(handler),参数:state。
定义
const store = new Vue.store({
state: {
a: 0
},
mutations: {
heade( state ) {
state.a += 10;
}
}
});
调用
sotre.commit('heade');
传入多个参数(提交载荷Payload) 多数情况下,另外的参数,会是对象。
// 定义
mutations: {
heade( state, n ) {
state.a += n;
}
}
// 调用
store.commit('heade', 100);
Mutations 需遵守 Vue 的响应规则
Vuex中的store中的状态是响应式的,变更状态时,监听状态的Vue组件也会自动更新。
注意:
最好提前在sotre中出初始化好所有所需属性。
当需要在对象上添加新属性时,应该使用
Vue.set(obj, 'newProp', 123)
或者 以新对象替换老对象。
使用常量替代Mutation事件类型
使用常量替代mutation
事件类型在各种 Flux 实现中常见模式.
结果:可以使linter之类的工具发挥作用,同时把这些常量放在单独的文件中可以让代码合作者对这个app包含的mutation清晰明了。
// mutation-types.js
export const FETCH_MOVIE_BY_ID = 'FETCH_MOVIE_BY_ID';
// store.js
import Vuex form 'vuex';
import {FETCH_MOVIE_BY_ID} from './mutations-types.js';
const store = new Vuex.Store({
state: {},
mutations: {
['FETCH_MOVIE_BY_ID']( state ) {
// mutate state
}
}
});
mutation注意点:
mutation必须是同步函数
原因:当mutation触发的时候,回调函数还没有被调用。 实质上任何在回调函数中进行的状态的改变都是不可追踪的。
在组件中提交Mutations
定义:
increment: function( context ) {
context.a += 10;
}
使用方式:
// 方式1
store.commit('increment'); // this.$store.commit('xxx')
// 方式2
methods: {
mapMutations(['increment']) // // 映射 this.increment() 为 this.$store.commit('increment')
}
Actions
Actions类似于mutations,不同点:
Actions 提交的是mutation,而不是直接变更状态。
Actinos 可以包含任意异步操作。
store = new Vuex.Store({
state: {
a: 0
},
mutations: {
add() {
state.a += 10;
}
},
actions: {
add( context ) {
context.commit('add');
}
}
});
Action 函数参数:store
实例具有相同的方法和属性的context对象。
可以调用context.commit(); 执行一个mutation。 或者通过context.state 和 context.getters 来获取 state和 getters。
分发Action
Action通过store.dispatch
方法触发.
store.dispatch();
Action 能够支持载荷方式和对象方式进行分发.
// 以载荷形式分发
store.dispatch('add', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'add',
amount: 10
})
在组件中分发Action
定义:
increment: function( context ) {
context.a += 10;
}
使用方式:
// 方式1
store.dispatch('increment'); // this.$store.dispatch('xxx')
// 方式2
methods: {
mapActions(['increment']) // // 映射 this.increment() 为 this.$store.commit('increment')
}
组合Actions
Action 通常是异步,如何知道action 什么时候结束。如何组合多个action,以处理更加复杂的异步流程?
store.dispatch
的返回的是被处罚的action函数的返回值,因为,可以在action中返回Promise
actions: {
actionA({commit}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation');
resolve();
}, 1000)
})
},
actionB({dispatch}) {
return dispatch('actionA').then(()=>{
commit('someOhterMutation');
})
}
}
一个sotre.dispatch()在不同模块中可以出发多个action函数。在当前情况下,只有当所有触发函数完成后,返回的Promise才会执行。
案例:
import * as types from '../types';
import {fetchMoviesByType, fetchSearchMovies, fetchMovieById} from '../api';
const state = {
movies: [],
movieList: {
title: '',
total: 0,
subjects: [],
},
busy: false,
movie: {},
};
const actions = {
[types.FETCH_MOVIES](context,payload){
fetchMoviesByType(payload.type, payload.start, payload.count)
.then(data=>{
data.type = payload.type;
return context.commit([types.FETCH_MOVIES], data)
});
},
[types.FETCH_MOVIE_LIST](context,payload){
fetchMoviesByType(payload.type, payload.start)
.then(data=>context.commit([types.FETCH_MOVIE_LIST], data));
},
[types.FETCH_MOVIE_BY_ID](context, id){
fetchMovieById(id)
.then(data => context.commit([types.FETCH_MOVIE_BY_ID], data));
},
[types.SET_INFINITE_BUSY](context, data){
context.commit([types.SET_INFINITE_BUSY], data);
},
[types.CLEAN_MOVIE](context){
context.commit(types.CLEAN_MOVIE);
},
[types.CLEAN_MOVIES](context){
context.commit([types.CLEAN_MOVIES])
},
[types.CLEAN_MOVIE_LIST](context){
context.commit([types.CLEAN_MOVIE_LIST])
}
};
const mutations = {
[types.FETCH_MOVIES](state, list){
state.movies.push(list);
},
[types.FETCH_MOVIE_LIST](state, list){
state.movieList.title = list.title;
state.movieList.total = list.total;
state.movieList.subjects = state.movieList.subjects.concat(list.subjects);
if(state.movieList.subjects.length < state.movieList.total){
state.busy = false;
}
},
[types.FETCH_MOVIE_BY_ID](state, movie){
state.movie = movie;
},
[types.SET_INFINITE_BUSY](state, data){
state.busy = data;
},
[types.CLEAN_MOVIE](state){
state.movie = {};
},
[types.CLEAN_MOVIES](state){
state.movies = [];
},
[types.CLEAN_MOVIE_LIST](state){
state.movieList = {};
}
};
export default {
state,
mutations,
actions
}
`api.js` 发送ajax // 定义ajax事件
`modules/movie.js` 中写 state,mutations,actions // action 中执行ajax定义事件, 回调函数中执行mutations中定义的事件
`mutations` 定义事件, 第一个参数是`context`,当前定义的store
`types.js` 定义常量替代mutation事件类型
// src/components/header.vue
// 从vuex拿数据,然后渲染到页面上
// 如果需要修改可以调用setTitle
import { setTitle } from '../vuex/actions';
export default {
vuex: {
// 获取vuex状态数据
getters: {
title: state => state.title,
info: ({index}) => index.info
},
// 状态变更事件
actions: {
setTitle
}
}
}
Modules
场景:使用单一状态数,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,sotre对象会变得难以维护。
Vuex允许将sotre分割到模块(module),每个模块拥有自己的state,mutation,action,getters.
import Vuex from 'vuex'
import Vue from 'vue'
import movie from './modules/movie'
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
movie
}
});
Store配置项
new Vuex.Store({
state: {}, // 初始状态
actions: {}, // 执行 mutation,异步.
getters: {}, // 全局方法,二次加工数据
mutations: {} // Store 与外界交互的入口
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。