VUE实现组件之间的通信

VUE之间的通信方式

在使用vue框架搭建项目时总会碰到父子组件,隔代组件,兄弟组件之间的传值,这里我介绍六种传值方式

一、使用props在父子之间进行传递值

父组件使用v-bind绑定相应的值,在子组件中使用props进行接收,获得父组件的值
其中使用this.$emit()来进行调用父组件的事件
数据向下行,事件向上流

在父组件Parent中:

<Child :data-list='datalist' v-on:parent-chang-count="parentcount"></Child>
<script>
data(){
     
    return {
     
        datalist:[
        {
     id:1,name:1,count:1,sex:1},
        {
     id:1,name:1,count:1,sex:1},
        {
     id:1,name:1,count:1,sex:1}
        ]
    }
}
methods:{
     
    perentcount(index,row){
     
        console.log(index)
        console.log(row)
    }
}
</script>

在子组件Child中:

<template>
    <div class="hello">
        <el-table :data="datalist" style="width:100%" border>
            <el-table-column props="id"></el-table-column>
            .....
            <el-table-column label="购买数量" min-width=' 200' align="center">
                <template slot-scope="scope">
                    <i class="el-icon-minus" v-on:click="jiaCount(scope.$index, scope.row)"></i>
                    <el-input v-model="scope.row.count" size='mini'></el-input>
                    <i class=" el-icon-plus" v-on:click="ianCount(scope.$index, scope.row)"></i>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

<script>
    export default {
     
        name: "Child",
        props: ['datalist'],
        methods: {
     
            jianCount(index, row) {
     
                row.count--;
                if (row.count < 1) row.count = 1;
                //在这里使用了$emit来使用父组件的事件函数
                this.$emit('parent-change-count', index, row);
            }
            ,
            jiaCount(index, rom) {
     
                row.count++;
                //可以判斷是否超过库存数量;
                this.$emit('parent- change count', index, row);
            }
        }
</script>

二、使用共用js组件实现数据传递

在store中创建一个bus.js(名字任意)
凡是要共享数据 的组件,都要引用bus.js文件;
每一个Vue实例对象,都会有一个 $on(), $emit()两个方法;

  • Bus.$on(自定义的事件名,回调函数(形参)): 监听事件
  • Bus.$emit(自定义的事件名, 实际参数): 触发事件
  • Bus.$off(自定义的事件名) : 销毁事件

//EventBus 主要解决非父子组件之间的通信。不适合用于大型的项目;大型项目,一般用Vuex来处理;
//提示: 页面跳转路由时,不建议使用EventBus来通信。

import Vue from 'vue'
export default new Vue(); 

在需要使用的组件和视图中引用bus.js
如在app.vue中

<script>
import Bus from '@/store/bus.js'
data(){
     
    return {
     
        ordernumber:0
    }
}
mounted(){
     
    Bus.$on('changeShopingCount',num=>{
     
    this.orderNumber=num;})
}
beforeDestroy(){
     
    Bus.$off('changeShopingCount')
}
</script>

三、使用vuex来实现信息的传递 比如登录状态和taken的存储

vuex实现状态管理 可以在多个不同组件中使用相同的值,一个值的变化可以改变多个组件里面的内容
修改vuex里面值的方法主要分非严格和严格模式
非严格模式可以使用this指向直接修改变的值
在非严格模式下修改值可以不遵循单向数据流向
严格模式必须使用actions或者mutations来修改变量的值:
使用actions修改值的时候使用dispatch
使用mutations修改值的时候使用commit

以下是非严格模式的情况

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
     
    //状态:
    state: {
     
        isLogin: false,
        token: ''
    },
    //相当于是计算属性:
    getters: {
     },
    //方法: mutations只支持同步操作,不支持异步操作;
    mutations: {
     
        //创建一个方法,用于直接修改state中的登录状态属性的值:
        setLoginState(state, payLoad) {
     
            //payLoad:载荷,用于接收传的参数;
            // state.isLogin = payLoad //普通参数
            state.isLogin = payLoad.stateValue; //对象做参数
        },
        //    修改token:
        setToken(state, payLoad) {
     
            state.token = payLoad.token;
        },
        getToken(state){
     
            return state.token;
        }
    },
    //方法: actions还可以支持异步的操作;
    actions: {
     
        // 触发mutations中的方法,修改登录状态 :
        //Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象:
        setLoginStateActions(context, obj) {
     
            context.commit('setLoginState', obj)
        },
        getToken(context){
     

            // new Promise((resolve, reject)=>{
     
                context.commit('getToken')
            //         .then(res=>{
     
            //             return res;
            //         })
            // })

        },

        //    修改token:
        setTokenActions(context, obj) {
     
            context.commit('setToken', obj);
        }
    },
    //多仓库的应用;
    modules: {
     },
    //使用严格模式
    //在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
    // strict:true
    strict: process.env.NODE_ENV !== 'production' //不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
})

