在开发过程中,我们会经常遇到需要组件之间相互进行通信:
比如App可能使用了多个相同的子组件,每个地方的子组件展示的内容不同,那么我们就需要使用者传递给子组件一些数据,让其进行展示;
又比如我们在父组件中一次性请求了所有子组件的数据,那么就需要传递给它们来进行展示;
也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个Vue项目中,组件之间的通信是非常重要的环节,所以接下来我们就具体学习一下组件之间是如何相互之间传递数据的
父子组件之间如何进行通信呢?
父组件传递给子组件:通过props属性;
子组件传递给父组件:通过$emit触发事件;
4.5.1 父组件传递给子组件
在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示:这个时候我们可以通过props来完成组件之间的通信;
什么是Props呢?
Props是你可以在组件上注册一些自定义的attribute;
父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值;
Props有两种常见的用法:
方式一:字符串数组,数组中的字符串就是attribute的名称;
方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等;
Props的数组用法
Person.vue
- <template>
-
- <p>姓名:{{name}}p>
- <p>年龄:{{age}}p>
- template>
- <script>
- //组件交互相关的js代码
- export default{
- name:'Person',
- props:["name","age"],
- data(){
- return {
- }
- },
- }
- script>
-
- <style>
- /*组件的样式 */
- p{
- color:red;
- }
- style>
App.vue
- <template>
- <div>
- <Person name="张三" age="21"/>
- <hr/>
- <Person name="李四" age="20"/>
- </div>
- </template>
-
- <script>
- import Person from './components/Person.vue'
- export default {
- name:'App',
- data() {
- return {
-
- };
- },
- components:{
- Person,
- },
- };
- </script>
-
- <style lang="scss" scoped>
-
- </style>
数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制,接下来我们来看一下对象的写法是如何让我们的props变得更加完善的。
当使用对象语法的时候,我们可以对传入的内容限制更多:
比如指定传入的attribute的类型
比如指定传入的attribute是否是必传的
比如指定没有传入时,attribute的默认值
- props:{
- name:{
- type:String,
- required:true,
- default:'无名氏'
- },
- age:{
- type:Number,
- required:true,
- },
- },
细节
那么type的类型都可以是哪些呢? String Number Boolean Array Object Date Function Symbol
Prop 名字格式
如果一个 prop 的名字很长,应使用 camelCase 形式,因为它们是合法的 JavaScript 标识符,可以直接在模板的表达式中使用,也可以避免在作为属性 key 名时必须加上引号。
- export default {
- props: {
- personSex: String
- }
- }
<p>性别:{{personSex}}p>
虽然理论上你也可以在向子组件传递 props 时使用 camelCase 形式 (使用 DOM 模板时例外),但实际上为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式:
<Person name="李四" age="22" person-sex="男"/>
对于组件名我们推荐使用 PascalCase,因为这提高了模板的可读性,能帮助我们区分 Vue 组件和原生 HTML 元素。然而对于传递 props 来说,使用 camelCase 并没有太多优势,因此我们推荐更贴近 HTML 的书写风格。
前面我们父组件向子组件传递数据, 都是静态值形式
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告:
导致你想要更改一个 prop 的需求通常来源于以下两种场景:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
- export default {
- props: {
- // 基础类型检查
- //(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
- propA: Number,
- // 多种可能的类型
- propB: [String, Number],
- // 必传,且为 String 类型
- propC: {
- type: String,
- required: true
- },
- // Number 类型的默认值
- propD: {
- type: Number,
- default: 100
- },
- // 对象类型的默认值
- propE: {
- type: Object,
- // 对象或者数组应当用工厂函数返回。
- // 工厂函数会收到组件所接收的原始 props
- // 作为参数
- default(rawProps) {
- return { message: 'hello' }
- }
- },
- // 自定义类型校验函数
- propF: {
- validator(value) {
- // The value must match one of these strings
- return ['success', 'warning', 'danger'].includes(value)
- }
- },
- // 函数类型的默认值
- propG: {
- type: Function,
- // 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
- default() {
- return 'Default function'
- }
- }
- }
- }
4.5.2 子组件传递给父组件
什么情况下子组件需要传递内容到父组件呢?
当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容
子组件有一些内容想要传递给父组件的时候
我们如何完成上面的操作呢?
首先,我们需要在子组件中定义好在某些情况下触发的事件名称; 自定义事件
其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中
最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
上图我们是通过点击子组件的两个按钮(+1
,-1
), 对父组件的count
进行加减操作
首先我们创建一个子组件CounterOperation.vue
- <template>
- <div>
- <button @click="increment">+1</button>
- <button @click="decrement">-1</button>
- </div>
- </template>
-
- <script>
- export default {
- name: 'CodeCounterOperation',
- //用于声明由组件触发的自定义事件。
- emits:['addOne','subOne'],
-
- data() {
- return {
-
- };
- },
-
- mounted() {
-
- },
-
- methods: {
- increment(){
- //自定义事件
- //通过this.$emit(事件名[,参数列表]) 发出事件
- this.$emit('addOne')
- },
- decrement(){
- this.$emit('subOne')
- }
- },
- };
- </script>
-
- <style lang="scss" scoped>
-
- </style>
接下来,我们在父组件App.vue,导入子组件,并在子组件上绑定自定义事件
- <template>
- <div>
- <h2>当前计数:{{count}}</h2>
- <!--在子组件绑定自定义事件-->
- <Counter-Operation @addOne="add" @subOne="sub"/>
- </div>
- </template>
-
- <script>
- import CounterOperation from './components/CounterOperation.vue'
- export default {
- name:'App',
- data() {
- return {
- count : 0,
- };
- },
- components:{
- CounterOperation,
- },
- methods:{
- add(){
- this.count++;
- },
- sub(){
- this.count--;
- }
- }
- };
- </script>
-
- <style lang="scss" scoped>
-
- </style>