父子组件通信

在这里插入图片描述

(1)props / emits

①props

Father.vue

<template>
  <Child
    title="用户信息"
    :index="1"
    :uid="userInfo.id"
    :user-name="userInfo.name"
  />
</template>

<script>
import { defineComponent } from 'vue'
import Child from '@cp/Child.vue'

interface Member {
  id: number,
  name: string
};

export default defineComponent({
  // 需要启用子组件作为模板
  components: {
    Child
  },

  // 定义一些数据并return给template用
  setup () {
    const userInfo: Member = {
      id: 1,
      name: 'Petter'
    }

    // 不要忘记return,否则template拿不到数据
    return {
      userInfo
    }
  }
})
</script>

Child.vue

export default defineComponent({
  props: [
    'title',
    'index',
    'userName',
    'uid'
  ]
})

加上类型限制:

export default defineComponent({
  props: {
    title: String,
    index: Number,
    userName: String,
    uid: Number
  }
})

在这里插入图片描述
如果你需要对某个 prop 允许多类型,比如这个 uid 字段,它可能是数值,也可能是字符串,那么可以在类型这里,使用一个数组,把允许的类型都加进去。

export default defineComponent({
  props: {
    // 单类型
    title: String,
    index: Number,
    userName: String,

    // 这里使用了多种类型
    uid: [ Number, String ]
  }
})

对部分字段设置为可选,并提供默认值:

export default defineComponent({
  props: {
    // 可选,并提供默认值
    title: {
      type: String,
      required: false,
      default: '默认标题'
    },

    // 默认可选,单类型
    index: Number,

    // 添加一些自定义校验
    userName: {
      type: String,

      // 在这里校验用户名必须至少3个字
      validator: v => v.length >= 3
    },

    // 默认可选,但允许多种类型
    uid: [ Number, String ]
  }
})

在这里插入图片描述

使用props

template 部分

<template>
  <p>标题:{{ title }}</p>
  <p>索引:{{ index }}</p>
  <p>用户id:{{ uid }}</p>
  <p>用户名:{{ userName }}</p>
</template>

script 部分

export default defineComponent({
  props: {
    title: String,
    index: Number,
    userName: String,
    uid: Number
  },

  // 在这里需要添加一个入参
  setup (props) {

    // 该入参包含了我们定义的所有props
    console.log(props);

  }
})
获取非 Prop 的 Attribute

在 Child.vue 里,可以通过 setup 的第二个参数 context 里的 attrs 来获取到这些属性。

export default defineComponent({
  setup (props, { attrs }) {
    // attrs 是个对象,每个 Attribute 都是它的 key
    console.log(attrs.class);

    // 如果传下来的 Attribute 带有短横线,需要通过这种方式获取
    console.log(attrs['data-hash']);
  }
})
emits(子传父)

Child.vue 绑定一个更新用户年龄的方法,那么在 Father.vue 里需要这么处理:

<template>
  <Child
    @update-age="updateAge"
  />
</template>
<script>
import { defineComponent, reactive } from 'vue'
import Child from '@cp/Child.vue'

interface Member {
  id: number,
  name: string,
  age: number
};

export default defineComponent({
  components: {
    Child
  },
  setup () {
    const userInfo: Member = reactive({
      id: 1,
      name: 'Petter',
      age: 0
    })

    // 定义一个更新年龄的方法
    const updateAge = (age: number): void => {
      userInfo.age = age;
    }

    return {
      userInfo,

      // return给template用
      updateAge
    }
  }
})
</script>

动态绑定 props 是用 :,绑定 emit 是用 @

Child.vue

export default defineComponent({
  emits: [
    'update-age'
  ]
})

接收 emits 时做一些校验
比如上面的更新年龄,只允许达到成年人的年龄才会去更新父组件的数据:

export default defineComponent({
  emits: {
    // 需要校验
    'update-age': (age: number) => {
      // 写一些条件拦截,记得返回false
      if ( age < 18 ) {
        console.log('未成年人不允许参与');
        return false;
      }

      // 通过则返回true
      return true;
    },

    // 一些无需校验的,设置为null即可
    'update-name': null
  }
})

调用 emits

export default defineComponent({
  emits: [
    'update-age'
  ],
  setup (props, { emit }) {
    
    // 2s 后更新年龄
    setTimeout( () => {
      emit('update-age', 22);
    }, 2000);

  }
})

(2)v-model / emits

  1. 在 Father.vue ,通过 v-model 向 Child.vue 传值
  2. Child.vue 通过自身设定的 emits 向 Father.vue 通知数据更新

Father.vue

<template>
  <Child
    v-model:user-name="userInfo.name"
  />
</template>

如果你要绑定多个数据,写多个 v-model 即可

<template>
  <Child
    v-model:user-name="userInfo.name"
    v-model:uid="userInfo.id"
  />
</template>

看到这里应该能明白了,一个 v-model 其实就是一个 prop,它支持的数据类型,和 prop 是一样的。

所以,子组件在接收数据的时候,完全按照 props 去定义就可以了。

export default defineComponent({
  props: [
    'title',
    'index',
    'userName',
    'uid'
  ]
})

Child.vue

虽然 v-model 的配置和 prop 相似,但是为什么出这么两个相似的东西?自然是为了简化一些开发上的操作。

使用 props / emits,如果要更新父组件的数据,还需要在父组件定义好方法,然后 return 给 template 去绑定事件给子组件,才能够更新。

而使用 v-model / emits ,无需如此,可以在 Child.vue 直接通过 “update:属性名” 的格式,直接定义一个更新事件:

export default defineComponent({
  props: {
    userName: String,
    uid: Number
  },
  emits: [
    'update:userName',
    'update:uid'
  ]
})

调用自身的 emits
在 Child.vue 配置好 emits 之后,就可以在 setup 里直接操作数据的更新了:

export default defineComponent({
  // ...
  setup (props, { emit }) {

    // 2s 后更新用户名
    setTimeout(() => {
      emit('update:userName', 'Tom')
    }, 2000);

  }
})

(3)ref / emits

在学习 响应式 API 之 ref 的时候,我们了解到 ref 是可以用在 DOM 元素与子组件 上面。

所以,父组件也可以直接通过对子组件绑定 ref 属性,然后通过 ref 变量去操作子组件的数据或者调用里面的方法。

比如导入了一个 Child.vue 作为子组件,需要在 template 处给子组件标签绑定 ref:

<template>
  <Child ref="child" />
</template>

然后在 script 部分定义好对应的变量名称(记得要 return 出来):

import { defineComponent, onMounted, ref } from 'vue'
import Child from '@cp/Child.vue'

export default defineComponent({
  components: {
    Child
  },
  setup () {
    // 给子组件定义一个ref变量
    const child = ref<HTMLElement>(null);

    // 请保证视图渲染完毕后再执行操作
    onMounted( () => {
      // 执行子组件里面的ajax函数
      child.value.getList();

      // 打开子组件里面的弹窗
      child.value.isShowDialog = true;
    });

    // 必须return出去才可以给到template使用
    return {
      child
    }
  }
})

子组件如果想主动向父组件通讯,也需要使用 emit

Logo

前往低代码交流专区

更多推荐