在组件之间进行传递数据一般可以通过props从父组件向子组件传递,用自定义事件的方式从子组件向父组件传递数据。

父传子props

之前模版语法中也演示过一次props的作用,这里再详细的介绍一下props

通过props你可以传递各种类型的数据,你可以直接传递一个静态的数据,也可以传递动态获取的数据。

传递静态数据

<!-- 传递静态的字符串 -->
<comp msg="this is a static message"></comp>

<!-- 传递静态的布尔类型数据(子组件的props也要指定类型为Boolean) -->
<comp is-success></comp>
<comp :is-success="true"></comp>

<!-- 传递静态的数组类型数据(数组、对象等类型虽然数据是静态的,但为了让Vue知道是数组类型而不是字符串就必须是v-bind来告知Vue这是一个js表达式) -->
<comp :book-ids="[111,222,333]"></comp>

tips: 在html中标签的属性不区分大小写,因此使用时camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 。

传递动态数据

传递动态数据都需要使用v-bind或者:来动态赋值。

<!-- 传递动态的字符串 -->
<comp :msg="comp.msg"></comp>

<!-- 传递动态的布尔类型数据 -->
<comp :is-success="comp.isSuccess"></comp>

<!-- 传递动态的数组类型数据 -->
<comp :book-ids="comp.bookIds"></comp>

props的定义以及类型检查

props的定义

你可以通过字符串数据的方式定义子组件中的props

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

也可以像Java等语言一样单独为一个属性定义类型,这种方式看上去会更加具有可读性。

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // 或任何其他构造函数
}

props的类型检查

  props: {
    // 基础的类型检查 (`null` 和 `undefined` 值会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组的默认值必须从一个工厂函数返回
      default() {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator(value) {
        // 这个值必须与下列字符串中的其中一个相匹配
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // 具有默认值的函数
    propG: {
      type: Function,
      // 与对象或数组的默认值不同,这不是一个工厂函数——这是一个用作默认值的函数
      default() {
        return 'Default function'
      }
    }
  }

自定义构造函数类型,此举可以让你知道这个属性是否是通过new的方式创建出来的(一般不怎么用)。

export class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

// es6之前的语法
// function Person(firstName, lastName) {
//   this.firstName = firstName
//   this.lastName = lastName
// }

export default {
  name: "Test",
  props: {
    customProp: {
      type: Person
    }
  }
}
<template>
  <test :custom-prop="customProp"/>
</template>

<script>
import Test from "@/components/Test";
import {Person} from "@/components/Test";

export default {
  name: 'App',
  components: {
    Test
  },
  data() {
    return {
      customProp: new Person('abc', 'sdj')
    }
  }
}
</script>

数据子传父

Vue有个自定义事件的功能,我们可以通过自定义事件的方式把子组件的数据传递给父组件。

// 可以在子组件的一个事件函数中使用$emit函数传递数据
this.$emit('myEvent', 123)

// 父组件中使用自定义定义的事件(同样的驼峰转连接符)
<my-component @my-event="myEvent"></my-component>


// ... 父组件中的myEvent函数接收的参数就是子组件中传递的数据
myEvent(data) {
// data就是子组件传递过来的数据
}

更多关于自定义事件的使用,可查阅官方文档自定义事件

父子组件相互访问

Provide / Inject

针对多层组件引用,比如app->subApp->demo1->demo2->demo3想要使用app组件中的某些方法或者属性,就可以使用一对Provide/Inject来达到目的,无论层次有多深都可以使用这一对。

// ... App组件
export default {
  name: "Test"
  components: {
    SubApp
  },
  data() {
    return {
      user: 
    }
  },
  provide: {
    user: 'Bennett'
  }
}
// ... demo3组件
export default {
  name: "Test",
  inject: ['user'],
  created() {
     // this.user is Bennett
     console.log(this.user);
  }
}

上面的provide中的user属性只能给后代组件使用,本身使用不到,demo3要想访问到实例中的属性或函数就需要使用provide()返回一个对象的形式才可以。

// ... App组件
export default {
  name: "Test"
  components: {
    SubApp
  },
  data() {
    return {
      user: 
    }
  },
  provide() {
    return {
      user: this.user
    }
  }
}

注意⚠️inject的数据并不会随着provide中的数据改变而改变,要想做到响应式就需要返回的数据是通过computed包装过的计算属性。

// ... App组件
export default {
  name: "Test"
  components: {
    SubApp
  },
  data() {
    return {
      user: 
    }
  },
  provide() {
    return {
      user: Vue.computed(() => this.user);
    }
  }
}

使用实例属性访问

Vue为所有的组件都提供了以下几个属性:

  • $data: 当前实例的数据对象
  • $props: 当前实例接收到的props对象
  • $el: 组件实例正在使用的根 DOM 元素(不推荐直接使用$el,推荐在使用模版引用)
<input ref="customInput" />

之后可通过this.$refs.customInput.focus()触发focus事件。

  • $options: 用于当前组件实例的初始化选项。当你需要在选项中包含自定义property时会有用处。
const app = createApp({
  customOption: 'foo',
  created() {
    console.log(this.$options.customOption) // => 'foo'
  }
})
  • $parent: 父实例,如果当前实例有的话。
  • $root: 当前组件树的根组件实例。如果当前实例没有父实例,此实例将会是其自己。
  • $slots: 用来以编程方式访问通过插槽分发的内容。每个具名插槽都有其相应的property(例如:v-slot:foo中的内容将会在this.$slots.foo()中被找到)。
  • $refs: 一个对象,持有注册过refattribute 的所有 DOM 元素和组件实例。
  • attrs: 包含了父作用域中不作为组件props或自定义事件的attribute绑定和事件。

父组件访问子组件数据

可以用上面的this.$refs.customRefName.componentData访问到customRefName子组件的componentData。这个需要依赖于$refs模版引用组合使用。

<custom-component ref="customComponent"></custom-component>
<custom-component></custom-component>


export default {
  name: "Test",
  components: {
   CustomComponent
  },
  methods: {
    demoMethod() {
      // 访问第一个CustomComponent组件中的componentData属性、componentMethod方法
      console.log(this.$refs.customRefName.componentData)
      console.log(this.$refs.customRefName.componentMethod)
    }
  }
}

子组件访问父组件数据

这个可直接使用$parent访问到父组件实例中的内容,比如:

// 访问父组件中的demo属性、demoMethod方法
this.$parent.demo;
this.$parent.demoMethod();

// 访问父组件的父组件的demo属性、demoMethod方法(没有provide/inject好用)
this.$parent.$parent.demo;
this.$parent.$parent.demoMethod();


// 访问根组件中的属性和方法
this.$root.demo;
this.$root.demoMethod();

可见如果层级太深就比较麻烦,如果是子组件访问父组件还好,反过来就麻烦了。这就需要到vue官方提供的vuex组件了,这个后面再说。