Vue组件通信的几种方式(包括Vuex)

props/$emit

1、父组件通过props向子组件传值
<!-- 父组件 -->
<template>
    <div>
        <item @click="getSonString" v-bind:users="users" :test="test" ref="item"/>
    </div>
</template>

<script>
import Item from './item'
export default {
    data() {
        return {
            users:['lee', 'he', 'mei']
        }
    },
    methods: {
        test(){  //将父组件的方法传给子组件调用

        }
    },
    components: {
        Item
    }
}
</script>
<!-- 子组件 -->
<template>
    <div>
        <ul>
            <li v-for="user in users">{{ user }}</li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
          
        };
    },
    props: {
        users: {
            
        },
      test: {
        type: Function,
        default: null
      }
    }
};
</script>
image.png
2、子组件使用$emit触发事件向父组件传值
<!-- 父组件 -->
<template>
    <div>
        <item @click="getSonString"/>
        <div>{{ title }}</div>
    </div>
</template>

<script>
import Item from './item'
export default {
    data() {
        return {
            title: '',
            users:['lee', 'he', 'mei'],
            paper: '父组件的一个值',
        }
    },
    components: {
        Item
    },
    methods: {
        getSonString(title) {
            this.title = title
        }
    }
}
</script>
<!-- 子组件 -->
<template>
    <div>
        <button @click="toFatherString">
            用$emit事件触发将子组件的值传递给父组件
        </button>
        <!-- <ul>
            <li v-for="user in users">{{ user }}</li>
        </ul> -->
        <!-- <div>{{ this.$parent.paper }}</div> -->
    </div>
</template>

<script>
export default {
    data() {
        return {
            title: "来自子组件的值"
        };
    },
    props: {
        users: {
            
        },
    },
    methods: {
        toFatherString() {
            // 触发父组件的click事件,参数为title
            this.$emit("click", this.title);
        },
    },
};
</script>

image.png


使用$parent / $children与 ref

this.$parent.XXX在子组件中读取父组件的值
ref是给DOM元素或组件自定义名称,便于引用
ref父组件访问子组件,子组件标签设ref,使用this.$refs.item.XXX获取

<!-- 父组件 -->
<template>
    <div>
        <item ref="item"/>
    </div>
</template>

<script>
mounted() {
        // 读取第一个子组件数据,不推荐,你并不知道哪个是第一个
        console.log(this.$children[0].clips)

        //读取命名子组件数据,对应子组件的元素要设ref='item'
        console.log(this.$refs.item.clips)

        //从根组件查找组件数据
        console.log(this.$root.$children[0].name) // 入口APP
        console.log(this.$root.$children[0].$children[0].paper) // 第一个父组件,这里是自己
        console.log(this.$root.$children[0].$children[0].$children[0].clips)// 第一个子组件,这里是item
    }
</script>
<!-- 子组件 -->
<template>
    <div>
        <div>{{ this.$parent.paper }}</div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            title: "来自子组件的值",
            clips: "来自子组件的一个值"
        };
    }
};
</script>


使用$emit/$on

通过一个空的Vue实例作为中央事件总线,用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
关键代码

    var Event=new Vue();
    Event.$emit(事件名,数据);
    Event.$on(事件名,data => {});
// 发送端代码:
    methods: {
        send() {
            event.$emit('data-a',this.title)
        }
    },
//接收端代码:
    mounted() {
        event.$on("data-a", (title) => {
            this.title = title; //箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
        });
    }

案例:
event.js

import Vue from 'vue'
export default new Vue()
// 子组件item
<template>
    <div>
        <div>{{ title }}</div>
    </div>
</template>

<script>
import event from './event'

export default {
    data() {
        return {
            title: "item自己的数据"
        };
    },
    props: {
        users: {},
    },
    mounted() {
        event.$on("data-a", (title) => {     // 接收其他组件的title值
            this.title = title; //箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
        });
    },
};
</script>
// 子组件itemB
<template>
    <div>
        <button @click="send">itemB将数据发送给item</button>
    </div>
</template>

<script>
import event from './event'

export default {
    data() {
        return {
            title: '来自于子组件itemB的数据'
        }
    },
    methods: {
        send() {
            event.$emit('data-a',this.title)    // 将title值通过自定义事件发送出去
        }
    },
}
</script>
// 子组件itemC
<template>
    <div>
        <button @click="send">itemC将数据发送给item</button>
    </div>
</template>

<script>
import event from './event'

export default {
    data() {
        return {
            title: '来自于子组件itemC的数据'
        }
    },
    methods: {
        send() {
            event.$emit('data-a',this.title)
        }
    },
}
</script>
// 父组件
<template>
    <div>
        <ItemB @click="getSonString" />
        <ItemC @click="getSonString" />
        <item @click="getSonString" v-bind:users="users" ref="item"/>
        <div>{{ title }}</div>
    </div>
</template>

