vue3 父子组件通信

108 阅读4分钟

父给子传值props

props的基本使用

只用了JS

// 1. 使用字符串数组的形式
const props = defineProps(['arr'])
// 2. 使用对象的形式。
// 对于以对象形式声明中的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数
const props = defineProps({
  arr: {
    type: Array,
    default: () => [4, 5, 6]
  },
  str: {
    type: String,
    default: 'Hello'
  }
})
console.log(props.arr, '--', props.str)

如果使用了ts

// 1. 不带默认值
const props = defineProps<{
  arr: number[],
  str: string
}>()
// 2. 带默认值
const props = withDefaults(defineProps<{
  arr: number[],
  str: string
}>(), {
  arr: () => [3, 2, 1]
})
console.log(props.arr, '--', props.str)

props细节

1. 传递静态数据

// 字符串
<CompA str="hello">
// 传递数值
<CompA :count="2">
// boolean值
<CompA :is-new="false"> // 子组件接收到isNew为false
<CompA is-new>  // 子组件接收到isNew为true
// 数组
<CompA :list="[2, 3, 4]">
// 对象
<CompA :count="{
    name: 'Li',
    sex: 'male'
}">

2. 使用一个对象绑定多个prop

const p = {
    name: 'Li',
    sex: 'male'
}
<CompA v-bind="p" str="hello"/>
<!-- 等价于 -->
<CompA :name="p.name" :sex="p.sex" str="hello">

注意:在这个例子中,p被定义为普通数据(非响应式对象),修改p的值,并不会影响子组件的props.nameprops.sex.如果希望修改p的值时,子组件的输入也随之改变,可以使用reactivep变为响应式。

单向数据流

props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。不应该\color{#f00}{不应该}在子组件中去更改一个 prop。如果这么做了,Vue 会在控制台上向抛出警告。 修改props的场景有以下两种

  1. prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter']) // 计数器只是将 

props.initialCounter 作为初始值 // 像下面这样做就使 prop 和后续更新无关了 
const counter = ref(props.initialCounter)
  1. 需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
const props = defineProps(['size'])

// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())

需要对props做进一步转换的时候,不能解构props的值后再赋值,因为解构后失去了与父组件的联系

const { size } = props
// 父组件的size变化时,子组件的normalizedSize不会变
const normalizedSize = computed(() => size.trim().toLowerCase())

prop校验

defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // Number 类型的默认值
  propD: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propE: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    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'
    }
  }
})

运行时类型检测

校验选项中的 type 可以是下列这些原生构造函数:

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

另外,type 也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配。例如下面这个类:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}
defineProps({
  author: Person
})

注意\color{red}{注意}

  • 传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的作用域。因此,传入的选项不能引用在 setup 作用域中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。

以下例子可以说明:

const b = [9, 9, 9]
const props = defineProps({
  localArr: {
    type: Array,
    default() {
      return b;
    }
  }
})

会出现编译错误:

image.png

如果换成以下写法,则不会报错:

var.js文件内容

export const a = [0, 0 ,0]
import { a } from '../assets/js/var.js'
const props = defineProps({
  arr: {
    type: Array,
    default: () => a
  }
})

子给父传值defineEmits

子组件:

<button @click="onClick">子向父传值</button>
 const emit = defineEmits(['on-click'])
 const onClick = () => {
  emit('on-click', '子组件参数1', 2)
}

父组件:

<ChildComp str="3" v-bind="p" @on-click="getMessage"/>

const getMessage = (name: string, count: number) => {
  console.log('从子组件接收到的值:', name, '--count:', count)
}

如果用的ts,子组件的外抛事件可定义为如下:

const emit = defineEmits<{
  (e: 'on-click', name: string, count: number): void
}>()
// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
  'on-click': [name: string, count: number]
}>()
const onClick = () => {
  emit('on-click', '子组件参数1', 2)
}

父组件访问子组件属性

子组件CompA:

const a = 1
const b = ref(1)
defineExpose({
  a,
  b
})

父组件:

  <CompA ref="compAinstance"></CompA>
<script setup lang="ts">
    import { ref, onMounted } from 'vue'

    const compAinstance = ref<InstanceType<typeof CompA>>()
    onMounted(() => {
      console.log('compAinstance.a:', compAinstance.value?.a) // 1
      console.log('compAinstance.b:', compAinstance.value?.b) // 1,ref自动解包

    })
})