先来看看一个简单德例子
target.PNG
一个带按钮和计数器的简单应用程序. 按下按钮会使计数器递增. 虽然这很容易实现, 但目标是理解基本概念.
problem.dot.PNG
假设应用程序中有两个组件:
1, 一个按钮 (事件的来源)
2, 计数器 (必须根据原始事件反映更新)
这两个组件彼此不了解, 无法相互通信. 即使是最小的 web 应用程序, 这也是一种非常常见的模式. 在较大的应用程序中, 许多组件相互通信, 并且必须相互控制. 以下是基本待办事项列表的一些交互:
todo.dot.PNG
这篇文章的目标
我们将探索解决同一问题的四种方法:
1, 使用事件广播在组件之间进行通信
2, 使用共享状态对象
3, 使用 vuex
首先, 我们 IncrementButton 在 src/components/IncrementButton.vue 以下位置创建组件:
- <template>
- <button @click.prevent="activate">+1</button>
- </template>
- <script>
- export default {
- methods: {
- activate () {
- console.log('+1 Pressed')
- }
- }
- }
- </script>
- <style>
- </style>
接下来, 我们创建 CounterDisplay 实际显示计数器的组件. 让我们创建一个新的基本 vue 组件 src/components/CounterDisplay.vue
- <template>
- Count is {{ count }}
- </template>
- <script>
- export default {
- data () {
- return {
- count: 0
- }
- }
- }
- </script>
- <style>
- </style>
替换 App.vue 为此文件:
- <template>
- <div id="app">
- <h3>Increment:</h3>
- <increment></increment>
- <h3>Counter:</h3>
- <counter></counter>
- </div>
- </template>
- <script>
- import Counter from './components/CounterDisplay.vue'
- import Increment from './components/IncrementButton.vue'
- export default {
- components: {
- Counter,
- Increment
- }
- }
- </script>
- <style>
- </style>
现在, 如果 NPM run dev 再次运行, 并在浏览器中打开页面, 您应该会看到一个按钮和一个计数器. 单击该按钮会在控制台中显示一条消息
解决方案 1: 事件广播
solution1.dot.PNG
让我们修改组件中的脚本. 首先, IncrementButton.vue 我们使用 $dispatch 向父级发送一条消息, 单击该按钮
- export default {
- methods: {
- activate () {
- // Send an event upwards to be picked up by App
- this.$dispatch('button-pressed')
- }
- }
- }
在 App.vue 我们收听来自孩子的事件并重新向所有孩子广播新事件以增加.
- export default {
- components: {
- Counter,
- Increment
- },
- events: {
- 'button-pressed': function () {
- // Send a message to all children
- this.$broadcast('increment')
- }
- }
- }
> 在 CounterDisplay.vue 我们听取 increemnt 事件并增加状态的价值.
- export default {
- data () {
- return {
- count: 0
- }
- },
- events: {
- increment () {
- this.count ++
- }
- }
- }
这种方法的一些缺点:
1, 对于每个操作, 父组件需要连接并将事件 "分派" 到正确的组件.
2, 对于更大的应用程序来说, 很难理解事件的来源.
3, 业务逻辑可以在任何地方, 这可能使其无法维护.
解决方案 2: 共享状态
让我们回复一下我们在解决方案 1 中所做的一切. 我们创建一个新文件 src/store.JS
- export default {
- state: {
- counter: 0
- }
- }
我们先修改一下 CounterDisplay.vue:
- <template>
- Count is {{ sharedState.counter }}
- </template>
- <script>
- import store from '../store'
- export default {
- data () {
- return {
- sharedState: store.state
- }
- }
- }
- export default store
- </script>
我们可以修改 IncrementButton.vue
- import store from '../store'
- export default {
- data () {
- return {
- sharedState: store.state
- }
- },
- methods: {
- activate () {
- this.sharedState.counter += 1
- }
- }
- }
解决方案 3:Vuex
定义一个 vuex.JS 文件, 并在 main.JS 里面引入
1. 在 main.JS 里面引入 vuex
- import Vuex from 'vuex'
- import vuexs from './vuex'
- Vue.use(Vuex);
- const appVuex = new Vuex.Store({
- vuexs
- })
- new Vue({
- el: '#app',
- router,
- appVuex ,
- components: { App },
- template: '<App/>'
- })
vuex.JS 文件代码如下
- const state = {
- count :0
- }
- const mutations={
- changeMenuIndex(state, num) {
- state.count = num
- }
- }
- const actions={
- post:function(context,payload){// 这里的 context 和我们使用的 $store 拥有相同的对象和方法
- return new Promise(function(resolve, reject){
- axios.post(payload.path,payload.datas).then(data=>{
- resolve(data)
- })
- });
- }
- }
- export default {
- state,
- mutations,
- actions
- }
state 里面定义自己需要的变量, 这里面的变量只能通过 mutations, 或者 actions 改变, 以下是获取 state 变量方式
1, 在计算属性中返回某个状态:
- <div>{{ count }}</div>
- computed: {
- count () {
- return store.state.count
- }
- }
2,mapState 辅助函数
- import {mapState} from "vuex";
- computed: {
- ...mapState(['count'])
- },
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation.Vuex 中的 mutation 非常类似于事件: 每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler). 这个回调函数就是我们实际进行状态更改的地方, 并且它会接受 state 作为第一个参数:
- const mutations={
- changeMenuIndex(state, num) {
- state.count = num
- }
- }
- // 在组件中使用, num 为传过来的参数
- this.$store.commit('changeMenuIndex', 10);
- // 这里有个问题就是怎样传多个参数, mutations 只能定义两个参数, 所以这里只能以对象的形式传值
- const mutations={
- changeMenuIndex(state, payload) {
- state.count = payload.vuexNum
- }
- }
- this.$store.commit('changeMenuIndex', {
- vuexNum: 10
- });
- // 也可以这样传, type 为函数名, 其他的都是传参
- store.commit({
- type: 'changeMenuIndex',
- vuexNum: 10
- })
这里可以使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用
- import { mapMutations } from 'vuex'
- export default {
- // ...
- methods: {
- // mapMutations 工具函数会将 store 中的 commit 方法映射到组件的 methods 中
- ...mapMutations([
- 'changeMenuIndex', // 将 `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')`
- })
- }
- }
Action 类似于 mutation, 不同在于:
Action 提交的是 mutation, 而不是直接变更状态.
Action 可以包含任意异步操作.
- const actions={
- // 写法一
- increment (context) {
- context.commit('increment')
- }
- // 写法二
- increment ({ commit }) {
- commit('changeMenuIndex')
- }
- }
- // 这里用到了对象的结构
- // 因为函数的参数是一个对象, 函数中用的是对象中一个方法, 我们可以通过对象的
- // 解构赋值直接获取到该方法
- // 因为 context 本身就是一个对象, 里面有 state 对象和 commit 方法例如
- let context {
- state: {},
- commit: function(){}
- }
- // 根据对象结构可以定义如下:
- let {state,commit} = context
- console.log(state)//state: {};
- console.log(commit)//commit: function(){};
- // 所以放在函数里面就是
- increment ({state,commit} ) {
- commit('changeMenuIndex')
- }
- // 具体 es6 的用法可以参考
- `http://es6.ruanyifeng.com/`
在组件中使用 this.$store.dispatch('increment',10);, 这里的传参与上面讲的一样, 这些都是一些常用的东西, 还有一些如 getter 和 moudle 等可以看看文档
https://vuex.vuejs.org/zh/guide/actions.html
来源: http://www.jianshu.com/p/10310d6cbea7