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

news/2024/7/10 1:36:34 标签: 前端, javascript, vue, 组件通信

目录

  • 1,props / $emit
    • 1.1,一个需求
      • 方法1
      • 方法2
    • 1.2,v-model 和 .sync
    • v-model
    • .sync
  • 2,$children / $parent
  • 3,ref
  • 4,$attrs / $listeners
    • $attrs
    • $listeners
    • inheritAttrs
    • 1.1 的问题的第3种解决方法
  • 5,provide / inject
  • 6,Vuex
  • 7,EventBus
  • 8,dispatch 和 broadcast
  • 9,路由
  • 10,localStorage / SessionStorage

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

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

1,props / $emit

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

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

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

举例1

<!-- 父组件 -->
<template>
  <Children :count="count" @updateCount="updateCount" />
</template>

<script>javascript">
import Children from "./components/Children.vue";
export default {
  components: {
    Children,
  },
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    updateCount(num) {
      this.count = num;
    },
  },
};
</script>
<!-- 子组件 -->
<template>
  <div>
    <div>{{ count }}</div>
    <button @click="handleClick">修改 count</button>
  </div>
</template>

<script>javascript">
export default {
  props: ["count"],
  methods: {
    handleClick() {
      this.$emit("updateCount", 10);
    },
  },
};
</script>

1.1,一个需求

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

方法1

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

<!-- 父组件 -->
<template>
  <Children @updateCount="updateCount" />
</template>

<script>javascript">
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>
<!-- 子组件 -->
<template>
  <button @click="handleClick">修改 count</button>
</template>

<script>javascript">
export default {
  methods: {
    handleClick() {
      this.$emit("updateCount", 10, () => {
        // 等待父组件调用该回调函数后,再执行子组件其他逻辑
        // ...
      });
    },
  },
};
</script>

方法2

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

<!-- 父组件 -->
<template>
  <Children :updateCount="updateCount" />
</template>

<script>javascript">
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>
<!-- 子组件 -->
<template>
  <button @click="handleClick">修改 count</button>
</template>

<script>javascript">
export default {
  props: {
    updateCount: Function,
  },
  methods: {
    async handleClick() {
      await this.updateCount(10);
      // 等待父组件调用该回调函数后,再执行子组件其他逻辑
      // ...
    },
  },
};
</script>

1.2,v-model 和 .sync

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

v-model

参考-自定义事件

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

<!-- 父组件 -->
<template>
  <Children v-model="count" />
  <!-- 二者等效 -->
  <!-- <Children :value="count" @input="(newValue) => (count = newValue)" /> -->
</template>

<script>javascript">
import Children from "./components/Children.vue";
export default {
  components: {
    Children,
  },
  data() {
    return {
      count: 0,
    };
  },
};
</script>
<!-- 子组件 -->
<template>
  <div>
    <div>{{ value }}</div>
    <button @click="handleClick">修改 count</button>
  </div>
</template>

<script>javascript">
export default {
  props: ["value"],
  methods: {
    handleClick() {
      this.$emit("input", 123);
    },
  },
};
</script>

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

<!-- 子组件 -->
<script>javascript">
export default {
  model: {
    prop: "value1",
    event: "change",
  },
  props: ["value1"],
  methods: {
    handleClick() {
      this.$emit('change', 123)
    },
  },
};
</script>

.sync

参考 - .sync 修饰符

本质如下:

<!-- 父组件 -->
<template>
  <!-- <Children :count.sync="count" /> -->
  <!-- 二者相等 -->
  <Children :count="count" @update:count="(newValue) => (count = newValue)" />
</template>

子组件中的处理和 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>javascript">
import Children from './components/Children.vue'
export default {
  components: {
    Children
  },
  data() {
    return {
      count: 0
    }
  },
  methods: {
    changeChild() {
      this.$children[0].name = '下雪天的夏风'
    }
  }
}
</script>
<!-- 子组件 -->
<template>
  <div>
    <div>{{ count }}</div>
    <div>{{ name }}</div>
    <button @click="changeParent">修改父组件的值</button>
  </div>
</template>

<script>javascript">
export default {
  props: ['count'],
  data() {
    return {
      name: '子组件'
    }
  },
  methods: {
    changeParent() {
      this.$parent.count = 1
    }
  }
}
</script>