<script>
import Item from './item'
import ItemB from './itemB'
import ItemC from './itemC'
import event from './event'
export default {
    data() {
        return {
            title: '',
            users:['lee', 'he', 'mei'],
            paper: '父组件的一个值',
        }
    },
    components: {
        Item,
        ItemB,
        ItemC
    },
    methods: {
        getSonString(title) {
            this.title = title
        }
    },
    mounted() {
        event.$on("data-a", (title) => {
            this.title = title; //箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
        });
    },
    beforeDestroy() {
        // 及时销毁自定义事件,否则可能造成内存泄露
        event.$off('data-a', this.title)
    }
}
</script>

一开始

点击后,子组件item和父组件都能接收到itemB的数据

同样能接收到itemC的数据


Vuex

Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享。
上面传递数据的方法都要一次又一次复杂的传递,没有一个集中传递的地方,Vuex就是解决这个问题,他将所有状态(数据)都存在store中,其他组件只有从store中取即可。

优点:
1、能够集中管理数据,便于开发和后期维护
2、能够高效地实现组件间的数据共享,提高开发效率
3、存在Vuex中的数据都是响应式的,能够实时保持数据与页面的同步

什么情况使用:一般只有需要组件之间共享的数据才有必要存储到Vuex中,对于组件中的私有数据,依旧存储在组件自身的data中即可。

用法:
store.js文件

// 使用vuex插件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

// 创建store实例
export const store = new Vuex.Store({
  // 数据定义,唯一公共数据源,所有共享数据都有放在这
  state: {
    lists: null
  },
  // 类似于computed计算属性
  getters:{
    
  },
  // 数据修改,同步触发,类似于methods,
  mutations: {
    
  },
  // 异步操作的数据,只能调用mutations
  actions: {
    
  }
})

export default store

同时要挂载到vue实例中,main.js:

import Vue from 'vue'
import store from './vuex'
import App from './App'

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})





如果你的需要共享数据的组件多,项目大,也可以每个组件单独一个store文件,最后归到一个js文件管理

<!--dialog_store.js-->
export default {
  state:{
     
  },
  // 类似于computed计算属性
  getters:{
    
  },
  // 类似于methods,
  mutations:{
    
  },
  // 执行多个 mutations 就需要用 action ,异步
  actions:{
    
  }
}
// index.js
// 这个文件可以管理多个store文件
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

import dialog_store from './dialog_store.js';//引入某个store对象

export default new vuex.Store({
    modules: {
        dialog: dialog_store    // 管理多个store对象
    }  
})


state

数据定义,存储数据,类似于data
组件访问state数据的两种方式
第一种:$store.state

// store没被拆分
{{ $store.state.XXX }}

如果有单独的组件store文件,要加上store对象的名称

// store被拆分,要加store对象的名称
{{ $store.state.dialog.XXX }}

第二种:mapState(State的集合)

// store没被拆分
<template>
  <div class="hello">
    <p>{{ name }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  props: {
    
  },
  computed: {
      ...mapState(['name'])
  }
}
</script>
// store被拆分
<template>
  <div class="hello">
    <p>{{ name }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  props: {
    
  },
  computed: {
      ...mapState({
          'name':state=>state.Hello.name
      })
  }
}
</script>


Mutation

Mutation用于变更Store中的数据,而且只能通过Mutation变更Store数据,不可直接操作Store中的数据。
这样可以集中监控所有数据的变化。
Mutation中不能写异步代码,不然数据会不一致,异步代码要写在actions中

调用Mutation的第一种方式this.$store.commit():

export default{
    state: {
        count: 0
    },
    mutations: {
        add(state) {
            state.count++
        }
    },
    actions: {
    },
    modules: {
    }
}
<!-- 组件中 -->
<template>
  <div class="hello">
    <button @click="handle1">增加</button>
    {{ $store.state.Hello.count }}
  </div>
</template>

<script>

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  computed: {
      
  },
  methods: {
      handle1() {
          this.$store.commit('add')
      }
  }
}
</script>

调用Mutation的第二种方式:mapMutations(Mutations的集合)

<!-- 组件中 -->
<template>
  <div>
      <button @click="handle1">点击加一</button>
      <p>{{ $store.state.Hello.count }}</p>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
    methods: {
        ...mapMutations(['add']),
        handle1() {
            this.add()
        }
    },
}
</script>
<!-- 某个被拆分的store.js -->
export default{
    state: {
        name:'你好',
        count: 0
    },
    mutations: {
        add(state) {
            state.count++
        }
    },
    actions: {
    },
    modules: {
    }
}

Mutation传递参数的方式:

<!-- 名字为Hello的store.js -->
export default{
    state: {
        name:'你好',
        count: 0
    },
    mutations: {
        add(state) {
            state.count++
        },
        addN(state, step) {  
            // 对应下面普通提交
            state.count += step
        },
        addN(state, payload) {    // 这里的第二个参数是一个对象,保存着type和step
            // 第二种特殊提交,对应下面特殊提交
            state.count += payload.count    // 要这样调用
        }
    },
    actions: {
    },
    modules: {
    }
}
<template>
    <div>
        <button @click="handle1">点击加10</button>
        <p>{{ $store.state.Hello.count }}</p>

        <button @click="handle2(10)">点击加10第二种办法</button>
        <p>{{ count }}</p>
    </div>