在vue组件中实现登录并存储token,可以不用使用多种方式改变state

<template>
    <div>
        <h1>axios 请求后台和带token请求</h1>
        <div>
            登录状态:{
     {
     $store.state.isLogin}}
        </div>
        <div>
            token: {
     {
     $store.state.token}}
        </div>
        <div>
            <el-button type="primary" @click="userLogin">登录</el-button>
        </div>
    </div>
</template>

<script>
    export default {
     
        name: "myAxios",
        data() {
     
            return {
     
                // token: ''
                bg: 'red'
            }
        },
        methods: {
     
            userLogin() {
     
                let that = this;
                this.$axios.post('/api/user/login',
                    this.qs.stringify({
     
                        usernameOrTel: 'lisi',
                        password: '123'
                    })
                )
                    .then((res) => {
     
                        console.log(res)
                        console.log(res.headers)
                        // 获取token,并存储
                        // that.token = res.headers['authenticate']
                        //把token放在本地存储中;
                        sessionStorage.setItem('token', res.headers['authenticate']);
                        //把token放在VUEX的状态中中;
                         that.$store.state.token = res.headers['authenticate'];
                         that.$store.state.isLogin = true;
                    })
                    .catch((res) => {
     
                        console.log(res)
                    })
            }
        }
    }
</script>

在严格模式下需要遵循单数据流向 component–>actions–>mutations实现数据的修改
store中找需要修改的vuex文件;添加严格模式 strict: process.env.NODE_ENV !== ‘production’
以下是严格模式的情况:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
     
    //状态:
    state: {
     
        isLogin: false,
        token: ''
    },
    //相当于是计算属性:
    getters: {
     },
    //方法: mutations只支持同步操作,不支持异步操作;
    mutations: {
     
        //创建一个方法,用于直接修改state中的登录状态属性的值:
        setLoginState(state, payLoad) {
     
            //payLoad:载荷,用于接收传的参数;
            // state.isLogin = payLoad //普通参数
            state.isLogin = payLoad.stateValue; //对象做参数
        },
        //    修改token:
        setToken(state, payLoad) {
     
            state.token = payLoad.token;
        },
        getToken(state){
     
            return state.token;
        }
    },
    //方法: actions还可以支持异步的操作;
    actions: {
     
        // 触发mutations中的方法,修改登录状态 :
        //Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象:
        setLoginStateActions(context, obj) {
     
            context.commit('setLoginState', obj)
        },
        getToken(context){
     
                context.commit('getToken')
        },
        //    修改token:
        setTokenActions(context, obj) {
     
            context.commit('setToken', obj);
        }
    },
    //多仓库的应用;
    modules: {
     },
    //使用严格模式
    //在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
    // strict:true
    strict: process.env.NODE_ENV !== 'production' //不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
})

在登录组件中只能使用actions或者mutation来改变state

<template>
    <div>
        <h1>axios 请求后台和带token请求</h1>
        <div>
            登录状态:{
     {
     $store.state.isLogin}}
        </div>
        <div>
            token: {
     {
     $store.state.token}}
        </div>
        <div>
            <el-button type="primary" @click="userLogin">登录</el-button>
            <el-button type="primary" @click="getUser">获取用户</el-button>
        </div>
    </div>
