Vue-3 props,$emit,slot,render,JSX和createElement

Vue-3 props,$emit,slot,render,JSX和createElement

Props 和 $emit

使用 Vue 开发项目时,我们将项目中的内容按照模块划分,但是有时候模块和模块之间会存在数据交互。在真正的项目开发中,父子、兄弟组件之间需要互相传值。最传统的传值方式就是 props 和 $emit。

一、Props

Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。为了给博文组件传递一个标题,我们可以用一个 props 选项将其包含在该组件可接受的 prop 列表中。

1. props传值的示例


<template>
    <div id="app">
        
        <Home msg="hello world!" />
        <Home msg="tom" />
        <Home msg="lion king" />
    div>
template>

<script>
import Home from './components/Home'

export default {
    name: 'App',
    components: { Home },
    data() {
        return { }
    },
}
script>

<template>
    <div class="home">
        
        <h3>{{ msg }}h3>
    div>
template>

<script>
    export default {
        name: "Home",
        // 通过props接受外界在调用当前组建时,传入的参数
        props: ['msg']
    }
script>

也可以向子组件传不同的参数:


<Home msg="hello world!" />
<Home name="tom" />
<Home ani="lion king" />

<script>
    export default {
        name: "Home",
        // props接受多个参数
        props: ['msg','name','ani']
    }
script>

2. props类型验证
如果我们希望每个 prop 都有指定的值类型,并且以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型。

// ...
// 使用props接收数据并规定数据的类型
props: {
    title: String,
    likes: Number,
    isPublished: Boolean,
    commentIds: Array,
    author: Object,
    callback: Function,
    contactsPromise: Promise // or any other constructor
}
// ...

为了让传值变得更有灵活性,vue 提供了以下 props 的验证方式:

props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    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 ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }

如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。

注:

  • 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
  • 注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
  • 因为组件不能总是预见其他组件在调用当前组件时要传入的参数,所以 vue 也允许组件在被调用时传入一些非 props 特性。这些特性会自动添加到组件的根元素上。

二、$emit

一个子组件被一个父组件调用,当子组件触发某个函数时,需要父组件响应,就要使用到 $emit

1. $emit的简单示例


<template>
  <div class="child">
    <button @click="ev">子组件中的按钮button>
  div>
template>
<script>
  export default {
      name: 'Child',
      data() {
          return {
              msg: 'I am a data from child component'
          }
      },
      methods: {
          ev() {
              // 当前组件的按钮被点击时,向外抛出一个事件,谁调用当前组件,谁就在当前组件的按钮被点击时响应这个事件
              this.$emit('clickBtn');
          }
      }
  }
script>

<template>
  <div id="app">
    
    <Child v-on:clickBtn="evInFather" />
  div>
template>

<script>
  import Child from './components/Child'

  export default {
    name: 'App',
    components: {
        Child
    },
    data() {
      return {
        flag: true
      }
    },
    methods: {
       //当Child子组件中的 clickBtn 事件被emit时,evInFather 也会触发    
       evInFather() {
          console.log('father up');
       }
    }
  }
script>

我们也可以在抛出事件时,同时向外界抛出一些数据。

// Child的methods
// ...
methods: {
    ev() {
        // 向外抛出数据
        this.$emit('clickBtn', this.msg);
    }
}
// ...
// 父组件中的methods
// ...
methods: {
    evInFather(data) {
        console.log(data);
    }
}
// ...

如果组件在抛出事件时抛出的数据不止一个,可以以参数列表的形式继续向 $emit 传值,然后在父组件中再以参数列表的形式调用。但是在数据很多的情况下会让代码显得和冗长,所以我们建议将所有要抛出的数据整合在一个对象中,向 $emit 函数的第二个参数传入一个对象。

slot 插槽

插槽是组件被调用的第二种方式,更好的提高了组件的复用性。

子组件在调用父组件并向其传值时,更偏向于数据层的传递,但是有时候我们希望子组件在结构渲染层也能更好的响应其某个父组件,可以通过父组件的数据控制让子组件中的内容渲染。

如我们需要一个操作结果提示框,弹框中的标题,图片和按钮栏全部根据操作结果渲染具体结果。使用 props 或者 $emit 也能实现这样的效果,但是如果多种渲染结果的差异很大,那代码冗余就又上去了,所以 Vue 提供了一种更简洁的方式,就是插槽。

插槽相当于将组件封装成一个大的框架,至于具体显示什么,怎么显示,可以在调用时传数据,通过数据控制,也可以在调用时传入内容,显示传入的内容。

1. 插槽的小示例


<template>
    <div class="alert">
        <p>我是组件中本来就有的内容p>
        
        <slot>slot>
    div>
template>

<script>
    export default {
        name: "Alert"
    }
script>
<template>
  <div id="app">   
     <Alert>
        
        <p>
            hello,我是father调用时分发给子组件的内容
        p>
     Alert>
  div>
template>
<script>
    // ...
script>

注:
如果 Alert 组件中没有包含一个 元素,则该组件被调用时起始标签和结束标签之间的任何内容都会被抛弃。

2. 为组件设置插槽的后备内容

如果一个组件中预留了插槽,但是在组件被调用时并没有为其分发内容,可能会导致结构、逻辑出现一些无法预知的错误,所以有时候为插槽设置一个默认内容是很有必要的。


<div class="alert">
    <slot>
        
        <p>default messagep>
    slot>
div>

3. 具名插槽

要想封装一个具有高复用性的组件,一个插槽可能还不够,我们可以在组件结构的不同位置为其预留多个插槽,为每个插槽命名,在调用组建时,向不同的插槽分发对应的内容。这就是具名插槽。


<div class="alert">
    <div class="title">
        
        <slot name="title">温馨提示slot>
    div>
    <div class="content">
        
        <slot name="con">您确定要做这样的操作吗slot>
    div>
    <div class="btn">
        
        <slot name="btn">
            <button>确定button>
        slot>
    div>
div>

调用 Alert 组件:

<Alert>
    
    <template v-slot:title>
        提示
    template>
    <template v-slot:con>
        <p>这个操作很危险,是否要进行?p>
    template>
    <template v-slot:btn>
        <button>确定button>
        <button>取消button>
    template>
Alert>

具名插槽的简写:

<template #btn>template>

注:

  • 没有设置 name 属性的 ,默认 name 值是 default。
  • v-slot 属性只能添加在一个