Vue组件间传值的实现解析
目录
- 1. 父组件向子组件传值
- 1.1 描述
- 1.2 props接收数据
- 2. 子组件向父组件传值
- 3. 兄弟组件间传值
- 4. 事件总线
- 5. Ref
- 6. root/parent/children
- 7. provide/inject
- 8. $attrs/$listeners
- 9. v-model绑定到自定义组件中
- 10. sync同步动态属性数据
- 11. localStorage / sessionStorage
1. 父组件向子组件传值
1.1 描述
父组件以属性的形式绑定值到子组件身上。
子组件通过使用属性 props 接收(props 是单向数据流【只读属性】:当父组件的属性变化时,将传导给子组件,但是反过来不会,即子组件中不可以修改父组件的值,应该通过给子组件传数据的父组件修改)
1.2 props接收数据
语法:
props: 数组 | 对象
数组方式接收:
此方式,一般用于你自己定义组件给自己所用,是一种简写方式
数组中的元素就是你自定义的属性名称(注意这里是自定义的属性名称,而不是父组件数据源中的数据名称)
示例:
子组件(child.vue):
<template> <div> <div class="title">child</div> <br /> <div>{{ title }}</div> </div> </template> <script> export default { // 在vue中接受父组件通过自定义属性传过来的数据,通过配置props来接受 props: ["title"], // 在方法中得到数据 created() { console.log(this.title); }, }; </script> <style lang="scss" scoped> .title { color: red; } </style>
父组件(App.vue):
<template> <div> <h3 class="title">App组件</h3> <hr /> <!-- vue中父组件中的数据可以通过自定义属性的方式向子组件传递 --> <!-- 第一个 title 是我们自定义的属性名称;第二个 title 是当前组件 data 中的数据名称 --> <!-- 两个名称可以不一样,但是一般情况下,我们写成一样的 --> <child :title="title" /> </div> </template> <script> import child from "./components/child.vue"; export default { components: { child, }, data() { return { title: "我是一个显示内容", }; }, }; </script> <style lang="scss" scoped></style>
对象方式接收:
一般用于,封装的组件提供给别人使用,它可以限制属性的类型和默认值
示例:
子组件(child.vue):
<template> <div> <div class="title">child组件</div> <br /> <div>{{ title }}--{{ age }}</div> </div> </template> <script> export default { props: { // key名称就是你自定义属性名称 // 类型首字母大写 // attrtitle: String title: { // 接收的数据类型 type: String, // 直接给值 [基础类型] | 回调函数 [基础类型和引用类型都可以,引用类型设置默认值必须是此方案] default: "我是一个字符串", }, // 自定义方法验证接收的数据 age: { type: Number, default: () => 10, // 验证操作 validator: (value) => { if (value > 90) { // 返回一个警告 return false; } return true; } }, }, }; </script> <style lang="scss" scoped> .title { color: red; } </style>
父组件(App.vue):
<template> <div> <h3 class="title">App组件</h3> <hr /> <child :title="title" :age="100" /> <child /> <child :title="'100'" /> </div> </template> <script> import child from "./components/child.vue"; export default { components: { child, }, data() { return { title: "我是一个显示内容", }; }, }; </script> <style lang="scss" scoped></style>
2. 子组件向父组件传值
上文提到props 是单向数据流,子组件中不可以修改父组件的值,应该通过给子组件传数据的父组件修改,这样设计是为了防止子和父在修改数据时,造成的数据混乱。
在 Vue 中,如果子组件修改了父组件传过来的数据,控制台会报一个警告,但在 React 中会直接报错。
那么子组件要怎么向父组件传值呢?
有两种方式:
- 子组件用
$emit()
定义自定义事件,$emit()
第一个参数为 自定义的事件名称 第二个参数为需要传递的数据;父组件用v-on(@)
绑定子组件定义的自定义事件名,监听子组件的事件,实现通信 - 父组件直接把修改数据的方法以属性的方式传给子组件(通过形参方式传递)
方法2实现:
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <!-- 把修改age的方法以属性的方式传给子组件 --> <child :title="title" :age="age" :setAge="setAge" /> </div> </template> <script> import child from "./components/child.vue"; export default { components: { child, }, data() { return { title: "我是一个显示内容", age: 80, }; }, methods: { setAge(n = 1) { this.age += n; }, }, }; </script> <style lang="scss" scoped></style>
子组件:
<template> <div> <div class="title">child组件</div> <br /> <div>{{ title }}--{{ age }}</div> <br /> <button @click="addAge">+++++</button> </div> </template> <script> export default { props: { title: { // 接收的数据类型 type: String, // 直接给值 [基础类型] | 回调函数 [基础类型和引用类型都可以,引用类型设置默认值必须是此方案] default: "我是一个字符串", }, // 自定义方法验证接收的数据 age: { type: Number, default: () => 10, // 验证操作 validator: (value) => { if (value > 90) { // 返回一个警告 return false; } return true; }, }, setAge: Function, }, methods: { addAge() { this.setAge(2); }, }, }; </script> <style lang="scss" scoped> .title { color: red; } </style>
方法1实现:
子组件:
<template> <div> <div class="title">child组件</div> <br /> <div>{{ title }}--{{ age }}</div> <br /> <button @click="addAge">+++++</button> </div> </template> <script> export default { props: { title: { // 接收的数据类型 type: String, // 直接给值 [基础类型] | 回调函数 [基础类型和引用类型都可以,引用类型设置默认值必须是此方案] default: "我是一个字符串", }, // 自定义方法验证接收的数据 age: { type: Number, default: () => 10, // 验证操作 validator: (value) => { if (value > 200) { // 返回一个警告 return false; } return true; }, }, setAge: Function, }, methods: { addAge() { // 通过给当前的组件定义一个自定义的事情,完成子向父 // 参数1:自定义事件的名称,参数2以后,它是传过去的数据 // this.$emit('onSetAge',......参数) this.$emit("onSetAge", 10); }, }, }; </script> <style lang="scss" scoped> .title { color: red; } </style>
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <!-- 父组件实现子组件创建的onSetAge自定义事件 --> <child :title="title" :age="age" @onSetAge="setAge" /> </div> </template> <script> import child from "./components/child.vue"; export default { components: { child, }, data() { return { title: "我是一个显示内容", age: 80, }; }, methods: { setAge(n = 1) { this.age += n; }, }, }; </script> <style lang="scss" scoped></style>
3. 兄弟组件间传值
概述:
兄弟间组件传值,通过公共的父组件来进行。这种方式使得两个兄弟组件共用的数据得到提升,即状态提升。
子组件通过修改父组件中自己和兄弟的公共数据,来和自己的兄弟组件传值。
示例:
子组件1:
<template> <div> <h3>child1 -- {{ age }}</h3> <button @click="$emit('onAddAge', 1)">++++</button> </div> </template> <script> export default { props: ["age"], }; </script> <style lang="scss" scoped> </style>
子组件2:
<template> <div> <h3>child2 -- {{ age }}</h3> </div> </template> <script> export default { props: ["age"], }; </script> <style lang="scss" scoped> </style>
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <child1 :age="age" @onAddAge="addAge" /> <child2 :age="age" /> </div> </template> <script> import child1 from "./components/child1.vue"; import child2 from "./components/child2.vue"; export default { components: { child1, child2, }, data() { return { age: 80, }; }, methods: { addAge(n = 1) { this.age += n; }, }, }; </script> <style lang="scss" scoped></style>
4. 事件总线
概述:
兄第组件间通过状态提升的方式来进行传值,适用于父子场景中,一旦涉及到子孙场景,状态提升的操作就变得很复杂,这时候我们就要用到 Vue2 中给我们提供的事件总线来处理。
什么是事件总线?
非父子组件或更多层级间组件间传值,在Vue中通过单独的事件中心来管理组件间的传值。
它相当于一个全局的仓库,任何组件都可以去这个仓库里获取事件,它就类似沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行的通知其他组件来进行通信。
简单来说就是,假设现在有一个组件 a 往全局仓库中塞数据,另一个组件 b 只要订阅了全局仓库,就可以收到更新的数据。
事件总线只能进行通知,不能进行数据的存储功能。
注意:实现事件总线的前提条件是,必须先订阅和发布
语法:
- 建立统一的事件中心:
const bus = new Vue()
- 传递数据方,通过一个事件触发:
bus.$emit(方法名,传递的数据)
- 接收数据方,在生命周期函数中,通过
bus.$on(方法名,(参数)=>{})
来监听 - 销毁事件,在接受数据方,通过
bus.$off(方法名)
销毁,销毁之后无法监听数据
示例:
建立统一的事件中心(即全局对象)可以在主 js 文件中写入如下代码:
import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false // 事件总线 --- 是当前文件的私有变量 // 因为所有组件对象都能看到 Vue 原型对象上的属性和方法,所以我们可以在Vue的原型对象上通过设定一个eventBus对象, // Vue.prototype.bus = new Vue(),此时所有的组件对象都能看到eventBus属性对象。 const eventBus = new Vue() Vue.prototype.$eventBus = eventBus new Vue({ render: h => h(App), }).$mount('#app')
也可以使用插件的方式,建立全局对象:
新建一个 bus.js 文件:
export default Vue => { Vue.prototype.$eventBus = new Vue() }
在 main.js 中导入:
import Vue from 'vue' import App from './App.vue' import bus from './plugin/bus' Vue.config.productionTip = false Vue.use(bus) new Vue({ render: h => h(App), }).$mount('#app')
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <child1 :age="age" /> <child2 :age="age" /> </div> </template> <script> import child1 from "./components/child1.vue"; import child2 from "./components/child2.vue"; export default { components: { child1, child2, }, data() { return { age: 80, }; }, // 让父组件也订阅子组件的频道,作为介质向子组件2传递数据 created() { this.$eventBus.$on("age", (value) => (this.age += value)); }, // 在订阅的地方都要销毁,相当于关闭频道 beforeDestroy() { this.$eventBus.$off("age"); }, }; </script> <style lang="scss" scoped></style>
子组件1(发布者):
<template> <div> <h3>child1 -- {{ age }}</h3> <button @click="setAge">+++++</button> </div> </template> <script> export default { props: ["age"], methods: { setAge() { // 传递数据方 // 这里的作用是,往全局对象中塞数据 // 参数1:频道名称,不能重复 // 参数2-N,传过去的数据 this.$eventBus.$emit("age", 10); }, }, }; </script> <style lang="scss" scoped> </style>
子组件2(订阅者):
<template> <div> <h3>child2 -- {{ age }}</h3> </div> </template> <script> export default { props: ["age"], // 接收数据方 // 这里相当于子组件2订阅了 age 频道,可以接收到更新后的数据 created() { this.$eventBus.$on("age", (value) => { console.log(value); }); }, beforeDestroy() { this.$eventBus.$off("age"); } }; </script> <style lang="scss" scoped> </style>
5. Ref
概述:
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。
ref 它不但可以实现组件通信,而且它还可以获取dom对象。
使用ref来获取普通元素的dom对象:
<template> <div> <h3 class="title">App组件</h3> <hr /> <!-- ref它可以实现组件通信,而且它还可以获取dom对象 --> <!-- 使用ref来获取普通元素的dom对象 --> <div ref="username">张三</div> <button @click="getUsernameDom">获取张三dom</button> </div> </template> <script> export default { components: { }, data() { return { } }, methods: { getUsernameDom() { // ref 获取dom对象 console.log(this.$refs['username'].innerHTML) } } } </script> <style lang="scss" scoped></style>
ref 获取的 dom 列表并不是真实的 dom,因为获取 dom 是同步的,而视图渲染是异步的。我们需要用到$nextTick
,该方法用于获取最新的dom的方法 等待视图渲染完毕后才触发执行,得到真实dom。
ref 绑定到自定义组件:
父组件:
<template> <div> <h3 class="title">App组件 -- {{ ptitle }}</h3> <hr /> <!-- ref 实现组件通信 如果ref绑定在自定义组件上,通可以得到当前组件实例对象 --> <child ref="childRef" /> <button @click="getChild">父获取child组件中的数据</button> </div> </template> <script> import child from './components/child.vue' export default { components: { child }, data() { return { age: 100, ptitle: '' } }, methods: { getChild() { // this.$refs.childRef.title = 'aaaaa' // this.ptitle = this.$refs.childRef.title this.ptitle = this.$refs.childRef.setTitle('afewfewfew') } }, // 写在 mounted 中也可以实现 // mounted(){ // // console.log(this.$refs.childRef?.title) // this.ptitle = this.$refs.childRef.title // } } </script> <style lang="scss" scoped></style>
子组件:
<template> <div> <h3>child组件</h3> </div> </template> <script> export default { data() { return { title: '我是child组件中的数据' } }, methods: { setTitle(title) { this.title = title+'#####3' return this.title } } } </script> <style lang="scss" scoped></style>
6. root/parent/children
概述:
获取父组件对象或子组件对象集合。
三个方法:
this.$root
this.$parent
this.$children
示例:
父组件:
<template> <div> <h3 class="title">App组件</h3> <button @click="getChild">父获取child组件中的数据</button> <hr /> <child /> </div> </template> <script> import child from './components/child.vue' export default { components: { child }, data() { return { title: 'parent' } }, methods: { getChild() { console.log(this.$root,"---父组件中获取"); // console.log(this.$children); // 获取子元素中的数据 // console.log(this.$children[0].title); // 如果有多个孩子 this.$children.forEach(node => { // console.log(node?.title) // 短路运算 console.log(node.title && node.title) }) } } } </script> <style lang="scss" scoped></style>
子组件:
<template> <div> <h3>child组件</h3> <button @click="getParent">获取父组件中的数据</button> </div> </template> <script> export default { data() { return { title: '我是child组件中的数据' } }, methods: { getParent() { // 获取到根上的元素(main.js),在任何层级都可以获取 console.log(this.$root,"---子组件中获取") // 子组件获取父组件 console.log(this.$parent.title) } } } </script> <style lang="scss" scoped></style>
7. provide/inject
概述:
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
比如我们现在想要封装一个组件,组件内层级很多,我们想在组件内实现很方便的通信,却又想要与外界隔绝,这时候就需要用到 provide/inject。
父组件向子组件传值:
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <child /> </div> </template> <script> import child from "./components/child.vue"; export default { components: { child, }, data() { return { msg: 'app中的标题' }; }, // 发布,父组件的后代无论在哪个层级都能收到 // 写法1: // provide: { // title: "app中的标题", // }, // 如果你要使用当前组件中的数据或方法,就需要把provide写成函数方式且返回一个对象 // 写法2: provide() { return { title: this.msg, }; }, methods: {}, }; </script>
子组件:
<template> <div> <h3>child组件 --- {{ title }}</h3> </div> </template> <script> export default { // 注入 // 数组方式接收 写的比较的多 // inject: ['title'], // 对象方式接收 inject: { title: { // 默认值 default: () => '默认值' }, }, data() { return {}; }, methods: {}, }; </script>
provide 和 inject 绑定并不是可响应的,如果想要变成响应式的,可以在父组件中 provide 返回一个函数,并且在子组件中接收:
父组件:
<template> <div> <h3 class="title">App组件</h3> <input v-model="msg" /> <hr /> <child /> </div> </template> <script> import child from "./components/child.vue"; export default { components: { child, }, data() { return { msg: 'app中的标题' }; provide() { return { title: () => this.msg }; }, methods: {}, }; </script>
子组件:
<template> <div> <h3>child组件 --- {{ title() }}</h3> </div> </template> <script> export default { // 对象方式接收 inject: { title: { // 默认值 default: () => () => "默认值", }, }, data() { return {}; }, methods: {}, }; </script>
8. $attrs/$listeners
概述:
$attrs 获取没有在 props 中定义的属性,把 props 没有接收到的属性进行接收
$listeners 获取父组件给子组件自定义事件
示例:
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <child :msg="msg" uid="1" @setTitle="setTitle" /> </div> </template> <script> import child from './components/child.vue' export default { components: { child }, data() { return { msg: 'app中的标题' } }, methods: { setTitle(title) { this.msg = title } } } </script>
子组件:
<template> <div> <h3>child组件 --- {{ $attrs.msg }}</h3> <button @click="setMsg">修改msg</button> </div> </template> <script> export default { props: ['uid'], data() { return {} }, methods: { setMsg() { // console.log(this.$listeners) this.$listeners.setTitle(Date.now()) } } } </script>
利用 attrs 来处理所以的自定义属性数据:
简单来说,就是拿一个组件(数据层容器)包裹另一个组件(最终显示在页面上的组件),增强了组件能力。
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <child :phone="phone" :uid="100" /> </div> </template> <script> import child from './components/child.vue' export default { components: { child }, data() { return { phone: '1323125125' } }, methods: {} } </script>
子组件(数据层容器,进行数据包装和处理):
<template> <div> <h3>child组件</h3> <showphone :attrs="attrs" /> </div> </template> <script> import showphone from './showphone.vue' export default { components: { // child 组件不直接显示数据,而是将数据进行包装处理后,交给自己的子组件 showphone 来显示 // 这时的 child 相当于一个数据层容器,包裹了 showphone 进行了相关的数据处理 showphone }, data() { return { attrs: {} } }, created() { this.attrs = { ...this.$attrs, phone: 'abc ---' + this.$attrs.phone } } } </script>
子组件的子组件(最终显示在页面数据的组件):
<template> <div> <h3>showphone显示手机号码 --- {{ attrs.phone }}</h3> </div> </template> <script> export default { props: ['attrs'] } </script>
9. v-model绑定到自定义组件中
默认用法:
父组件:
<template> <div> <h3 class="title">App组件</h3> <!-- v-model它可以对于表单项进行数据的双项绑定 --> <!-- <input type="text" v-model="title" /> --> <hr /> <!-- v-model它是一个语法糖,它是 value和事件[input[默认]]的集合体 --> <!-- v-model语法糖的作用相当于下面这一句 --> <!-- <child :value="title" @input="setTitle" /> --> <child v-model="title" /> </div> </template> <script> import child from './components/child.vue' export default { components: { child }, data() { return { title: '我是父组件的title' } }, // v-model相当于写有这个方法 // methods: { // setTitle(title) { // this.title = title // } // } } </script>
子组件:
<template> <div> <!-- 默认的 --> <h3>child组件 -- {{ value }}</h3> <!-- 默认的 --> <button @click="$emit('input', Date.now())">修改value数据</button> </div> </template> <script> export default { // 默认的 props: ['value'], data() { return {}; }, }; </script>
除了使用默认的用法,还可以自定义属性名称和事件名称:
只需要修改子组件:
<template> <div> <!-- 默认的 --> <!-- <h3>child组件 -- {{ value }}</h3> --> <h3>child组件 -- {{ title }}</h3> <!-- 默认的 --> <!-- <button @click="$emit('input', Date.now())">修改value数据</button> --> <button @click="$emit('change', Date.now())">修改value数据</button> </div> </template> <script> export default { // 默认的 // props: ['value'], props: ["title"], model: { // 修改v-model中的绑定的属性名称,默认为value prop: "title", // 修改v-model它的自定义事件的名称,默认为input event: "change", }, data() { return {}; }, }; </script>
10. sync同步动态属性数据
上文说到 props 是单向数据流,子组件不能修改父组件的数据,但是加上 sync 修饰符就可以实现修改 。
父组件:
<template> <div> <h3 class="title">App组件</h3> <hr /> <!-- sync修饰符,它是一语法糖 动态属性和 update:属性名称 事件 --> <child :title.sync="title" /> </div> </template> <script> import child from './components/child.vue' export default { components: { child }, data() { return { title: '我是父组件的title' } }, methods: {} } </script>
子组件:
<template> <div> <h3>child组件 -- {{ title }}</h3> <button @click="setTitle">修改Title</button> </div> </template> <script> export default { props: ['title'], data() { return {} }, methods: { setTitle() { // 这里的update事件是固定写法 this.$emit('update:title', Date.now()) } } } </script>
11. localStorage / sessionStorage
这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。
通过window.localStorage.getItem(key)
获取数据
通过window.localStorage.setItem(key,value)
存储数据
注意用JSON.parse() / JSON.stringify()
做数据格式转换
localStorage / sessionStorage可以结合vuex,实现数据的持久保存,同时使用vuex解决数据和状态混乱问题。
到此这篇关于Vue组件间传值的实现解析的文章就介绍到这了,更多相关Vue组件间传值内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!