4

Vuex

集中式状态管理

使用时机:
每一个组件都拥有当前应用状态的一部分,整个应用的状态是分散在各个角落的。然而经常会需要把把状态的一部分共享给多个组件。

Vuex:一个专门为Vue.js 应用设计的状态管理架构.

状态管理:统一管理和维护各个vue组件的可变化状态(可以理解成vue组件里的某些data数据,全局变量)

出现背景:

  1. 追踪自定义事件NaN,这个事件由那个组件触发的,谁在监听它。

  2. 业务逻辑遍布各个组件,导致各种意想不到的问题。

  3. 由于要显式的分发和监听,父组件和子组件强耦合

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和单纯的全局对象:

  1. Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化。

  2. 不能截至改变store中的状态。改变store中的状态的唯一途径就是显示地分发 状态变更事件

    作用:方便的跟踪每一个状态的变化。
    

vuex 把应用的数据和修改的数据的方法,放在了一个 sotre 对象里面统一管理,对数据的获取和操作则分别通过 vm新增的配置属性 vuexgettersactions 来进行

整个APP的数据就是存放在state对象里,随取随用.

定义一个mutations对象。可以把mutations理解为“用于改变state状态的一系列方法”

在vuex的概念里,state仅能通过mutations修改。
好处:能够更直观清晰的集中管理应用的状态。

clipboard.png

  • 数据流动是单向

  • 组件可以调用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遵守规则:

  1. 应用层级的状态应该几种到单个sotre对象中。

  2. 提交mutation是更改状态的唯一方法,并且这个过程是同步的。

  3. 异步逻辑都应该封装到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特性:

  1. 每当store.state.count变化的时候,都会重新求取计算属性,并且触发更新相关的DOM。

  2. 导致组件过度依赖全局状态单例。

  3. 每个模块化的组件中,需要频繁导入,并且在测试组件时需要模拟状态。

组件仍然保有局部状态

使用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组件也会自动更新。
注意:

  1. 最好提前在sotre中出初始化好所有所需属性。

  2. 当需要在对象上添加新属性时,应该使用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,不同点:

  1. Actions 提交的是mutation,而不是直接变更状态。

  2. 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 与外界交互的入口
    
});

alogy
1.3k 声望121 粉丝

// Designer and Developer


« 上一篇
MySQL