3,ref

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

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

举例:

<!-- 父组件 -->
<template>
  <div>
    <Children ref="_refChild" />
    <button @click="changeChild">改变子组件的值</button>
  </div>
</template>

<script>javascript">
import Children from './components/Children.vue'
export default {
  components: {
    Children
  },
  methods: {
    changeChild() {
      this.$refs._refChild.name = '下雪天的夏风'
    }
  }
}
</script>
<!-- 子组件 -->
<template>
  <div>{{ name }}</div>
</template>

<script>javascript">
export default {
  data() {
    return {
      name: '子组件'
    }
  }
}
</script>

下面是隔代通信

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 直接中转属性和方法即可,不用写多余的代码。

举例

<!-- A组件 -->
<template>
  <BComponent :name="name" :age="age" :sex="sex" @clickA="methodA" />
</template>

<script>javascript">
import BComponent from './components/B.vue'
export default {
  components: {
    BComponent
  },
  data() {
    return {
      name: '下雪天的夏风',
      age: 18,
      sex: 'male'
    }
  },
  methods: {
    methodA(item) {
      console.log(item)
    }
  }
}
</script>
<!-- B组件 -->
<template>
  <CComponent v-bind="$attrs" v-on="$listeners" />
  <!-- 或只传递需要的属性和方法 -->
  <!-- <CComponent v-bind="{ age: $attrs.age }" v-on="{ clickA: $listeners.clickA }" /> -->
</template>

<script>javascript">
import CComponent from './C.vue'
export default {
  inheritAttrs: false, // 下面有解释
  props: ['name'], // 则 $attrs 上只有 age 和 sex
  components: {
    CComponent
  }
}
</script>
<!-- C组件 -->
<template>
  <div>
    <div>{{ sex }} {{ age }}</div>
    <button @click="handleClick">触发A组件的方法</button>
  </div>
</template>

<script>javascript">
export default {
  props: ['age', 'sex'],
  methods: {
    handleClick() {
      this.$emit('clickA', 123)
    }
  }
}
</script>

inheritAttrs

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

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

<div age="18" sex="male">
  <div>male 18</div>
  <button>触发A组件的方法</button>
</div>

false 时

<div>
  <div>male 18</div>
  <button>触发A组件的方法</button>
</div>

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

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

<!-- 父组件 -->
<template>
  <Children @updateCount="updateCount" />
</template>

<script>javascript">
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>
<!-- 子组件 -->
<template>
  <button @click="handleClick">修改 count</button>
</template>

<script>javascript">
export default {
  props: {
    updateCount: Function,
  },
  methods: {
    async handleClick() {
      await this.$listeners.updateCount(10);
      // 等待父组件调用该回调函数后,再执行子组件其他逻辑
      // ...
    },
  },
};
</script>

5,provide / inject

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

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

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

<!-- 祖先组件 -->
<script>javascript">
export default {
  provide: {
    author: 'xxx'
  },
}
</script>
<!-- 某后代组件 -->
<template>
  <div>{{ author }}</div>
</template>

<script>javascript">
export default {
  inject: ['author'],
  // 也可以像 props 一样,设置默认值
  // inject: {
  //   author: {
  //     default: 'fpp'
  //   }
  // },
}
</script>

下面是全局通信

6,Vuex

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

7,EventBus

事件总线

我们需要一个插件满足以下需求,来实现不受层级约束的组件通信

  • 可以监听 / 取消监听某个事件。
  • 可以触发监听的事件,还可以传参,并且自动通知监听者。

实现:

// eventBus.js
const listeners = {}

export default {
  // 监听
  $on(event, handler) {
    if (!listeners[event]) {
      listeners[event] = new Set()
    }
    // 同一个事件,可以绑定多个处理函数
    listeners[event].add(handler)
  },
  // 取消监听
  $off(event, handler) {
    if (listeners[event]) {
      listeners[event].delete(handler)
    }
  },
  // 触发监听,可传递参数
  $emit(event, ...args) {
    if (listeners[event]) {
      for (const handler of listeners[event]) {
        handler(...args)
      }
    }
  }
}

举例1

// test.js
import eventBus from './eventBus.js'
eventBus.$on('event1', function () {
  console.log('1')
})

eventBus.$on('event1', function () {
  console.log('2')
})

