Vuex

Vuex

官方demo:

https://github.com/vuejs/vuex/tree/dev/examples

 

 

前言

上图引入的问题

共享数据区

总述

基本使用

思考:

mutations变更数据

Mutation需遵守Vue的响应规则

Mutation必须是同步函数

在组件中提交Mutation

数据处理

Action

说明

我的案例

组合Action

Module

 

 

 

 

 

前言

我们知道在vue中,组件之间的传值是比较麻烦的,而且局限性也很大,对于简单的父子传值只是简单的数据流向。这种数据流向都是单向单一的,也就是说由一个组件单向流向另一个组件,而且这种流向也仅仅支持父子组件而已,对于兄弟组件之间或者祖子组件或】等多种关系的组件形式单向单一简单数据流向就满足不了我们的需求了。vuex是一个专门为vue.js程序开发出来的状态管理模式,或者说是:数据管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。简单点说:vuex采用一个公共存储区的方式去存储一些多个组件依赖的数据,并且用相应的规则去保证各个组件之间共享的这批数据同步。

 

单向数据流理念图

上图引入的问题

1、传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。

2、我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

 

 

共享数据区

共享数据理念图

设计理念:把组件的共享状态抽取出来,以一个全局单例模式管理。通过定义和隔离状态管理中的各种理念并通过强制规则维持视图和状态树和状态间的独立性,我们的代码将会变得更结构化且易维护。上图中使用绿色虚线描绘出来的内容即为vuex管理的共享数据区,所有的组件不能之间获取公共区的内容,而是通过调度的形式,用公共区封装的方法去获取其内数据【其中Mutations的含义是改变、变动的意思】,即:通过mutations去变动共享数据。

 

总述

每一个Vuex应用的核心就是store(仓库)。"store"基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex和单纯的全局对象有以下两点不同:

1、Vuex 的状态存储是响应式的。 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

2、你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

 

 

基本使用

main.js

import Vue from 'vue'

// 1、装包:cnpm i vuex -S
// 2、引入
import Vuex from 'vuex'
// 3、载入
Vue.use(Vuex)

import App from './App.vue'

// 4、创建仓库
var store = new Vuex.Store({
    state: { // 相当于组件的data
        num: 0
    },
    mutations: { // 相当于组件的methods

    }
})

var vm = new Vue({
    el: "#app",
    date: {

    },
    render: (c) => c(App),
    // 5、挂载到vue实例
    // store:store
    // 类似router简写
    store
})

app.vue

<template>
    <div>
        <h1>这是App组件</h1>
        <hr>
        <num></num>
        <show></show>
    </div>
</template>

<script>
import num from './components/num.vue'
import show from './components/show.vue'

export default {
    components:{
        num,
        show
    }
}
</script>

组件num.vue

<template>
    <div>
        <button @click="decrease">减少</button>
        <button @click="increament">增加</button>
        <p><input type="text" :value="$store.state.num"></p>
    </div>
</template>

<script>
export default {
    methods:{
        decrease() {
            // 
        },
        increament() {
            // 严重不推荐!!!,这样违背了vuex的设计理念,公共数据区的修改,不允许直接修改,需要
            // 使用公共管理区的方法去更改数据。
            // this.$store.state.num++
        }
    }
}
</script>

组件show.vue

<template>
    <div>
        <h2>当前数量为:{{$store.state.num}}</h2>
    </div>
</template>

思考:

如果我们在组件内部直接修改仓库的数据有哪些坏处?

如果每一个组件都去修改我们公共存储区的数据,会造成一个问题就是一旦发生数据“异常”问题(某组件内发现引入的存储区数据不正常),你需要一个一个组件的去查找是哪个组件造成了公共数据的“异常”,如果只有几个组件还可以接收,但是如果你有几十成百个呢?去翻找错误来源,这简直要命!

vuex的设计理念里要求,外部不允许直接变更公共存储区内数据,只有通过dispatcher公共存储区mutations里的方法去变更数据。怎么调度mutations里的方法去变更呢?

 

 

mutations变更数据

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

// main.js
var store = new Vuex.Store({
    state: { // 相当于组件的data
        num: 0
    },
    mutations: { // 相当于组件的methods
         // 第一个参数即为公共存储区的state对象,第二个参数即外部传递来的数据
         // 注意:只能有两个参数!
        add(state,obj) {
            state.num += (obj.a + obj.b)
        },
        substract(state) {
            state.num--
        }
    }
})

你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 add 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:

<!-- num.vue组件 -->
<template>
  <div>
    <button @click="decrease">减少</button>
    <button @click="increament">增加</button>
    <p>
      <input type="text" :value="$store.state.num" />
    </p>
  </div>
</template>

<script>
export default {
  methods: {
    decrease() {
      this.$store.commit("substract");
    },
    increament() {
      // 严重不推荐!!!,这样违背了vuex的设计理念,公共数据区的修改,不允许直接修改,需要
      // 使用公共管理区的方法去更改数据。
      // this.$store.state.num++

      // 提交
      // this.$store.commit('add')

      // 传递参数
      // this.$store.commit('add',{a:1,b:2})
      
      // 对象风格提交
      //   this.$store.commit({
      //     type: "add",
      //     a: 1,
      //     b: 2
      //   });
    }
  }
};
</script>