</template>

<script>
    export default {
     
        name: "myAxios",
        data() {
     
            return {
     
                // token: ''
                bg: 'red'
            }
        },
        methods: {
     
            userLogin() {
     
                let that = this;
                this.$axios.post('/api/user/login',
                    this.qs.stringify({
     
                        usernameOrTel: 'lisi',
                        password: '123'
                    })
                )
                    .then((res) => {
     
                        console.log(res)
                        console.log(res.headers)
                        //把token放在本地存储中;
                        sessionStorage.setItem('token', res.headers['authenticate']);
                        //    在严格模式下的修改token的方法:
                        that.$store.dispatch({
     type: 'setTokenActions', token: res.headers['authenticate']});
                        that.$store.dispatch({
     type: 'setLoginStateActions', stateValue: true})
                    })
                    .catch((res) => {
     
                        console.log(res)
                    })
            }
        }
    }
</script>

以下是介绍严格模式获取state和修改state的情况

<template>
    <div>
        <h1>Vuex通信: </h1>
        <div>
<!--            在组件的模板中,直接引用VUEX的状态中的属性:-->
            登录状态:{
     {
     $store.state.isLogin}}
        </div>
        <div>
            token: {
     {
     $store.state.token}}
        </div>
        <div>
            <el-button type="primary" @click="modifyIsLoginState">修改状态</el-button>
        </div>
    </div>
</template>

<script>
    export default {
     
        name: "myVuex",
        methods: {
     
            modifyIsLoginState(){
     
                console.log("modifyIsLoginState");
          // 操作状态的方法1:
         // this.$store.state.isLogin = 'aaaa';  //严格模式下,不要直接修改state。系统会抛出一个错误异常!

        // 操作状态的方法2:正规的单向数据方式,去修改state。
        // this.$store.dispatch('setLoginStateActions',true)
       //  以对象做参数时,type是固定的属性名;多参数时,添加对象的属性名就可以了
      // this.$store.dispatch({type: 'setLoginStateActions', stateValue: true});

     //  操作状态的方法3: 直接通过commit(),触发mutations中的函数去修改状态:
     //                 this.$store.commit('setLoginState',true)
                this.$store.commit({
     type: 'setLoginState',stateValue: true})  ;
            },
       },
        mounted() {
     
            console.log(this)
        }
    }
</script>

<style scoped>

</style>

在组件中使用vuex的属性,显示相关的值有两种方式

  • $store.state.token 直接调用state中的属性
  • $store.getters.getToken 调用getters里面的方法 然后实现数据的显示

在vuex文件里面的getters属性添加相关的方法:

getters: {
     
        getToken(state){
      //注入一个state做参数;
            return state.token;  //返回一个值;
        }
    },

在组件中显示相关的值:

<div style="border: 1px solid blue;height:100px;">
            token: {
     {
     $store.state.token}}
</div>
        //绑定在v-html中
<div style="border: 1px solid red;height:100px;" v-html="$store.getters.getToken">
            getters来获取 token:
</div>

映射vuex里面的数据
在需要使用数据的组件中引入vuex的方法
组件里面使用相关的方法时 传参是 其中的对象名vuex里面的对象名一致。

<template>
    <div>
        <h1>Vuex通信: </h1>
        <div>
<!--            在组件的模板中,直接引用VUEX的状态中的属性:-->
            登录状态:{
     {
     $store.state.isLogin}}
        </div>
        <div>
            token: {
     {
     $store.state.token}}
        </div>
        <div style="border: 1px solid red; height: 100px;" v-text="getToken">
           没有token
        </div>
        <div>
            <el-button type="primary" @click="modifyIsLoginState">修改状态</el-button>
            <el-button type="primary" @click="setLogin({stateValue: 789})">映射Actions中的方法</el-button>
            <el-button type="primary" @click="setTokenActions({token: '天王盖地虎,...'})">映射Actions中的方法,修改token</el-button>
        </div>
    </div>
