目录

一、通过 $parent 和 $root 获取父/根组件实例

二、通过 $refs 获取子组件实例

在循环中使用ref

三、通过 prop 向子组件传值

命名方式

动态属性

字面量语法 vs 动态语法

单向数据流

属性验证

四、通过 emit 向父组件传值

使用 v-on 绑定自定义事件

emit 传值

向自定义事件中传入额外参数

给组件绑定原生事件

总线通信

五、组件数据双向绑定(.sync 修饰符)

六、通过 v-model 实现表单组件双向数据传递

同步子组件的表单输入

自定义组件的 v-model

七、依赖注入(provide / inject)

注入数据

注入方法

八、参考资料


一、通过 $parent 和 $root 获取父/根组件实例

在子组件中,想要获取到父组件、根组件的实例,可以直接通过 $parent 和 $root 获取。

比如在父组件中有如下数据:

import ChildView from '../ChildView.vue'
export default {components: {ChildView},data() {return {msg: 'Hello'}},methods: {get() {console.log("Hello world")}}
}

在子组件 ChildView 中获取:

<template>
<div>{{$parent.msg}}</div>
</template><script>
export default {created() {this.$parent.get()}
}
</script>

父组件中的数据也是可读写的 ,可以直接修改其值:

<template>
<div>{{$parent.msg}}</div>
<!-- 修改父组件的数据 -->
<button @click="changeMsg"> </button>
</template><script>
export default {inject: ['get'],created() {this.$parent.get()},methods: {changeMsg() {this.$parent.msg = "I'm a child"}}
}
</script>


而所谓的根组件,其实就是在创建Vue实例时创建的那一堆数据:

new Vue({data: {foo: 1},computed: {bar: function () { /* ... */ }},methods: {baz: function () { /* ... */ }}
})

在任何子孙组件中都可访问:

// 获取根组件的数据
this.$root.foo// 写入根组件的数据
this.$root.foo = 2// 访问根组件的计算属性
this.$root.bar// 调用根组件的方法
this.$root.baz()

可以归纳如下:

  • this 代表当前组件的实例
  • this.$parent 代表父组件的实例
  • this.$root 代表根组件的实例

二、通过 $refs 获取子组件实例

尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用。例如:

<base-input ref="userNameInput"></base-input>

现在在你已经定义了这个 ref 的组件里,你可以使用:

this.$refs.userNameInput

来访问这个 <base-input> 实例,以便不时之需。比如程序化地从一个父级组件聚焦这个输入框。

在刚才那个例子中,该 <base-input> 组件也可以使用一个类似的 ref 提供对内部这个指定元素的访问,例如:

<input ref="input">

甚至可以通过其父级组件定义方法:

methods: {// 用来从父级组件聚焦输入框focus: function () {this.$refs.input.focus()}
}

这样就允许父级组件通过下面的代码聚焦 <base-input> 里的输入框:

this.$refs.usernameInput.focus()

在循环中使用ref

当 ref 和 v-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。

看一个示例:

<template>
<ul><li v-for="(item, key) in list" :key="key" ref="li">{{item}}</li>
</ul>
</template><script>
export default {mounted() {console.log(this.$refs.li)},
}
</script>

因此,要取出其中的某个元素,需要使用相应的下标:

this.$refs.li[0].innerText

上面的程序看到我是在 mounted 中取出 $refs 的,因为$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。所以应该避免在模板或计算属性中访问 $refs

三、通过 prop 向子组件传值

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。

子组件要显式地用 props 选项声明它预期的数据:

Vue.component('child', {// 声明 propsprops: ['message'],// 就像 data 一样,prop 也可以在模板中使用// 同样也可以在 vm 实例中通过 this.message 来使用template: '<span>{{ message }}</span>'
})

然后我们可以这样向它传入一个普通字符串:

<child message="hello!"></child>

命名方式

HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名):

Vue.component('child', {// 在 JavaScript 中使用 camelCaseprops: ['myMessage'],template: '<span>{{ myMessage }}</span>'
})
<!-- 在 HTML 中使用 kebab-case -->
<child my-message="hello!"></child>

如果你使用字符串模板,则没有这些限制。

动态属性

与绑定到任何普通的 HTML 特性相类似,我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件:

<div><input v-model="parentMsg"><br><child v-bind:my-message="parentMsg"></child>
</div>