Mutation需遵守Vue的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

 最好提前在你的 store 中初始化好所有所需属性。

 当需要在对象上添加新属性时,你应该

 使用 Vue.set(obj, 'newProp', 123), 或者

 以新对象替换老对象。例如,利用 stage-3 的对象展开运算符

我们可以这样写:

state.obj = { ...state.obj, newProp: 123 }

案例:

<template>
  <div>
    <button @click="increament">增加</button>
  </div>
</template>

<script>
import Vue from "vue";

export default {
  methods: {
    increament() {
        // 传递参数
      this.$store.commit("add", { a: 1, b: 2 });
      
      // 给仓库新增一个id属性
      Vue.set(this.$store.state, "id", 111);

      // 给仓库替换num的属性值为119
      this.$store.replaceState({
        ...this.$store.state,
        num: 119
      });
    }
  }
};
</script>

这里有个问题:

虽然我是在更改state.num之前commit,我将该值改成了199,在页面中虽然input框显示的没问题是119,但奇怪的是下面num出现了126???整的我一脸懵逼。

 

 

Mutation必须是同步函数

一条重要的原则就是要记住mutation必须是同步函数,看个案例:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

每一条mutation被记录,devtools都需要捕捉到前一个状态和后一个状态的快照。然而,在上面的mutation中的异步函数中的回调让这不可能完成:因为mutation触发的时候,函数立即被执行,并不会等待异步执行的结果,所以在异步回调函数里的状态变更是不可追踪的,数据不会被更新!

 

 

在组件中提交Mutation

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

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

数据处理

vuex还提供了对调度的数据进行处理,这就类似于组件内的computed或Filter

var store = new Vuex.Store({
    state: { // 相当于组件的data
        num: 0
    },
    mutations: { // 相当于组件的methods
        // 第一个参数即为公共存储区的state对象,第二个参数即外部传递来的数据
        // 注意:只能有两个参数!
        add(state, obj) {
            state.num += (obj.a + obj.b)
        },
        substract(state) {
            state.num--
        }
    },
    getters: {
        // 每当state.num数据变更时都会执行此方法,并且触发组件更新相关联的 DOM
        shownum(state) {
            // return this.$store.state.todos.filter(todo => todo.done).length
            return `当前数量为:${state.num}`
        },
        showshow: (state) => (id) => {
            return `当前数量为:${state.num},id为${id}`
        }
    }
})

show.vue组件

<template>
    <div>
        <!-- <h2>当前数量为:{{$store.state.num}}</h2> -->
        
        <!-- 即可以通过属性访问,也可以通过方法访问,如果不传参,推荐使用属性访问 -->
        <h2>{{$store.getters.shownum}}</h2>
        <h2>{{$store.getters.showshow(2)}}</h2>
    </div>
</template>

 

Action

说明

mutation中混合异步调用会导致你的程序很难调试。在Vuex中,mutation都是同步事务

 

Action类似于mutation,不同于:

 Action提交的是mutation,而不是直接变更状态。

 Action可以包含任意异步操作。

 

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。Action 通过 store.dispatch 方法触发。且具有和commit类似的传参规则等。

 

 

我的案例

在加入购物车小球动画中,我如果直接在mutation里的addGoods方法里使用定时器延迟一秒执行数据更新,发现小球动画执行完,数据并没有被更新。这就是由于定时器异步执行,当触发addGoods方法时,主程序立马执行完【同步事务】,对于定时器则需要过一段时间才执行完,而我们的数据更新操作又恰恰在定时器内部,最后定时器执行完数据更新并没有触发vue的数据更新机制,所以在页面上呈现的就是动画执行完了,购物车数量没更新的问题。

// main.js

var storeObj = new Vuex.Store({
    state: {
        car: []
    },
    mutations: {
        addGoods(state, goods) { // 向购物车内添加一条信息
            let exist = state.car.some((cargoods) => {
                if (cargoods.id === goods.id) {
                    cargoods.num += goods.num
                    return true;
                }
            })
            if (!exist) {
                state.car.push(goods);
            }

            localStorage.setItem("car",JSON.stringify(state.car))
        }
    },
    actions: {
        // 加入购物车动画属于异步处理,需要使用actions
        addGoods(context, goods) {
            setTimeout(function () {
                context.commit('addGoods', goods)
            }, 1000)
        }
    }
})
export default {
  methods: {
    pushcar() {
      this.show = !this.show;

      // this.$store.commit('addGoods',{})
      // 使用了异步处理,不能直接使用commit提交了,而是采用actions的方式
      this.$store.dispatch("addGoods", {
        id: this.goodsId,
        num: this.buycount,
        price:this.price,
        selected: true
      }).then(function(){
        console.log('success');
      });
      
      // 返回的是一个Promise对象
      console.log('obj',obj);
      
    }
};
</script>

 

组合Action

Action通常是异步的,那么如何知道action什么时候结束?更重要的是我们如何才能组合多个action,以处理更加复杂的异步流程?

store.dispatcher可以处理被触发的action的处理函数返回的Promise,并且store.dispatch仍旧返回Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

现在你可以:

store.dispatch('actionA').then(() => {
  // ...
})

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

 

 

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

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

 

前进时,请别遗忘了身后的脚印。
原文地址:https://www.cnblogs.com/liudaihuablogs/p/13469066.html