</template>

<script>
    import {
     mapGetters, mapMutations, mapActions} from 'vuex';
    export default {
     
        name: "myVuex",
        computed:{
     
            //映射Getters中的方法到本地,做当前组件的计算属性中的方法;
            ...mapGetters(['getToken'])
        },
        methods: {
     
        //    通过映射mutations中的方法,到当前组件的methods中。调用时,当本地方法来用;
            ...mapMutations(['setLoginState','setToken']),  //映射方式1:数组的方法
        //    映射Actions中的方法到本地:
            ...mapActions (['setLoginStateActions','setTokenActions'])
        //  ...mapActions({setLogin: 'setLoginStateActions'})  //映射方式2:对象,取一个别名的方式,setLogin就是别名;
        },
        mounted() {
     
            console.log(this)
        }
    }
</script>

四、$attrs $listeners传值法 理解为祖先传后代

其中 $attrs与 $props类似,都是接受值的方法
而 $listeners是指后代触发祖先事件的方法
在祖先组件中ancestors中:

<template>
    <el-row>
        <el-col :span="24">
            <el-page-header title="案例4:跨级通信" content="$attrs,$listeners:"></el-page-header>
        </el-col>
        <el-col :span="24">
        //在这里将值绑定到子组件上去
            <courseSystem v-on:myTest="myTest" :html="html" :jsoo="jsoo" :css="css" :vue="vue" web="WEB前端体系"></courseSystem>
        </el-col>
    </el-row>
</template>

<script>
    //es6语法
    const courseSystem = ()=>import('@/components/course/courseSystem.vue');
   // import courseSystem from '@/components/course/courseSystem.vue';
    export default {
     
        name: "myAttrs",
        components:{
     courseSystem},
        data (){
     
            return {
     
                html:'HTML体系',
                jsoo: 'JavaScript面向对象体系',
                css: 'Cascading Style Sheets 体系',
                vue: 'VUE全家桶'
            }
        },
        methods :{
     
            myTest(a){
     
                console.log('myTest方法:',a)
            }
        }
    }
</script>

在爷爷组件grandfather中

<template>
    <el-row style="border: 1px solid red;">
        <el-col :span="24">
            <h1>我们的体系:{
     {
     web}}</h1>
            <div>$attrs:{
     {
     $attrs}}</div>
            //v-bind="$attrs"     把$attrs传给它的子组件 ,它的子组件中,放可有$attrs属性的值
            // v-on="$listeners" :通过 v-on  绑定$listeners,把父组件中所有监听的事件,传给子组件;
            <course-list v-bind="$attrs" v-on="$listeners" v-on:test11="test11"></course-list>
        </el-col>
    </el-row>
</template>
//1. $attrs: 用来接收除 props 属性中包括的值以外的父组件传递进来的属性的值;
//注意在props里面如果接收了父组件传过来的属性与值,在$attrs里面是不会接收到改属性的,$attrs会将该属性剔除。
<script>
    const courseList = ()=>import('@/components/course/courseList.vue');
    export default {
     
        name: "courseSystem",
        components:{
     courseList},
        props:['web'],         //声明了props包括的属性,那么其它绑定的属性,则由$attrs来接收。
        mounted() {
     
            console.log('$attrs:',this.$attrs);
            console.log('$props:',this.$props);
            console.log('$listeners:',this.$listeners); //监听父级组件作用域中,所有的v-on的事件:
        },
        methods:{
     
            test11(){
     
                console.log('test11')
            }
        },
        inheritAttrs: false// 默认是true, 把$attrs对应的属性渲染到子组件的根元素上;false:不渲染$attrs 绑定的属性到子组件的根元素.
    }
</script>

<style scoped>

</style>

在爸爸组件father中:

<template>
    <div style="border: 1px solid blue;margin: 10px;">
        <h3>课程列表:{
     {
     html}}</h3>
        <div> $attr:{
     {
     $attrs}}</div>
        <ul><li v-for="item in $attrs">{
     {
     item}}</li></ul>
        <div><button type="info" @click="triggerTest">触发caseThree组件中的事件</button></div>
        <hr>
        <div>
            //给子组件绑定$attrs
            <check-course v-bind="$attrs" v-on="$listeners"></check-course>
        </div>

    </div>
