• Vue2中10种组件通信方式和实践技巧


    以组件之间的关系,主要分为3种情况

    • 父子组件通信
    • 隔代组件通信
    • 全局通信(任意组件)

    兄弟组件通信:以父级为媒介,就变成父子组件通信。或通过全局通信。

    1,props / $emit

    这是最基础也最常用的方式。

    • 父组件通过 props 向子组件传递数据。
    • 子组件通过 $emit 调用父组件方法向父组件传参。

    需要说明2点

    1,不应该在子组件中改变 props,这会破坏单向数据流。如果有这样需求,可用 computed 做转换。

    2,$emit 触发的是组件自己的事件,下面的代码中 @updateCount 也是属于子组件的事件,只不过事件处理函数是父组件的方法(this._events 可查看组件所有的事件)。

    举例1

    
    <template>
      <Children :count="count" @updateCount="updateCount" />
    template>
    
    <script>
    import Children from "./components/Children.vue";
    export default {
      components: {
        Children,
      },
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        updateCount(num) {
          this.count = num;
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    
    <template>
      <div>
        <div>{{ count }}div>
        <button @click="handleClick">修改 countbutton>
      div>
    template>
    
    <script>
    export default {
      props: ["count"],
      methods: {
        handleClick() {
          this.$emit("updateCount", 10);
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1.1,一个需求

    子组件调用父组件方法时,需要等待父组件处理之后,再执行子组件其他逻辑。如何实现?

    方法1

    $emit() 可以传多个参数,所以也可以传递回调函数。实现如下:

    
    <template>
      <Children @updateCount="updateCount" />
    template>
    
    <script>
    import Children from "./components/Children.vue";
    // 模拟延迟
    const delay = (duration) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve()
        }, duration)
      })
    }
    export default {
      components: {
        Children,
      },
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        async updateCount(num, callback) {
          this.count = num;
          await delay(2000)
          callback && callback()
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    
    <template>
      <button @click="handleClick">修改 countbutton>
    template>
    
    <script>
    export default {
      methods: {
        handleClick() {
          this.$emit("updateCount", 10, () => {
            // 等待父组件调用该回调函数后,再执行子组件其他逻辑
            // ...
          });
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    方法2

    将父组件的方法作为参数传递(而不是事件),子组件中直接使用该属性即可。

    
    <template>
      <Children :updateCount="updateCount" />
    template>
    
    <script>
    import Children from "./components/Children.vue";
    // 模拟延迟
    const delay = (duration) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve();
        }, duration);
      });
    };
    export default {
      components: {
        Children,
      },
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        async updateCount(num) {
          this.count = num;
          await delay(2000);
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    
    <template>
      <button @click="handleClick">修改 countbutton>
    template>
    
    <script>
    export default {
      props: {
        updateCount: Function,
      },
      methods: {
        async handleClick() {
          await this.updateCount(10);
          // 等待父组件调用该回调函数后,再执行子组件其他逻辑
          // ...
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.2,v-model 和 .sync

    这2个都是语法糖,可以实现数据的双向绑定。但本质上还是 props / $emit

    v-model

    参考-自定义事件

    当在一个组件上使用 v-model 时, 默认传入名为 value 的 prop 和名为 input 的事件。

    
    <template>
      <Children v-model="count" />
      
      
    template>
    
    <script>
    import Children from "./components/Children.vue";
    export default {
      components: {
        Children,
      },
      data() {
        return {
          count: 0,
        };
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    
    <template>
      <div>
        <div>{{ value }}div>
        <button @click="handleClick">修改 countbutton>
      div>
    template>
    
    <script>
    export default {
      props: ["value"],
      methods: {
        handleClick() {
          this.$emit("input", 123);
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以使用 model 来修改 v-model 的默认设置。

    
    <script>
    export default {
      model: {
        prop: "value1",
        event: "change",
      },
      props: ["value1"],
      methods: {
        handleClick() {
          this.$emit('change', 123)
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    .sync

    参考 - .sync 修饰符

    本质如下:

    
    <template>
      
      
      <Children :count="count" @update:count="(newValue) => (count = newValue)" />
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    子组件中的处理和 v-model 类似,不做赘述。

    2,$children / $parent

    $parent$children 都可以访问组件的实例,所以可直接访问和修改属性。

    组件的子组件可能有多个,所以$children是数组,如果组件没有子组件,则 $children 是空数组。
    子组件的父组件只有一个,所以$parent直接就是组件实例,App.vue 的父组件是根组件实例$root,在往上就是 undefined

    举例:

    
    <template>
      <div>
        <Children :count="count" />
        <button @click="changeChild">修改子组件的值button>
      div>
    template>
    
    <script>
    import Children from './components/Children.vue'
    export default {
      components: {
        Children
      },
      data() {
        return {
          count: 0
        }
      },
      methods: {
        changeChild() {
          this.$children[0].name = '下雪天的夏风'
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    
    <template>
      <div>
        <div>{{ count }}div>
        <div>{{ name }}div>
        <button @click="changeParent">修改父组件的值button>
      div>
    template>
    
    <script>
    export default {
      props: ['count'],
      data() {
        return {
          name: '子组件'
        }
      },
      methods: {
        changeParent() {
          this.$parent.count = 1
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3,ref

    1,称为模板引用,用于获取子组件的实例。在 v-for 中使用时,获取的是一个 ref 数组(注意不保证和源数组顺序相同)。

    2,对普通元素使用时,获取的是DOM元素,比 document.querySelector() 更方便。

    举例:

    
    <template>
      <div>
        <Children ref="_refChild" />
        <button @click="changeChild">改变子组件的值button>
      div>
    template>
    
    <script>
    import Children from './components/Children.vue'
    export default {
      components: {
        Children
      },
      methods: {
        changeChild() {
          this.$refs._refChild.name = '下雪天的夏风'
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    
    <template>
      <div>{{ name }}div>
    template>
    
    <script>
    export default {
      data() {
        return {
          name: '子组件'
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    下面是隔代通信

    4,$attrs / $listeners

    当组件嵌套多层时,使用上面的父子组件通信的方式,略显繁琐。

    比如对 A <-B<-C 3个组件,A 是父组件。当C需要使用A组件的属性和方法时,一般处理情况:

    • 对属性来说,C 中 prop 接收 B 的属性,B 又 prop 接收 A 的属性。

    • 对方法来说,C 中 $emit() B 的方法,B 又 $emit() A 的方法。

    B 只起了中转的作用,但却需要写重复的代码,属性和方法较多时都就更难受了。

    所以出现了 $attrs / $listeners

    $attrs

    如果子组件没有在 props 中接收父组件传递给它的属性(不包含 class 和 style 属性),则这些属性会放在 $attrs

    $listeners

    包含了父组件传递过来了所有自定义事件 (不包含 .native 修饰器的)。

    所以,在中间组件上可用$attrs / $listeners 直接中转属性和方法即可,不用写多余的代码。

    举例

    
    <template>
      <BComponent :name="name" :age="age" :sex="sex" @clickA="methodA" />
    template>
    
    <script>
    import BComponent from './components/B.vue'
    export default {
      components: {
        BComponent
      },
      data() {
        return {
          name: '下雪天的夏风',
          age: 18,
          sex: 'male'
        }
      },
      methods: {
        methodA(item) {
          console.log(item)
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    
    <template>
      <CComponent v-bind="$attrs" v-on="$listeners" />
      
      
    template>
    
    <script>
    import CComponent from './C.vue'
    export default {
      inheritAttrs: false, // 下面有解释
      props: ['name'], // 则 $attrs 上只有 age 和 sex
      components: {
        CComponent
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    
    <template>
      <div>
        <div>{{ sex }} {{ age }}div>
        <button @click="handleClick">触发A组件的方法button>
      div>
    template>
    
    <script>
    export default {
      props: ['age', 'sex'],
      methods: {
        handleClick() {
          this.$emit('clickA', 123)
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    inheritAttrs

    表示是否将 $attrs 作为当前组件的根元素上的 HTML 属性,默认 true。

    为 true 时,B组件最终渲染的HTML

    <div age="18" sex="male">
      <div>male 18div>
      <button>触发A组件的方法button>
    div>
    
    • 1
    • 2
    • 3
    • 4

    false 时

    <div>
      <div>male 18div>
      <button>触发A组件的方法button>
    div>
    
    • 1
    • 2
    • 3
    • 4

    1.1 的问题的第3种解决方法

    原问题:子组件调用父组件方法时,需要等待父组件处理之后,再执行子组件其他逻辑。如何实现?

    
    <template>
      <Children @updateCount="updateCount" />
    template>
    
    <script>
    import Children from "./components/Children.vue";
    // 模拟延迟
    const delay = (duration) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve();
        }, duration);
      });
    };
    export default {
      components: {
        Children,
      },
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        async updateCount(num) {
          this.count = num;
          await delay(2000);
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    
    <template>
      <button @click="handleClick">修改 countbutton>
    template>
    
    <script>
    export default {
      props: {
        updateCount: Function,
      },
      methods: {
        async handleClick() {
          await this.$listeners.updateCount(10);
          // 等待父组件调用该回调函数后,再执行子组件其他逻辑
          // ...
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    5,provide / inject

    provide / inject需要在一起使用,无视嵌套的层级。

    祖先组件通过 provide 提供属性和方法,所有后代组件都可以通过 inject 接收注入的属性和方法。

    注意,provide 和 inject 绑定不是响应的!

    
    <script>
    export default {
      provide: {
        author: 'xxx'
      },
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    
    <template>
      <div>{{ author }}div>
    template>
    
    <script>
    export default {
      inject: ['author'],
      // 也可以像 props 一样,设置默认值
      // inject: {
      //   author: {
      //     default: 'fpp'
      //   }
      // },
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    下面是全局通信

    6,Vuex

    不多赘述,官方文档 很详细。

    7,EventBus

    参考 EventBus

    8,dispatch 和 broadcast

    参考 dispatch 和 broadcast

    9,路由

    通过 url 参数通信。

    不多赘述,官方文档 很详细。

    10,localStorage / SessionStorage

    不多赘述。


    以上。

  • 相关阅读:
    【Linux系统管理】10 Shell 编程进阶篇
    HTTP 协议的基本格式(部分)
    用::before伪元素,在文字前面画一个圆形
    ATmega328P加硬件看门狗MAX824L看门狗
    【布局优化】基于帝国企鹅算法求解潮流计算的电力系统总线优化问题附matlab代码
    Windows与网络基础-15-本地安全策略
    微信小程序云开发实战
    高考有哪些东西没有考,但是却对人生发展至关重要的东西
    PerformanceOne一站式性能测试平台
    球迷 如何在Linux纯命令行玩转谷歌浏览器,边看欧洲杯,边看足球宝贝
  • 原文地址:https://blog.csdn.net/qq_40147756/article/details/133051328