Toggle navigation
首页
产品中心
全新RDIF.vNext低代码框架
镇店
.NET C/S开发框架
.NET Web敏捷开发框架
.NET 快速开发框架(全新EasyUI版本)
.NET 代码生成器
.NET WebAPI服务端开发框架
客户案例
付款方式
国思学堂
技术文章
新闻资讯
下载
关于
首页
技术文章
前端技术
正文
原创
2023-04-27
浏览 (
6897
)
Vue2.x 组件通信方式详解,这篇讲全了
## 前言 vue是数据驱动视图更新的框架, 我们平时开发,都会把页面不同模块拆分成一个一个vue组件, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢? 首先我们需要知道在vue中组件之间存在什么样的关系, 才更容易理解他们的通信方式。 一般我们分为如下关系: > 父子组件之间通信 > 非父子组件之间通信(兄弟组件、隔代关系组件、跨层级组件等) ![vue如何进行组件间的通信](http://doc.rdiframework.net/rdiblog/20230426105427.png) Vue2.x 组件通信共有12种 1. props 2. $emit / v-on 3. .sync 4. v-model 5. ref 6. $children / $parent 7. $attrs / $listeners 8. provide / inject 9. EventBus 10. Vuex 11. $root 12. slot 13. 路由传参 14. observable 父子组件通信可以用: - props - $emit / v-on - $attrs / $listeners - ref - .sync - v-model - $children / $parent 兄弟组件通信可以用: - EventBus - Vuex - $parent 跨层级组件通信可以用: - provide/inject - EventBus - Vuex - $attrs / $listeners - $root ## Vue2.x 组件通信使用写法 下面把每一种组件通信方式的写法一一列出 ### 1. props 父组件向子组件传送数据,这应该是最常用的方式了 子组件接收到数据之后,**不能直接修改**父组件的数据。会报错,所以当父组件重新渲染时,数据会被覆盖。如果子组件内要修改的话推荐使用 computed 格式: ```js // 数组:不建议使用 props:[] // 对象 props:{ inpVal:{ type:Number, //传入值限定类型 // type 值可为String,Number,Boolean,Array,Object,Date,Function,Symbol // type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认 required: true, //是否必传 default:200, //默认值,对象或数组默认值必须从一个工厂函数获取如 default:()=>[] validator:(value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } ``` 事例: ```js // Parent.vue 传送 <template> <child :msg="msg"></child> </template> // Child.vue 接收 export default { // 写法一 用数组接收 props:['msg'], // 写法二 用对象接收,可以限定接收的数据类型、设置默认值、验证等 props:{ msg:{ type:String, default:'这是默认数据' } }, mounted(){ console.log(this.msg) }, } ``` **注意:** prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。 - 第一,不应该在一个子组件内部改变 prop,这样会破坏单向的数据绑定,导致数据流难以理解。如果有这样的需要,可以通过 data 属性接收或使用 computed 属性进行转换。 - 第二,如果 props 传递的是引用类型(对象或者数组),在子组件中改变这个对象或数组,父组件的状态会也会做相应的更新,利用这一点就能够实现父子组件数据的“双向绑定”,虽然这样实现能够节省代码,但会牺牲数据流向的简洁性,令人难以理解,最好不要这样去做。 - 想要实现父子组件的数据“双向绑定”,可以使用 v-model 或 .sync。 ![props与$emit](http://doc.rdiframework.net/rdiblog/20230426104548.png) ### 2. $emit / v-on 子传父的方法,子组件通过派发事件的方式给父组件数据,或者触发父组件更新等操作 ```js // Child.vue 派发 export default { data(){ return { msg: "这是发给父组件的信息" } }, methods: { handleClick(){ this.$emit("sendMsg",this.msg) } }, } // Parent.vue 响应 <template> <child v-on:sendMsg="getChildMsg"></child> // 或 简写 <child @sendMsg="getChildMsg"></child> </template> export default { methods:{ getChildMsg(msg){ console.log(msg) // 这是父组件接收到的消息 } } } ``` ### 3. v-model 和 .sync 类似,可以实现将父组件传给子组件的数据为双向绑定,子组件通过 $emit 修改父组件的数据 ```js // Parent.vue <template> <child v-model="value"></child> </template> <script> export default { data(){ return { value:1 } } } // Child.vue <template> <input :value="value" @input="handlerChange"> </template> export default { props:["value"], // 可以修改事件名,默认为 input model:{ // prop:'value', // 上面传的是value这里可以不写,如果属性名不是value就要写 event:"updateValue" }, methods:{ handlerChange(e){ this.$emit("input", e.target.value) // 如果有上面的重命名就是这样 this.$emit("updateValue", e.target.value) } } } </script> ``` ### 4. ref ref 如果在普通的DOM元素上,引用指向的就是该DOM元素; 如果在子组件上,引用的指向就是子组件实例,然后父组件就可以通过 ref 主动获取子组件的属性或者调用子组件的方法 ```js // Child.vue export default { data(){ return { name:"RDIF" } }, methods:{ someMethod(msg){ console.log(msg) } } } // Parent.vue <template> <child ref="child"></child> </template> <script> export default { mounted(){ const child = this.$refs.child console.log(child.name) // RDIF child.someMethod("调用了子组件的方法") } } </script> ``` ### 5. .sync 可以帮我们实现父组件向子组件传递的数据 的双向绑定,所以子组件接收到数据后**可以直接修改**,并且会同时修改父组件的数据 ```js // Parent.vue <template> <child :page.sync="page"></child> </template> <script> export default { data(){ return { page:1 } } } // Child.vue export default { props:["page"], computed(){ // 当我们在子组件里修改 currentPage 时,父组件的 page 也会随之改变 currentPage { get(){ return this.page }, set(newVal){ this.$emit("update:page", newVal) } } } } </script> ``` ### 6. $attrs / $listeners 多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,比如父组件向孙子组件传递数据时。 `$attrs`:包含父作用域里除 class 和 style 除外的非 props **属性集合**。通过 this.$attrs 获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过 v-bind="$attrs"。 > 场景:如果父传子有很多值,那么在子组件需要定义多个 props > > 解决:$attrs获取子传父中未在 props 定义的值 ```js // 父组件 <home title="这是标题" width="80" height="80" imgUrl="imgUrl"/> // 子组件 mounted() { console.log(this.$attrs) //{title: "这是标题", width: "80", height: "80", imgUrl: "imgUrl"} }, ``` 相对应的如果子组件定义了 props,打印的值就是剔除定义的属性。 ```js props: { width: { type: String, default: '' } }, mounted() { console.log(this.$attrs) //{title: "这是标题", height: "80", imgUrl: "imgUrl"} }, ``` `$listeners`:包含父作用域里 .native 除外的监听**事件集合**。如果还要继续传给子组件内部的其他组件,就可以通过 v-on="$linteners"。 > 场景:子组件需要调用父组件的方法 > > 解决:父组件的方法可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用 ```js // 父组件 <home @change="change"/> // 子组件 mounted() { console.log(this.$listeners) //即可拿到 change 事件 } ``` 如果是孙组件要访问父组件的属性和调用方法,直接一级一级传下去就可以。 ### 7. $children / $parent `$children`:获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,可以直接拿到子组件中所有数据和方法等。 `$parent`:获取到一个父节点的 VueComponent 对象,同样包含父节点中所有数据和方法等 ```js // Parent.vue export default{ mounted(){ this.$children[0].someMethod() // 调用第一个子组件的方法 this.$children[0].name // 获取第一个子组件中的属性 } } // Child.vue export default{ mounted(){ this.$parent.someMethod() // 调用父组件的方法 this.$parent.name // 获取父组件中的属性 } } ``` `$children`和`$parent` 并不保证顺序,也不是响应式的,只能拿到一级父组件和子组件。 ### 8. provide / inject provide / inject 为依赖注入,主要为高阶插件/组件库提供用例。说是不推荐直接用于应用程序代码中,但是在一些插件或组件库里却是被常用,所以我觉得用也没啥,还挺好用的。 `provide`:可以让我们指定想要提供给后代组件的数据或方法 `inject`:在任何后代组件中接收想要添加在这个组件上的数据或方法,不管组件嵌套多深都可以直接拿来用 要注意的是 provide 和 inject 传递的数据不是响应式的,也就是说用 inject 接收来数据后,provide 里的数据改变了,后代组件中的数据不会改变,除非传入的就是一个可监听的对象 所以建议还是传递一些常量或者方法 ```js // 父组件 export default{ // 方法一 不能获取 this.xxx,只能传写死的 provide:{ name:"RDIF", }, // 方法二 可以获取 this.xxx provide(){ return { name:"RDIF", msg: this.msg // data 中的属性 someMethod:this.someMethod // methods 中的方法 } }, methods:{ someMethod(){ console.log("这是注入的方法") } } } // 后代组件 export default{ inject:["name","msg","someMethod"], mounted(){ console.log(this.msg) // 这里拿到的属性不是响应式的,如果需要拿到最新的,可以在下面的方法中返回 this.someMethod() } } ``` ### 9. EventBus EventBus 是中央事件总线,不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作。 - 声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上; - 类似于 Vuex。但这种方式只适用于极小的项目; - 原理就是利用on和emit 并实例化一个全局 vue 实现数据共享; - 可以实现平级,嵌套组件传值,但是对应的事件名eventTarget必须是全局唯一的; 定义方式有三种: ```js // 方法一 // 抽离成一个单独的 js 文件 Bus.js ,然后在需要的地方引入 // Bus.js import Vue from "vue" export default new Vue() // 方法二 直接挂载到全局 // main.js import Vue from "vue" Vue.prototype.$bus = new Vue() // 方法三 注入到 Vue 根对象上 // main.js import Vue from "vue" new Vue({ el:"#app", data:{ Bus: new Vue() } }) ``` 使用如下,以方法一按需引入为例: ```js // 在需要向外部发送自定义事件的组件内 <template> <button @click="handlerClick">按钮</button> </template> import Bus from "./Bus.js" export default{ methods:{ handlerClick(){ // 自定义事件名 sendMsg Bus.$emit("sendMsg", "这是要向外部发送的数据") } } } // 在需要接收外部事件的组件内 import Bus from "./Bus.js" export default{ mounted(){ // 监听事件的触发 Bus.$on("sendMsg", data => { console.log("这是接收到的数据:", data) }) }, beforeDestroy(){ // 取消监听 Bus.$off("sendMsg") } } ``` 以方法二直接挂载在全局: ```js // 在 main.js Vue.prototype.$eventBus=new Vue() // 传值组件 this.$eventBus.$emit('eventTarget','这是eventTarget传过来的值') // 接收组件 this.$eventBus.$on("eventTarget",v=>{ console.log('eventTarget',v);//这是eventTarget传过来的值 }) ``` ### 10. Vuex - Vuex 是状态管理器,集中式存储管理所有组件的状态。 - 适合数据共享多的项目里面,因为如果只是简单的通讯,使用起来会比较重。 ```js state:定义存贮数据的仓库 ,可通过this.$store.state 或mapState访问。 getter:获取 store 值,可认为是 store 的计算属性,可通过this.$store.getter 或 mapGetters访问。 mutation:同步改变 store 值,为什么会设计成同步,因为mutation是直接改变 store 值,vue 对操作进行了记录,如果是异步无法追踪改变,可通过mapMutations调用。 action:异步调用函数执行mutation,进而改变 store 值,可通过 this.$dispatch或mapActions访问。 modules:模块,如果状态过多,可以拆分成模块,最后在入口通过...解构引入。 ``` 这一块内容过长,如果基础不熟的话可以看这个[Vuex](https://v3.vuex.vuejs.org/zh/),然后大致用法如下: 比如创建这样的文件结构 ![Vuex](http://doc.rdiframework.net/rdiblog/20230426091218.png) index.js 里内容如下 ```js import Vue from 'vue' import Vuex from 'vuex' import getters from './getters' import actions from './actions' import mutations from './mutations' import state from './state' import user from './modules/user' Vue.use(Vuex) const store = new Vuex.Store({ modules: { user }, getters, actions, mutations, state }) export default store ``` 然后在 main.js 引入 ```js import Vue from "vue" import store from "./store" new Vue({ el:"#app", store, render: h => h(App) }) ``` 然后在需要的使用组件里 ```js import { mapGetters, mapMutations } from "vuex" export default{ computed:{ // 方式一 然后通过 this.属性名就可以用了 ...mapGetters(["引入getters.js里属性1","属性2"]) // 方式二 ...mapGetters("user", ["user模块里的属性1","属性2"]) }, methods:{ // 方式一 然后通过 this.属性名就可以用了 ...mapMutations(["引入mutations.js里的方法1","方法2"]) // 方式二 ...mapMutations("user",["引入user模块里的方法1","方法2"]) } } // 或者也可以这样获取 this.$store.state.xxx this.$store.state.user.xxx ``` ![组件通信](http://doc.rdiframework.net/rdiblog/20230426104629.png) ### 11. $root `$root` 可以拿到 App.vue 里的数据和方法 ```js // 父组件 mounted(){ console.log(this.$root) //获取根实例,最后所有组件都是挂载到根实例上 console.log(this.$root.$children[0]) //获取根实例的一级子组件 console.log(this.$root.$children[0].$children[0]) //获取根实例的二级子组件 } ``` ### 12. slot 将父组件的 template 传入子组件 分类: A.匿名插槽(也叫默认插槽): 没有命名,有且只有一个; ```js // 父组件 <todo-list> <template v-slot:default> 任意内容 <p>我是匿名插槽 </p> </template> </todo-list> // 子组件 <slot>我是默认值</slot> //v-slot:default写上感觉和具名写法比较统一,容易理解,也可以不用写 ``` B.具名插槽: 相对匿名插槽组件slot标签带name命名的; ```js // 父组件 <todo-list> <template v-slot:todo> 任意内容 <p>我是匿名插槽 </p> </template> </todo-list> //子组件 <slot name="todo">我是默认值</slot> ``` C.作用域插槽: 子组件内数据可以被父页面拿到(解决了数据只能从父页面传递给子组件) ```vue // 父组件 <todo-list> <template v-slot:todo="slotProps" > {{slotProps.user.firstName}} </template> </todo-list> //slotProps 可以随意命名 //slotProps 接取的是子组件标签slot上属性数据的集合所有v-bind:user="user" // 子组件 <slot name="todo" :user="user" :test="test"> {{ user.lastName }} </slot> data() { return { user:{ lastName:"Zhang", firstName:"yue" }, test:[1,2,3,4] } }, // {{ user.lastName }}是默认数据 v-slot:todo 当父页面没有(="slotProps") ``` ### 13、路由传参 1.方案一 ```js // 路由定义 { path: '/describe/:id', name: 'Describe', component: Describe } // 页面传参 this.$router.push({ path: `/describe/${id}`, }) // 页面获取 this.$route.params.id ``` 2.方案二 ```js // 路由定义 { path: '/describe', name: 'Describe', component: Describe } // 页面传参 this.$router.push({ name: 'Describe', params: { id: id } }) // 页面获取 this.$route.params.id ``` 3.方案三 ```js // 路由定义 { path: '/describe', name: 'Describe', component: Describe } // 页面传参 this.$router.push({ path: '/describe', query: { id: id `} ) // 页面获取 this.$route.query.id ``` 4.三种方案对比 > 方案二参数不会拼接在路由后面,页面刷新参数会丢失 > 方案一和三参数拼接在后面,丑,而且暴露了信息 ### 14、`Vue.observable` 用法:让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象; 返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新; 也可以作为最小化的跨组件状态存储器,用于简单的场景。 通讯原理实质上是利用Vue.observable实现一个简易的 vuex ```js // 文件路径 - /store/store.js import Vue from 'vue' export const store = Vue.observable({ count: 0 }) export const mutations = { setCount (count) { store.count = count } } //使用 <template> <div> <label for="bookNum">数 量</label> <button @click="setCount(count+1)">+</button> <span>{{count}}</span> <button @click="setCount(count-1)">-</button> </div> </template> <script> import { store, mutations } from '../store/store' // Vue2.6新增API Observable export default { name: 'Add', computed: { count () { return store.count } }, methods: { setCount: mutations.setCount } } </script> ``` ## 参考资料 vue.js: [https://v2.cn.vuejs.org/](https://v2.cn.vuejs.org/) vuex是什么:[https://v3.vuex.vuejs.org/zh/](https://v3.vuex.vuejs.org/zh/) 工作中要使用Git,看这篇文章就够了:[http://www.guosisoft.com/article/detail/410508049313861](http://www.guosisoft.com/article/detail/410508049313861) 企业数字化转型如何做?看过来:[http://www.guosisoft.com/article/detail/408745545576517](http://www.guosisoft.com/article/detail/408745545576517) ## 结语 如果本文对你有一点点帮助,点个赞支持一下吧,你的每一个【赞】都是我创作的最大动力 ^_^ 更多技术文章请往:[http://www.guosisoft.com/article](http://www.guosisoft.com/article),大家一起共同交流和进步呀 ----- 一路走来数个年头,感谢RDIF框架的支持者与使用者,大家可以通过下面的地址了解详情。 官方网站:[http://www.guosisoft.com/](http://www.guosisoft.com/) [http://www.rdiframework.net/](http://www.rdiframework.net/) 特别说明,框架相关的技术文章请以官方网站为准,欢迎大家收藏! **国思RDIF低代码快速开发框架**由海南国思软件科技有限公司专业团队长期打造、一直在更新、一直在升级,请放心使用! 欢迎关注国思RDI低代码快速开发框架官方公众微信(微信号:guosisoft),及时了解最新动态。 使用微信扫描二维码立即关注 ![微信扫描二维码](http://doc.rdiframework.net/weixin.png )
正文到此结束
本文标签:
挨踢业界
RDIF.NET
Web前端
版权声明:
本站原创文章,由
guosisoft.com
发布,遵循
CC 4.0 by-sa
版权协议,转载请附上原文出处链接和本声明。
上一篇
干货|工作中要使用Git,看这篇文章就够了
下一篇
国思RDIF.vNext全新低代码快速开发框架平台发布
热门推荐
{{article.title}}
热门指数:
浏览({{article.lookCount + 5000}})
相关文章
{{article.title}}
该篇文章的评论功能暂时被站长关闭
说给你听
本文目录
文章标签
RDIF.NET
其他
微信开发
.NET
消息交互
.NetCore
项目管理
常用工具
工作流
Web前端
数据库
挨踢业界
随机文章
RDIFramework.NET V3.2->Web版本新增新的角色授权管理界面效率更高、更规范
RDIFramework.NET Web版报表管理-助力企业高效智能图表
信息系统项目管理系列之七:项目时间管理
RDIFramework.NET敏捷开发框架Web新增邮件中心实现便捷式的邮件收发
微信公众号开发C#系列-12、微信前端开发利器:WeUI
.NET编译项目时出现《此实现不是 Windows 平台 FIPS 验证的加密算法的一部分》处理方法
RDIFramework.NET ━ .NET快速信息化系统开发框架 V3.2版本 正式发布
.NET快速信息化系统开发框架 V3.2->Web版本工作流部分业务处理界面与查看界面全新展示
[推荐]ORACLE SQL:经典查询练手(不懂装懂,永世饭桶!)
[特别公告]RDIFramework.NET微信公众号迁移通知
通过SignalR技术整合即时通讯(IM)在.NET敏捷开发框架中落地
.NET Core部署到linux(CentOS)最全解决方案,进阶篇(Supervisor+Nginx)
ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)
信息系统项目管理系列之六:项目范围管理
RDIFramework.NET V3.3 WinForm版新增订单管理主从表事例
.NET分离exe和dll在不同的目录让你的程序更整洁
RDIFramework.NET V3.3 Web版新增报表管理功能模块-重量级实用功能
RDIFramework.NET开发实例之产品管理(WebForm版)
团队项目开发"编码规范"之二:代码外观
微信公众号开发C#系列-3、搭建微信本地调试环境-借助花生壳实现内网穿透
网站信息
文章总数:599 篇
标签总数:8 个
分类总数:8 个
留言数量:1385 条
在线人数:
89
人
运行天数:1321天
最后更新:2023-05-18
QQ:406590790
13005007127