setTimeout(() => {
  eventBus.$emit('event1')
}, 1000)

而 Vue 实例本身就实现了上面这3个方法,所以可直接这样实现

// eventBus.js
import Vue from 'vue'
export default new Vue({})

或是直接绑定到原型上来使用。

// main.js
import Vue from 'vue'
import App from './App.vue'

const vm = new Vue({
  render: (h) => h(App)
})

Vue.prototype.$bus = vm
vm.$mount('#app')

举例2:下面2个组件层级关系不做限制。

<!-- 组件 1 -->
<template>
  <button @click="handleClick">触发 event1 事件</button>
</template>

<script>javascript">
export default {
  methods: {
    handleClick() {
      this.$bus.$emit('event1', 123)
    }
  }
}
</script>
<!-- 组件 2 -->
<script>javascript">
export default {
  methods: {
    methodC1(item) {
      console.log(item)
    },
    methodC2(item) {
      console.log(item)
    }
  },
  created() {
    this.$bus.$on('event1', this.methodC1)
    this.$bus.$on('event1', this.methodC2)
  }
}
</script>

注意,这个思路是将事件放到一个对象中管理(监听和触发)。如果直接在不同组件中使用 this.$on()this.$emit() 是无效的,因为 this 指向不同的组件实例,$emit() 触发的是实例自己通过 $on() 监听的事件。

8,dispatch 和 broadcast

因为这篇文章太长了,无法发表,所以 看这篇文章

9,路由

通过 url 参数通信。

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

10,localStorage / SessionStorage

不多赘述。


以上。


http://www.niftyadmin.cn/n/5040586.html

相关文章

TypeScript逆变 :条件、推断和泛型的应用

TypeScript逆变 &#xff1a;条件、推断和泛型的应用 1 一个类型问题 有一个名为 test 的函数&#xff0c;它接受两个参数。第一个参数是函数 fn&#xff0c;第二个参数 options 受到 fn 参数的限制。乍一看&#xff0c;这个问题貌似并不复杂&#xff0c;不是吗&#xff1f;糊…

竞赛选题 基于深度学习的动物识别 - 卷积神经网络 机器视觉 图像识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

nvm下载安装教程

前言 nvm 官网地址&#xff1a;https://nvm.uihtm.com 一、nvm 下载 进入 nvm github 地址&#xff0c;下载最新版本&#xff1a;https://github.com/coreybutler/nvm-windows/releases 点击选择当前最新版本。 滑动到底部&#xff0c;点击 nvm-setup.exe 下载安装文件。 接…

Idea操作Git合并另一个分支的部分提交

现有master、dev两个分支&#xff0c;master有提交1、2、3、4、5、6、7&#xff0c;dev是从master提交的3拉出来的分支&#xff08;Reset Current Branch to Here…&#xff0c;Mixed模式&#xff09;&#xff0c;有提交1、2、3&#xff0c;现在的需求是dev分支只需要合并maste…

力扣刷题-数组-另一种双指针-有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;平方后&#xff0c;数组变为 [1…

YOLOv5,YOLOv8添加ASFF(自适应空间特征融合)

ASFF&#xff1a;Adaptively Spatial Feature Fusion (自适应空间特征融合) 论文来源&#xff1a;Learning Spatial Fusion for Single-Shot Object Detection 代码地址&#xff1a;ASFF 1.背景 不同特征尺度之间的不一致性是基于特征金字塔的单阶段检测器的主要缺陷。 本文…

云可观测性:提升云环境中应用程序可靠性

随着云计算的兴起和广泛应用&#xff0c;越来越多的企业将其应用程序和服务迁移到云环境中。在这个高度动态的环境中&#xff0c;确保应用程序的可靠性和可管理性成为了一个迫切的需求。云可观测性作为一种解决方案&#xff0c;针对这一需求提供了有效的方法和工具。本文将介绍…

【UE 粒子练习】07——创建动画拖尾类型粒子

效果 步骤 1. 将动画序列“Idle_ModifyBones”添加到场景中 2. 新建一个材质&#xff0c;命名为“Mat_AnimTrails” 材质混合模式设置为半透明&#xff0c;着色模型设置为无光照&#xff0c;设置材质为双面 材质节点如下 3. 新建一个粒子系统&#xff0c;命名为“P_AnimTrail”…