你也可以使用 v-bind 的缩写语法:

<child :my-message="parentMsg"></child>

如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind(即用 v-bind 而不是 v-bind:prop-name)。例如,已知一个 todo 对象:

todo: {text: 'Learn Vue',isComplete: false
}

然后:

<todo-item v-bind="todo"></todo-item>

将等价于:

<todo-item :text="todo.text" :is-complete="todo.isComplete"></todo-item>

字面量语法 vs 动态语法

初学者常犯的一个错误是使用字面量语法传递数值:

<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp>

因为它是一个字面量 prop,它的值是字符串 "1" 而不是一个数值。如果想传递一个真正的 JavaScript 数值,则需要使用 v-bind,从而让它的值被当作 JavaScript 表达式计算:

<!-- 传递真正的数值 -->
<comp :some-prop="1"></comp>

单向数据流

Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

在两种情况下,我们很容易忍不住想去修改 prop 中数据:

  1. Prop 作为初始值传入后,子组件想把它当作局部数据来用;
  2. Prop 作为原始数据传入,由子组件处理成其它数据输出。

对这两种情况,正确的应对方式是:

  1. 定义一个局部变量,并用 prop 的值初始化它:

    props: ['initialCounter'],
    data: function () {return { counter: this.initialCounter }
    }
  2. 定义一个计算属性,处理 prop 的值并返回:
    props: ['size'],
    computed: {normalizedSize: function () {return this.size.trim().toLowerCase()}
    }

    注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

    属性验证

    我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。

    要指定验证规则,需要用对象的形式来定义 prop,而不能用字符串数组:

    Vue.component('example', {props: {// 基础类型检测 (`null` 指允许任何类型)propA: Number,// 可能是多种类型propB: [String, Number],// 必传且是字符串propC: {type: String,required: true},// 数值且有默认值propD: {type: Number,default: 100},// 数组/对象的默认值应当由一个工厂函数返回propE: {type: Object,default: function () {return { message: 'hello' }}},// 自定义验证函数propF: {validator: function (value) {return value > 10}}}
    })

    type 可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

type 也可以是一个自定义构造器函数,使用 instanceof 检测。

四、通过 emit 向父组件传值

父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派得上用场了。

每个 Vue 实例都实现了事件接口,即:

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件

使用 v-on 绑定自定义事件

Vue 的事件系统与浏览器的 EventTarget API 有所不同。尽管它们的运行起来类似,但是 $on$emit 并不是addEventListenerdispatchEvent 的别名。

另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。

父组件:

<div id="counter-event-example"><p>{{ total }}</p><button-counter v-on:increment="incrementTotal"></button-counter><button-counter v-on:increment="incrementTotal"></button-counter>
</div>
new Vue({el: '#counter-event-example',data: {total: 0},methods: {incrementTotal: function () {this.total += 1}}
})

子组件:

Vue.component('button-counter', {template: '<button v-on:click="incrementCounter">{{ counter }}</button>',data: function () {return {counter: 0}},methods: {incrementCounter: function () {this.counter += 1this.$emit('increment')}}
})

上例中,子组件使用 emit 向父组件传递一个 increment 的事件,父组件只需监听 increment 即可,使用 v-on:increment 或 @increment 。

emit 传值

子组件中可以使用 emit 附带数据传递给父组件。

子组件:

Vue.component('counter', {template: '<button v-on:click="incrementCounter">{{ counter }}</button>',data: function () {return {counter: 0}},methods: {incrementCounter: function () {this.counter += 1this.$emit('increment', this.counter)}}
})

父组件:

<div id="counter-event-example"><p>{{ total }}</p><counter @increment="incrementTotal"></counter>
</div>
new Vue({el: '#counter-event-example',data: {total: 0},methods: {incrementTotal (counter) {this.total = counter}}
})

上例中,子组件向父组件传递一个 increment 事件,并附带数据 ,在父组件中相应的事件监听方法中可以捕捉到传递的数据。

向自定义事件中传入额外参数

有的时候,在某些自定义组件中,通过 $emit 本身就暴露出一些参数的情况下,我们还需要从父组件中传递其他参数,但是如果直接写到方法的参数中会覆盖本身的 $emit 返回的参数。这个时候可以在外面包裹一层箭头函数,在箭头函数体中调用方法并传递额外的参数。

举个例子,在一个组件中通过 $emit 返回了一些数据:

<template><divv-for="(item, index, key) in list":key="key"@click="clickItem(item, index)">{{ item.name }}</div>
</template><script>
export default {props: {list: Array,},methods: {clickItem(item, index) {this.$emit("click", item, index);},},
};
</script>

如果在父组件中直接传入参数,则会覆盖掉从 $emit 中返回的值:

<template><div><TestView @click="clickItem('hello')" :list="list"></TestView></div>
</template><script>
import TestView from "./components/TestView.vue";export default {components: {TestView,},data() {return {list: [{name: "item1",},{name: "item2",},],};},methods: {clickItem(item, index, data) {console.log(item, index, data); // hello undefined undefined},},
};
</script>

如果使用事件对象 $event ,则只能够获取到从 $emit 传出的第一个参数:

<template><div><TestView @click="clickItem($event, 'hello')" :list="list"></TestView></div>
</template>

点击列表项后将打印出:

{...} "hello" undefined

改写为以下写法即可:

<template><div><TestView@click="(item, index) => {clickItem(item, index, 'hello');}":list="list"></TestView></div>
</template>

点击列表项后将打印出:

{...} 0 "hello"

给组件绑定原生事件

有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符 .native。例如:

<my-component @click.native="doTheThing"></my-component>

总线通信

有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:

let bus = new Vue()// 触发组件 A 中的事件
bus.$emit('id-selected', 1)// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {// ...
})

在复杂的情况下,应该考虑使用专门的状态管理模式。

五、组件数据双向绑定(.sync 修饰符)

在一些情况下,我们可能会需要对一个 prop 进行“双向绑定”。事实上,这正是 Vue 1.x 中的 .sync 修饰符所提供的功能。当一个子组件改变了一个带 .sync 的 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了单向数据流。由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,你完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。

从 Vue 2.3.0 起重新引入了 .sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。

如下代码:

<comp :foo.sync="bar"></comp>

会被扩展为:

<comp :foo="bar" @update:foo="val => bar = val"></comp>

当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:

this.$emit('update:foo', newValue)

相当于在子组件中更改了来自父组件传递的值,再通过 emit 将改变的值反馈给父组件,父组件拿到更改后的值后,再将传递给子组件的值更新。

举个例子:

父组件:

<template><div id="app"><HelloWorld :msg.sync='msg'/></div>
</template><script>
import HelloWorld from './components/HelloWorld.vue'export default {components: {HelloWorld},data() {return {msg: 'Hello'}},
}
</script>

子组件:

<template><div class="hello"><h1>{{ msg }}</h1><button @click="modifyMsg">修改msg</button></div>
</template><script>
export default {props: {msg: String},methods: {modifyMsg() {this.$emit('update:msg', 'Got msg')}}
}
</script>

效果如下:

六、通过 v-model 实现表单组件双向数据传递

同步子组件的表单输入

由于下面这种写法

<input v-model="something">

这不过是以下示例的语法糖:

<inputv-bind:value="something"v-on:input="something = $event.target.value" />

所以在组件中使用时,它相当于下面的简写:

<custom-inputv-bind:value="something"v-on:input="something = arguments[0]">
</custom-input>

所以要让组件的 v-model 生效,它应该 (从 2.2.0 起是可配置的):

  • 接受一个 value 的 prop
  • 在有新的值时触发 input 事件并将新值作为参数

举个例子:一个非常简单的货币输入的自定义控件。

父组件:

<template><div><div>${{ price }}</div><currency-input v-model="price"></currency-input></div>
</template><script>
import CurrencyInput from "./components/CurrencyInput.vue";export default {components: {CurrencyInput,},data() {return {price: 10,};},
};
</script>

子组件:

<template><span><inputref="input":value="value"@input="updateValue($event.target.value)"/></span>
</template><script>
export default {props: ["value"], // 显式接收一个 value 的 propmethods: {// 不直接更新值,而是使用此方法来对输入值进行格式化和位数限制updateValue: function (value) {let len =value.indexOf(".") === -1 ? value.length : value.indexOf(".") + 3;let formattedValue = value.trim().slice(0, len);// 如果值尚不合规,则手动覆盖为合规的值if (formattedValue !== value) {this.$refs.input.value = formattedValue;}// 通过 input 事件带出数值this.$emit("input", Number(formattedValue));},},
};
</script>

效果如下:

自定义组件的 v-model

默认情况下,一个组件的 v-model 会使用 value 属性input 事件。但是诸如单选框、复选框之类的输入类型可能把 value 用作了别的目的。model 选项 (从 2.2.0 起)可以避免这样的冲突:

Vue.component('my-checkbox', {model: {prop: 'checked',event: 'change'},props: {checked: Boolean,// 这样就允许拿 `value` 这个 prop 做其它事了value: String},// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>

上述代码等价于:

<my-checkbox:checked="foo"@change="val => { foo = val }"value="some value">
</my-checkbox>

注意你仍然需要显式声明 checked 这个 prop。


checkbox 示例:

父组件:

<template><div><div>checkbox: {{ checkbox1 }} {{ checkbox2 }}</div><div>colors: {{ colors }}</div><my-checkboxv-model="checkbox1"value="red"@change="updateVal"></my-checkbox><my-checkboxv-model="checkbox2"value="green"@change="updateVal"></my-checkbox></div>
</template><script>
import MyCheckbox from "./components/MyCheckbox.vue";export default {components: { MyCheckbox },data() {return {checkbox1: false,checkbox2: false,colors: [],};},methods: {updateVal(checked, value) {console.log(checked);console.log(value);if (checked) {this.colors.push(value);} else {this.colors.splice(this.colors.indexOf(value), 1);}},},
};
</script>

子组件:

<template><span><inputtype="checkbox"ref="checkbox"@change="updateValue($event.target)":value="value":id="value"/><label :for="value">{{ value }}</label></span>
</template><script>
export default {model: {prop: "checked",event: "change",},props: {checked: Boolean,value: String,},methods: {updateValue(val) {// 通过 change 事件带出数值this.$emit("change", val.checked, val.value);},},
};
</script>

效果如下:

七、依赖注入(provide / inject

除了通过 props 向子组件传值之外,Vue还允许通过 provide / inject 向子组件注入数据、方法。跟 props 不同的是, inject 可以向 子孙组件 注入数据、方法,无视子孙组件的层级,而不光光只是子组件。

即,比如:

Root(provide) -> Child 1 -> Child 2 -> Child 3(inject)

在Root组件中注入(provide)数据,只要是其子孙组件,都能通过 inject 取出注入的数据。

注入数据

举个例子,在祖先组件中注入数据 msg

<template><div><InjectView /></div>
</template><script>
import InjectView from "../components-test/InjectView.vue";
export default {components: {InjectView,},provide: {msg: "Hello",},
};
</script>

其子组件(InjectView)又包含一个子组件 InjectChild

<template><div><InjectChild /></div>
</template><script>
import InjectChild from "./InjectChild.vue";
export default {components: {InjectChild,},
};
</script>

最后在 InjectChild 中取出注入的数据:

<template><div>{{ info }}</div>
</template><script>
export default {inject: {info: "msg",},created() {console.log(this.info); // Hello},
};
</script>

将注入的数据 msg 更名为 info

要是使用props,则需要层层传值才能达到相同的效果。

注入方法

data 属性一样, provide 也可返回一个函数,其返回值甚至可以为组件中的方法。

还是以上面的祖先组件为例,下面只贴出其 script 部分:

import InjectView from '../components-test/InjectView.vue'
export default {components: {InjectView},provide() {return {log: this.log}},methods: {log() {console.log("Hello world")}}
}

InjectView 中取出此方法:

export default {inject: {print: 'log',},created() {this.print() // Hello world}
}

inject 也可返回一个数组,跟 props 差不多:

export default {inject: ['log'],created() {this.log()}
}

八、参考资料

  • 访问根实例
  • 访问父级组件实例
  • Prop
  • 自定义事件
  • provide / inject
  • 依赖注入

VUE组件通讯——父子互传、互调相关推荐

  1. vue组件通讯:父传子、子传父、事件发射详解

    1.组件通讯 每个组件有自己的数据, 提供在data中:每个组件数据独立, 组件数据无法互相直接访问 (合理的) 但是如果需要跨组件访问数据,  怎么办呢?  =>   组件通信组件通信的方式有 ...

  2. this指向-作用域、作用域链-预解析 变量提升-Vue组件传值 父子 子父 非父子-Vue数据双向绑定原理

    目录 this指向 作用域.作用域链 预解析 变量提升 Vue组件传值 父子 子父 非父子 Vue数据双向绑定原理 1.this指向 函数的this指向 看调用.不看声明 (1)普通函数调用 ①函数名 ...

  3. Vue组件通讯方式 provide/inject 介绍以及使用场景

    文章目录 vue的通讯方式 provide / inject的缺点 使用办法 代码案例展示一(provide字符串) 代码案例展示二(provide返回一个方法,并且方法返回字符串) vue的通讯方式 ...

  4. 九段刀客:vue组件通讯

    组件通信的方法: 1.props和emit 2.vuex 3.bus 4.children和parent 5.ref props和emit() 说明:props和emit用于父子组件通讯,props父 ...

  5. vue组件通信:父传子—子传父

    我们都知道组件是vue里很重要的一个知识点,这里我看的是b站上的coderwhy老师的视频 我看到了弹幕上说很多人在这一块不理解:下面我就来写分享以下我的课程总结 父传子 为什么要进行"传& ...

  6. Vue组件通信:父传子、子传父、跨组件通信

    方法一:组件通信_父传子_props(属性绑定) 在进行组件通信之前,我们首先要明确父和子是谁,父传子=>在父中引入子(被引入的是子) 1. 父传子,要先在子组件内定义props变量,准备接收, ...

  7. vue组件间传值: 父传子、子传父、非父子组件传值

    父组件向子组件传值 // 父组件通过属性将值传递给子组件 <template><div><closePopup :optionsID="optionsID&qu ...

  8. 浏览器 调用 vue 组件_父子组件的通信

    在前端vue框架中,我们常常使用多个页面组成一个组件来创建项目.每个组件都有其自己的功能,基于组件的体系结构使开发和维护应用程序变得容易.在开发过程中,您可能会遇到需要与其他组件共享数据的情况.在本文 ...

  9. Vue组件通信——父子组件通信的四种方法

    引入组件 全局引入 在main.js文件中引入并注册 import ChildrenDemo from '@/views/components/ChildrenDemo' Vue.component( ...

最新文章

  1. YTU 2899: D-险恶逃生 I
  2. 查询三个月前的所有数据的sql语句
  3. python工作-Python自动化运维|Python语言工作岗位待遇如何?
  4. Android实现文件下载并自动安装apk包
  5. java动画闪烁_优化Java动画编程中的显示效果
  6. python怎么爬虫理数据_Python神技能 | 使用爬虫获取汽车之家全车型数据
  7. [css] 举例说明你对相邻兄弟选择器的理解
  8. Mac使用Homebrew安装Kafka
  9. SQL SERVER2008 存储过程、表、视图、函数的权限
  10. python实现不重复排列组合_Python使用combinations实现排列组合的方法
  11. tiledmap 图块属性_cocos2dx[3.4](25)——瓦片地图TiledMap
  12. 多媒体视频会议系统的测试方法
  13. GPRS通信原理及应用特点
  14. Blockchain Meets Edge Computing: A Distributed and Trusted Authentication System
  15. 怎么用java做动态壁纸_开发实时壁纸
  16. 多种汉语方言语音落地应用,微软智能语音解锁更多交互场景
  17. php抓取页面方法汇总
  18. 集成测试最全详解,看完必须懂了
  19. High performance server architecture(高性能服务器架构)
  20. Eth-Trunk捆绑技术

热门文章

  1. 真实!八大最辛苦的公务员岗位
  2. java 图像傅里叶变换_图像频域滤波与傅里叶变换
  3. 【STL源码剖析】list模拟实现 | 适配器实现反向迭代器【超详细的底层算法解释】
  4. 微信小程序下载zip压缩包后解压,并且打开文件查看的内容
  5. vue绑定后台数据ajax,vueJS 获取后台数据 绑定data
  6. 杰里之AC69 系列 K 歌宝的混响、MIC 和背景音量调节函数篇
  7. arduino LED灯控制基础篇
  8. SH35D喇叭扩音器:大功率喊话,彻底解放你的嗓子
  9. 计算机考研复试面试问答整理(计算机网络、数据结构、操作系统、数据库、热点概念)
  10. php石头剪刀布源码,剪刀石头布微信小程序配套源码