</template>

<script>
    const checkCourse = ()=>import('@/components/course/checkCourse.vue')
    export default {
     
        name: "courseList",
        props:['html'],
        components:{
     checkCourse},
        inheritAttrs: false,
        methods:{
     
            triggerTest (){
     
                // 在子组件中,通过$emit()来触发外层组件上v-on绑定的事件:
                this.$emit('myTest', 'I LOVE YOU!');
                console.log('courseList 触发 myAttrs组件上的事件 ')
            }
        }
    }
</script>

在儿子组件child中:

<template>
    <div style="border:1px solid darkgreen; margin: 10px;">
        <h3>选择课程:{
     {
     jsoo}}</h3>
        <div>$attr:{
     {
     $attrs}}</div>
        <label v-for="item in $attrs">
            <input type="checkbox">{
     {
     item}}
        </label>
        <button type="button" @click="test2">触发最外层绑定的事件</button>
    </div>
</template>

<script>
    export default {
     
        name: "checkCourse" ,
        props: ['jsoo'],
        inheritAttrs: false,
        methods:{
     
            test2(){
     
                this.$emit('myTest','1111111111')
                console.log('test2方法的触发:触发myTest方法')
                this.$emit('test11')
            }
        },
        mounted() {
     
            console.log("checkCourse:",this.$listeners)
        }

    }
</script>

$attrs和 $props可以搭配使用 但是一个用了 另一个不能再用。
每一层都会使用props来接收某些值,所以每一层显示的attrs会越来越少
注意 $listeners与 $emit的搭配使用,实现子代调用祖先的方法

五、provide和inject传值法

在爷爷组件father中:
使用provide来返回数据给子组件
在子组件中使用inject来注入父组件提供的数据

<template>
    <div style="border: 1px solid red; padding: 20px;">
        <h1>产品列表  <button @click="modifyData1">外层修改数据</button></h1>
        <provideA></provideA>

    </div>
</template>

<script>
    import provideA from '@/components/provide/provideA.vue'
    export default {
     
        name: "myProvide",
        components: {
     provideA},
        methods: {
     
            modifyData1(){
     
                this.dataList.splice(3,1, {
     id: 3, name: 'BBBBB',des:'都是皇帝的新装', count: 1})
                this.resUser.uname = '皇帝'
            }
        },
        data (){
     
            return {
     
                dataList: [
                    {
     id: 1, name: '衣服1',des:'都是皇帝的新装', count: 1},
                    {
     id: 2, name: '衣服2',des:'都是皇帝的新装', count: 1},
                    {
     id: 3, name: '衣服3',des:'都是皇帝的新装', count: 1},
                    {
     id: 4, name: '衣服4',des:'都是皇帝的新装', count: 1},
                    {
     id: 5, name: '衣服5',des:'都是皇帝的新装', count: 1}
                ],

            //  给provide相关的一个属性
                resUser: {
     uid: 10001, uname: '张三'}
            }
        },
      // 有几种使用方式
      //第一种
      // provide:{ // 给后代组件提供共享的数据:
           // dataList: [
             //   {id: 1, name: '衣服1',des:'都是皇帝的新装', count: 1},
             //   {id: 2, name: '衣服2',des:'都是皇帝的新装', count: 1},
             //   {id: 3, name: '衣服3',des:'都是皇帝的新装', count: 1},
              //  {id: 4, name: '衣服4',des:'都是皇帝的新装', count: 1},
              //  {id: 5, name: '衣服5',des:'都是皇帝的新装', count: 1}
           // ]
        //}
        //第二种
       provide (){
     
           return {
     
               // dataList: [
               //     {id: 1, name: '衣服1',des:'都是皇帝的新装', count: 1},
               //     {id: 2, name: '衣服2',des:'都是皇帝的新装', count: 1},
               //     {id: 3, name: '衣服3',des:'都是皇帝的新装', count: 1},
               //     {id: 4, name: '衣服4',des:'都是皇帝的新装', count: 1},
               //     {id: 5, name: '衣服5',des:'都是皇帝的新装', count: 1}
               // ],
               //第三种
               dataList: this.dataList,
               user: this.resUser  //把组件的属性赋值给user。就是给后代组件,提供了当前的this对象。
           }
       }
    }
