Vuex 使用演示demo+通俗版教程

1,115 阅读7分钟

使用vuex 教程 【跟着官网走一遍】

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

1. 在vue项目中安装vuex

npm install vuex --save

--save 添加到生产依赖中【packae.json文件的dependencies字段中】

2. 在项目的src文件中新建一个vuex文件夹,在文件夹中新建store.js文件

3. 在store.js文件中写入

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

// 方法一
const store = new Vuex.Store({
    state: { // store中包含组件中的共享状态
        count: 10
    },
    mutations: { // 改变状态的方法(暂且称作方法)[更改 Vuex 的 store 中的状态的唯一方法是提交 mutation]
        add(state) {
            state.count++
        },
        reduce(state) {
            state.count--
        },
    }
})
export default store  // 用export default 导出,让外部可以引用

// 方法二
// const state = {
//     count: 10
// }
// const mutations = {
//     add(state) {
//         state.count++
//     },
//     reduce(state) {
//         state.count--
//     },
// }
// export default new Vuex.Store({
//     state: state,
//     mutations  // 这里因为对象的key和value相同,可以省略【属性的简洁表示法】
// })


4. 在main.js中配置,配置好了以后,在整个项目中就能全局使用

// main.js
import Vue from 'vue' // 项目构建时自动生成的代码
import App from './App.vue' // 项目构建时自动生成的代码
import store from './vuex/store' // 【自己手动配置的代码】

Vue.config.productionTip = false // 项目构建时自动生成的代码

new Vue({  // 项目构建时自动生成的代码
  render: h => h(App), // 项目构建时自动生成的代码
  store,  // 【自己手动配置的代码】【通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到】
}).$mount('#app') // 项目构建时自动生成的代码

5. State 项目中使用vuex

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态,【这也意味着,每个应用将仅仅包含一个 store 实例】

这个是在项目自带的HelloWorld 组件中使用的

// HelloWorld
<template>
  <div class="hello">
    <h1>{{ $store.state.count }}</h1>
    <h1>{{ count }}</h1>
    <button @click="add()">+</button>
    <button @click="$store.commit('reduce')">-</button>
  </div>
</template>

<script>

export default {
  name: "HelloWorld",
  computed: { // 通过计算属性获取
    count () {
      return this.$store.state.count // 【return this.$store.state.count这一句,一定要写this】
    }
  },
  methods:{
    add(){ 
      this.$store.commit('add')
    }
  }
};
</script>


<style scoped>
</style>

效果图:

6.使用mapState 辅助函数

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

在项目中使用mapState

6.1 先修改一下store.js中state的数据【这一步不是必要的】

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 10,
        sun: 190 // 【新加一条测试数据】
    },
})
export default store

6.2 在组件中使用mapState 辅助函数

  • 第一种 箭头函数可使代码更简练
<template>
  <div class="hello">
    <h1>{{ count }}</h1>
    <h1>{{ sun }}</h1>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  computed: mapState({
    // 【这里一定要注意 逗号,】
    // 第一种:箭头函数可使代码更简练
    count() {
      return this.$store.state.count;
    },
    // sun() {
    //   return this.$store.state.sun;
    // },
    // 等同于:
    sun: (state) => state.sun,
  })
};
</script>
<style scoped>
</style>

效果:

  • 第二种 传字符串参数 'count' 等同于 state => state.count
<template>
  <div class="hello">
    <h1>{{ countX }}</h1> <!--【这里有修改】-->
    <h1>{{ sun }}</h1>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  computed: mapState({
    // 【这里一定要注意 逗号,】
    // 第二种 传字符串参数 'count' 等同于 `state => state.count`
    countX: "count", // key是计算属性名
    sun: "sun",
  }),
};
</script>
<style scoped>
</style>

  • 第三种 为了能够使用 this 获取局部状态,必须使用常规函数