</template>

<script>
import { mapState, mapMutations } from "vuex";

export default {
    methods: {
        ...mapMutations(["add", "addN"]),
        // mapMutations调用
        handle1() {
            this.addN(10);
        },
        // 普通调用
        handle2(step) {
          // 1、普通提交
            this.$store.commit("addN", step);
          // 2、特殊提交
            this.$store.commit({
                type: 'addN',
                step
            })
        },
    },
    computed: {
        ...mapState({
            'count': (state) => state.Hello.count,
        })
    },
};
</script>

使用常量替代 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
    }
  }
})


Action异步操作

异步执行 mutations 就需要用 action
Action只能调用mutations的函数,不能操作数据,只有mutations才有权利操作state中的数据

export default{
    state: {
        name:'你好',
        count: 0
    },
    mutations: {
        add(state) {
            state.count++
        },
        addN(state, step) {  
            state.count += step
        }
    },
    actions: {
        addAsync(context) {   // 异步,延迟1000毫秒触发mutations里的add函数
            setTimeout(() => {
                context.commit('add')
            }, 1000);
        }
    },
    modules: {
    }
}
<template>
    <div>
        <button @click="handle1">点击加一</button>
        <p>{{ $store.state.Hello.count }}</p>

        <button @click="handle2">点击加一第二种办法</button>
        <p>{{ count }}</p>

        <button @click="handle3">异步点击加一</button>
        <p>{{ count }}</p>
    </div>
</template>

<script>
import { mapState, mapMutations } from "vuex";

export default {
    methods: {
        ...mapMutations(["add", "addN"]),
        handle1() {
            this.addN(4);
        },
        handle2() {
            this.$store.commit("addN", 4);
        },
        handle3() {   // 触发addAsync异步函数
            this.$store.dispatch('addAsync')
        }
    },
    computed: {
        ...mapState({
            'count': (state) => state.Hello.count,
        })
    },
};
</script>

触发action异步任务时带参数:

export default{
    state: {
        name:'你好',
        count: 0
    },
    mutations: {
        add(state) {
            state.count++
        },
        addN(state, step) {  
            state.count += step
        }
    },
    actions: {
        addAsync(context, step) {    // 带参数,任意加多少
            setTimeout(() => {
                context.commit('addN', step)
            }, 1000);
        }
    },
    modules: {
    }
}
export default {
    methods: {
        handle4() {
            this.$store.dispatch('addAsync', 4)  // 带参数
        }
    }
};

this.$store.dispatch()是触发actions的第一种方式
触发actions的第二种方式:
没错,又是map,mapActions

<!-- 组件中 -->
<template>
    <div>
        <button @click="addAsync(4)">异步带参数点击加一</button>  <!-- 直接使用addAsync函数 -->
        <p>{{ count }}</p>
    </div>
</template>
<script>
import { mapState, mapMutations, mapActions } from "vuex";  // 引入mapActions 

export default {
    methods: {
        ...mapMutations(['add', 'addN']),
        ...mapActions(['addAsync']),   // 映射actions中的addAsync函数,映射为自己的函数
    },
    computed: {
        ...mapState({
            'count': (state) => state.Hello.count,
        })
    },
};
</script>


Getter

Getter对store中已有的数据处理之后形式新的数据,类似于computed计算属性
store中的数据发生变化,Getter的数据也会跟着变化
定义Getter

export default{
    state: {
        name:'你好',
        count: 0,
        students: [
          { name:"lili", age: 23 },
          { name:"lin", age: 12 },
          { name:"zh", age: 65 },
          { name:"xue", age: 16 },
          { name:"he", age: 14 }
        ]
    },
    mutations: {
        add(state) {
            state.count++
        },
        addN(state, step) {  
            state.count += step
        }
    },
    actions: {
        addAsync(context, step) {
            setTimeout(() => {
                context.commit('addN', step)
            }, 1000);
        }
    },
    // 计算属性
    getters: {
        showNum(state) {
            return `当前数字为` + state.count
        },
        // 第二个参数为getters,可以调用getters中的方法
        showNumLength(state, getters) {
            return getters.showNum.length
        },
        // 返回年龄大于20的学生
        more20stu(state) {
            return state.students.filter(s => s.age > 20)
        },
        // getters传参数:还可以返回函数,返回大于age的学生,调用:$store.getters.moreAgeStu(18)
        moreAgeStu(state) {
            return function (age){
                  return state.students.filter(s => s.age > age)
            }
        }
    }
}

使用:

{{ $store.getters.showNum }}

第二种使用,没错,同样有map,mapGetters

<template>
    <div>
        <p>{{ showNum }}</p>   <!-- 直接使用 -->
    </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from "vuex";   // 引用

export default {
    methods: {
        
    },
    computed: {
        ...mapState({
            'count': (state) => state.Hello.count,
        }),
        ...mapGetters(['showNum'])    // 映射
    },
};
</script>

provide/inject

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容