</script>

父组件father中:

<template>
    <div>
        provideA inject: {
     {
     user.uname}}
        <ul>
            <li v-for="item in dataList">
                {
     {
     item.name}}
            </li>
        </ul>
        <div style="border: 1px solid blue; height: 100px;">
            <provideB></provideB>
        </div>
    </div>
</template>

<script>
    import provideB from '@/components/provide/provideB.vue'
    export default {
     
        name: "provideA",
        components:{
     provideB},
        inject:['dataList','user'] //注入父级提供的数据:
    }
</script>

子组件child中:
父组件传递实例对象的数据
子组件使用实例对象的方式来修改父组件的数据

<template>
    <div>
        provideB当中的inject: {
     {
     user.uname}}
        <ol>
            <li v-for="item in dataList" style="display:  inline-block; width: 100px;">
                {
     {
     item.name}}
            </li>
        </ol>

        <button @click="modifyData">修改数据</button>
    </div>
</template>

<script>
    export default {
     
        name: "provideB",
        inject:['dataList','user'],  //只要在子孙组件中,通过inject来注入属性名,就可以拿 到最外层组件提供的数据。
        methods:{
     
            modifyData () {
     
                let arr = this.dataList;
                arr.splice(0,1, {
     id: 1, name: '衣服AAA',des:'都是皇帝的新装', count: 1})
                this.user.uname = '皇子'
            }
        }
    }
</script>

六、 $parent和 $children传值法 只实现父子之间的传值,其他方式不行

输出this.$children时,当组件有多个时,输出的是一个数组
父组件中给子组件绑定ref传值给子组件

<template>
    <div>
        <h1>ref属性, $parent, $children : {
     {
     title}}</h1>
        <p> $parent, $children这两种方法的弊端是,无法在跨级或兄弟间通信。</p>
<!--        在子组件使用时,添加ref属性,通过$refs.comA对象来获取子组件对象 -->
        <childrenA ref="comA"></childrenA>
        <childrenB ref="comB"></childrenB>
    </div>
</template>

<script>
    import childrenA from  '@/components/children/childrenA.vue'
    import childrenB from  '@/components/children/childrenB.vue'
    export default {
     
        name: "myRef",
        data (){
     
            return {
     
                title: 'CaseFive父组件'
            }
        },
        components: {
     
            childrenA,childrenB
        },
        mounted() {
     
            console.log(this.$refs);//{comA: VueComponent, comB: VueComponent}
            const comA = this.$refs.comA;
            console.log(comA.title)
            comA.title = '今天天气好热';

            // console.log(this.$children) //[VueComponent, VueComponent]
            //通过下标来操作子组件对象:
            console.log(this.$children[0].title)
            this.$children[0].title = '今天不打滚'
        }
    }
</scrip t>

子组件一
在子组件中是用this.$parent时获得父组件的值

<template>
    <div>
        <h1> {
     {
     title}}</h1>
    </div>
</template>

<script>
    export default {
     
        name: "childrenA",
        data (){
     
            return {
     
                title:"组件A"
            }
        },
        mounted() {
     
            const parentCom = this.$parent;
            console.log(parentCom.title)//CaseFive父组件
            console.log(parentCom.$children[1].title) //组件b
            parentCom.title = '哈哈哈哈'
        }
    }
</script>

子组件二

<template>
    <div>
        <h1> {
     {
     title}}</h1>
    </div>
</template>

<script>
    export default {
     
        name: "childrenB",
        data (){
     
            return {
     
                title:"组件b"
            }
        }
    }
</script>

你可能感兴趣的:(vue.js)