<template>
  <div class="hello">
    <h1>{{ countX }}</h1>
    <h1>{{ sun }}</h1>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  data() {  // 【这里有修改】
    return {
      addNum: "8888",
    };
  },
  computed: mapState({
    // 【这里一定要注意 逗号,】
    // 第三种 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countX(state) {
      return state.count + this.addNum; // 加上data中的数据
    },
    sun(state) {
      return state.sun + this.addNum; // 加上data中的数据
    },
  }),
};
</script>
<style scoped>
</style>


效果:

  • 第四种 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
<template>
  <div class="hello">
    <h1>{{ count }}</h1> <!--【这里有修改】-->
    <h1>{{ sun }}</h1>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  computed: mapState([ <!--【这里有修改 改为了数组 】-->
    // 映射 this.count 为 store.state.count
    "count",
    "sun",
  ]),
};
</script>
<style scoped>
</style>

这里一定要保证计算机属性的名称与store.js文件中的state对象中的key相同

  • 第五种 对象展开运算符

mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法:


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

7.Getter

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

7.1 定义Getter

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 10,
    },
    getters: { // 定义getters属性
        count: function (state) {
            return state.count += 100; // 每次count的值改变后,都会加100
        }
    },
    mutations: {
        add(state) {
            state.count++;
        },
        reduce(state) {
            state.count--;
        }
    }
})
export default store

7.2 使用Getter

<template>
  <div class="hello">
    <h1>{{ count }}</h1>
    <button @click="add">+</button>
    <button @click="reduce">-</button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  computed: {
    count() {
      // 每次count 的值发生变化的时候,都会进行加100的操作。
      return this.$store.getters.count;
    },
  },
  methods: {
    add() { // 执行store中的mutations里面add事件
      this.$store.commit("add"); 
    },
    reduce() {
      this.$store.commit("reduce");
    },
  },
};
</script>
<style scoped>
</style>

8.Mutation

  • 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
  • vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
  • 这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

你不能直接调用一个 mutation对象的increment,需要这样:

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);


const store = new Vuex.Store({
    state: {
        count: 10,
    },
    mutations: { //【添加两个修改count值的事件】
        add(state) {
            state.count ++;
        },
        reduce(state) {
            state.count--;
        }
    }
})
export default store

// HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ count }}</h1>
    <button @click="updata">+</button>
    <button @click="$store.commit('reduce')">-</button>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  computed: mapState(['count']),
  methods:{
    updata(){
      this.$store.commit('add')
    }
  }
};
</script>
<style scoped>
</style>


8.1 提交载荷(Payload)

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);


const store = new Vuex.Store({
    state: {
        count: 10,
    },
    mutations: { 
        add(state,n ){ // 【添加第二个参数】
            state.count +=n;
        },
        reduce(state,n) { // 【添加第二个参数】
            state.count-=n;
        }
    }
})
export default store

// HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ count }}</h1>
    <button @click="updata">+</button>
    <button @click="$store.commit('reduce',5)">-</button>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  computed: mapState(['count']),
  methods:{
    updata(){
      this.$store.commit('add',10)
    }
  }
};
</script>
<style scoped>
</style>

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);


const store = new Vuex.Store({
    state: {
        count: 10,
    },
    mutations: { 
        add(state,n ) { // 【添加第二个参数】
            state.count +=n.addNum;
        },
        reduce(state,n) { // 【添加第二个参数】
            state.count-=n.reduceNum;
        }
    }
})
export default store

// HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ count }}</h1>
    <button @click="updata">+</button>
    <button @click="$store.commit('reduce',{reduceNum:5})">-</button>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  computed: mapState(['count']),
  methods:{
    updata(){
      this.$store.commit('add',{addNum:8})
    }
  }
};
</script>
<style scoped>
</style>

8.2 对象风格的提交方式

<template>
  <div class="hello">
    <h1>{{ count }}</h1>
    <button @click="updata">+</button>
    <!-- <button @click="$store.commit('reduce', {reduceNum:5})">-</button> -->
    <button @click="$store.commit({type:'reduce', reduceNum:5})">-</button>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  name: "HelloWorld",
  computed: mapState(['count']),
  methods:{
    updata(){
      // this.$store.commit('add',{add:8})
      this.$store.commit({
        type:'add',
        addNum:5
      })
    }
  }
};
</script>
<style scoped>
</style>

8.3 Mutation 需遵守 Vue 的响应规则

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

  1. 最好提前在你的 store 中初始化好所有所需属性。
  2. 当需要在对象上添加新属性时,你应该: 【这里还有点懵】

8.4 使用常量替代 Mutation 事件类型

使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做

8.5 Mutation 必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子:

8.6 在组件中提交 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')`
    })
  }
}

自己演示的dome

//store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);


const store = new Vuex.Store({
    state: {
        count: 10,
    },
    mutations: {
        add(state) {
            state.count++;
        },
        reduce(state) {
            state.count--;
        }
    }
})
export default store
  • 第一种 参数是数组
// HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ $store.state.count }}</h1>
    <button @click="add">+</button>
    <button @click="reduce">-</button>
  </div>
</template>

<script>
import { mapMutations } from "vuex";
export default {
  name: "HelloWorld",
   methods: {
    // 第一种 参数是数组
    ...mapMutations(["add","reduce"]), // 使用mapMutations
    // 等同于:
    // add() { // 原来的形式
    //   this.$store.commit("add");
    // },
    // reduce() { // 原来的形式
    //   this.$store.commit("reduce");
    // },
  },
};
</script>
<style scoped>
</style>
  • 第二种 参数是对象
// HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ $store.state.count }}</h1>
    <button @click="addFun">+</button> <!-- 【这里修改为addFun】 -->
    <button @click="reduceFun">-</button> <!-- 【这里修改为reduceFun】 -->
  </div>
</template>

<script>
import { mapMutations } from "vuex";
export default {
  name: "HelloWorld",
  methods: {
    // 第二种 参数是对象
    ...mapMutations({
      addFun: "add", // 将 `this.addFun()` 映射为 `this.$store.commit('add')`
      reduceFun: "reduce", // 将 `this.addFun()` 映射为 `this.$store.commit('add')`
    }),
  },
};
</script>
<style scoped>
</style>


【未完结】

扩展文章:Vuex 通俗版教程

作者: Yeaseon Blog:yeaseonzhang.github.io 原文链接 [已挂]

  • 本文基本上是官方教程的盗版,用通俗易懂的文字讲解Vuex,也对原文内容有删减。

  • 如果你对以上声明不介意,那么就可以继续看本文,希望对你有所帮助。

  • 学习一个新技术,必须要清楚两个W,"What && Why"。

  • "XX 是什么?","为什么要使用 XX ,或者说 XX 有什么好处",最后才是"XX 怎么使用"。

1. Vuex是什么?

Vuex 类似 Redux 的状态管理器,用来管理Vue的所有组件状态。

2. 为什么使用Vuex?

当你打算开发大型单页应用(SPA),会出现多个视图组件依赖同一个状态,来自不同视图的行为需要变更同一个状态。

  • 遇到以上情况时候,你就应该考虑使用Vuex了,它能把组件的共享状态抽取出来,当做一个全局单例模式进行管理。这样不管你在何处改变状态,都会通知使用该状态的组件做出相应修改。

下面讲解如何使用Vuex。

3.最简单的Vuex示例

本文就不涉及如何安装Vuex,直接通过代码讲解。

import Vue from 'vue';
import Vuex form 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment (state) {
            state.count++
        }
    }
})

以上就是一个最简单的Vuex,每一个Vuex应用就是一个store,在store中包含组件中的共享状态==state==和改变状态的方法(暂且称作方法)mutations。

需要注意的是只能通过mutations改变store的state的状态,不能通过store.state.count = 5;直接更改(其实可以更改,不建议这么做,不通过mutations改变state,状态不会被同步)。

使用store.commit方法触发mutations改变state:

store.commit('increment');

console.log(store.state.count)  // 1

一个简简单单的Vuex应用就实现了。

4.在Vue组件使用Vuex

如果希望Vuex状态更新,相应的Vue组件也得到更新,最简单的方法就是在Vue的computed(计算属性)获取state

// Counter 组件
const Counter = {
    template: `<div>{{ count }}</div>`,
    computed: {
        count () {
            return store.state.count;
        }
    }
}

上面的例子是直接操作全局状态store.state.count,那么每个使用该Vuex的组件都要引入。为了解决这个,Vuex通过store选项,提供了一种机制将状态从根组件注入到每一个子组件中。

// 根组件
import Vue from 'vue';
import Vuex form 'vuex';

Vue.use(Vuex);
const app = new Vue({
    el: '#app',
    store,
    components: {
        Counter
    },
    template: `
        <div class="app">
            <counter></counter>
        </div>
    `
})

通过这种注入机制,就能在子组件Counter通过this.$store访问:

// Counter 组件
const Counter = {
    template: `<div>{{ count }}</div>`,
    computed: {
        count () {
            return this.$store.state.count
        }
    }
}

mapState函数

computed: {
    count () {
        return this.$store.state.count
    }
}

这样通过count计算属性获取同名state.count属性,是不是显得太重复了,我们可以使用mapState函数简化这个过程。

import { mapState } from 'vuex';

export default {
    computed: mapState ({
        count: state => state.count,
        countAlias: 'count',    // 别名 `count` 等价于 state => state.count
    })
}

还有更简单的使用方法:

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

Getters对象

如果我们需要对state对象进行做处理计算,如下:

computed: {
    doneTodosCount () {
        return this.$store.state.todos.filter(todo => todo.done).length
    }
}

如果多个组件都要进行这样的处理,那么就要在多个组件中复制该函数。这样是很没有效率的事情,当这个处理过程更改了,还有在多个组件中进行同样的更改,这就更加不易于维护。

Vuex中getters对象,可以方便我们在store中做集中的处理。Getters接受state作为第一个参数:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

在Vue中通过store.getters对象调用。

computed: {
  doneTodos () {
    return this.$store.getters.doneTodos
  }
}

Getter也可以接受其他getters作为第二个参数:

getters: {
  doneTodos: state => {
      return state.todos.filter(todo => todo.done)
  },
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

mapGetters辅助函数

与mapState类似,都能达到简化代码的效果。mapGetters辅助函数仅仅是将store中的getters映射到局部计算属性:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
    // 使用对象展开运算符将 getters 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

上面也可以写作:

computed: mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])

所以在Vue的computed计算属性中会存在两种辅助函数:

import { mapState, mapGetters } form 'vuex';

export default {
    // ...
    computed: {
        mapState({ ... }),
        mapGetter({ ... })
    }
}

Mutations

之前也说过了,更改Vuex的store中的状态的唯一方法就是mutations。

每一个mutation都有一个事件类型type和一个回调函数handler。

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

调用mutation,需要通过store.commit方法调用mutation type:

store.commit('increment')

Payload 提交载荷

也可以向store.commit传入第二参数,也就是mutation的payload:

mutaion: {
    increment (state, n) {
        state.count += n;
    }
}

store.commit('increment', 10);

单单传入一个n,可能并不能满足我们的业务需要,这时候我们可以选择传入一个payload对象:

mutation: {
    increment (state, payload) {
        state.totalPrice += payload.price + payload.count;
    }
}

store.commit({
    type: 'increment',
    price: 10,
    count: 8
})

mapMutations函数

不例外,mutations也有映射函数mapMutations,帮助我们简化代码,使用mapMutations辅助函数将组件中的methods映射为store.commit调用。

import { mapMutations } from 'vuex'

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

注 Mutations必须是同步函数。

如果我们需要异步操作,Mutations就不能满足我们需求了,这时候我们就需要Actions了。

Aciton

相信看完之前的Vuex的内容,你就已经入门了。那么Action就自己进行学习吧(Action有点复杂,我还需要时间消化)。

结语

上个月看Vuex还是一头雾水,现在看来Vuex也是很容易理解的。

学习一门新技术最重要的就是实践,单单看教程和demo是远远不够的。

前端路途漫漫,共勉。