Skip to content

组件传值

1.defineProps()

Vue3中父组件向子组件传值扔仍和Vue2一样,如下所示

vue
<Child :num="123" name="坤坤"></Child>

但子组件接受的方式不一样

在Vue3中需要使用defineProps(),其传入的参数与选项式Api的props一致

defineProps 是只能在 <script setup> 中使用的编译器宏。他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。

1.1 props校验

1.1.1 数组

defineProps()可接收一个字符串类型的数组,数组中的字符串代表父组件穿来的数据

js
const props=defineProps(['num','name'])

1.1.2 对象-简单

defineProps()可接收对象,对象中每一个键对应父组件传来的数据,其值可以是一个类型

js
const props=defineProps({
    num:Number,
    name:String
})

如果穿来的值可能有多个类型,可以用一个数组存放这些类型

js
const props=defineProps({
    id: [Number,String]
})

1.1.2 对象-复杂

defineProps()接受一个对象,其中每一个键也可以是一个对象,可以更严格进行限制,对象中包含以下键

  • type:接受数据的类型,可以是一个类型,也可以是一个类型数组
  • default:默认值,可以是一个字面量,也可以是一个函数返回一个字面量
  • requireBoolean,限制是否必传
  • validator:校验函数,返回一个布尔值

详细对象形式-可限制类型、必要性、默认值

js
const props=defineProps({
    num:{
        type:Number,
        default:0
    },
    name:{
        type:String,
        require:true
    }
})

详细对象形式还可以校验

js
const props = defineProps({
    num:{
        validator(value, props) {
            return value > 60
        }
    },
})

validator()参数

  • value:父组件传过来的值
  • props:当前defineProps()返回的对象

1.2 props解构

上述写法我们使用props中的值需要使用props.键使用

通过解构可以方便使用

js
const {num, name} = defineProps(['num','name'])

1.3 传递props细节

使用一个对象可以绑定多个 prop

如果你想要将一个对象的所有属性都当作 props 传入,你可以使用没有参数的 v-bind,即只使用 v-bind 而非 :prop-name。例如,这里有一个 post 对象:

js
const post = {
  id: 1,
  title: 'My Journey with Vue'
}

以及下面的模板:

vue
<BlogPost v-bind="post" />

而这实际上等价于:

vue
<BlogPost :id="post.id" :title="post.title" />

1.4 Boolean类型转换

defineProps()中接受一个布尔类型的数据时

js
defineProps({
  disabled: Boolean
})

在父组件中只需这样传

vue
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />

<!-- 等同于传入 :disabled="false" -->
<MyComponent />

当一个 prop 被声明为允许多种类型时,Boolean 的转换规则也将被应用。然而,当同时允许 StringBoolean 时,有一种边缘情况——只有当 Boolean 出现在 String 之前时,Boolean 转换规则才适用:

js
// disabled 将被转换为 true
defineProps({
  disabled: [Boolean, Number]
})

// disabled 将被转换为 true
defineProps({
  disabled: [Boolean, String]
})

// disabled 将被转换为 true
defineProps({
  disabled: [Number, Boolean]
})

// disabled 将被解析为空字符串 (disabled="")
defineProps({
  disabled: [String, Boolean]
})

2.defineEmits()

defineEmits()用于组件的自定义事件,defineEmits()也不需要导入就可以直接使用

defineEmits()接受一个数组,数组中包含多个自定义事件名的字符串

:::code-goup

vue
<template>
  <Child @emitEvent="add"></Child>
</template>

<script setup>
const add=(num,num1)=>{
	console.log(num+num1)
}
</script>
vue
<script setup>
   const emit = defineEmits(['emitEvent'])
   emit('emitEvent',11,22)
</script>

:::

在模版中可以直接使用$emit调用父组件中的事件,而且不需要在js中使用defineEmits()

vue
<button @click="$emit('add')">add</button>

3.defineExpose()

当父组件通过ref获取子组件实例时,默认子组件中的数据时获取不到的,需要使用defineExpose()将数据暴露出去

defineExpose()接受一个对象,对象中包含需要暴露出去的数据

vue
<template>
  {{ num }}
</template>

<script setup>
import { ref } from 'vue'
    
const num = ref(666)
const add = () => {
  console.log(num.value * 100)
}

defineExpose({ 
    num,
    add
})
</script>
vue
<template>
  <div>
    <Child ref="child"></Child>
    <button @click="show">click</button>
  </div>
</template>

<script setup>
import Child from './views/Child.vue'
import { ref } from 'vue'

const child = ref(null)

const show = () => {
  child.value.num = 777
  child.value.add()
}
</script>

4.defineModel()

defineModel()在3.4+引入,在v-model绑定组件时使用

defineModel()可获取v-model传过来的值,并不需要导入就可以使用

defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:

vue
<template>
  <div>
    <Child v-model="num"></Child>
  </div>
</template>

<script setup>
import Child from './views/Child.vue'
import { ref } from 'vue'

const num = ref(123)
</script>
vue
<template>
  <div>
    {{ num }}
    <button @click="add">add</button>
  </div>
</template>

<script setup>
import { defineModel } from 'vue'

const num = defineModel()

const add = () => {
  num.value++
}
</script>

4.1 底层机制

defineModel 是一个便利宏。编译器将其展开为以下内容:

  • 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
  • 一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。

在 3.4 版本之前,你一般会按照如下的方式来实现上述相同的子组件:

vue
<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

然后,父组件中的 v-model="foo" 将被编译为:

vue
<!-- Parent.vue -->
<Child
  :modelValue="foo"
  @update:modelValue="$event => (foo = $event)"
/>

4.2 v-model参数

组件上的 v-model 也可以接受一个参数:

vue
<MyComponent v-model:title="bookTitle" />

在子组件中,我们可以通过将字符串作为第一个参数传递给 defineModel() 来支持相应的参数:

vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>

<template>
  <input type="text" v-model="title" />
</template>

4.3 多个v-model

利用刚才在 v-model 的参数小节中学到的指定参数与事件名的技巧,我们可以在单个组件实例上创建多个 v-model 双向绑定。

组件上的每一个 v-model 都会同步不同的 prop,而无需额外的选项:

vue
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

而在3.4以下的版本需要使用以下写法

vue
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

4.4 修饰符

通过如下方式获取v-model修饰符

vue
<Child v-model.trim="num"></Child>
vue
<script setup>
const [num, opt] = defineModel()

console.log(opt)  // {trim: true}
</script>

当然也可以自定义修饰符

vue
<Child v-model.abcd="num"></Child>
vue
<script setup>
const [num, opt] = defineModel()

console.log(opt)  // {abcd: true}
</script>

4.5 转换器

当存在修饰符时,我们可能需要在读取或将其同步回父组件时对其值进行转换。我们可以通过使用 getset 转换器选项来实现这一点:

js
const [modelValue, modelModifiers] = defineModel({
  // get() 省略了,因为这里不需要它
  set(value) {
    // 如果使用了 .trim 修饰符,则返回裁剪过后的值
    if (modelModifiers.trim) {
      return value.trim()
    }
    // 否则,原样返回
    return value
  }
})

4.6 带参数的修饰符

这里是另一个例子,展示了如何在使用多个不同参数的 v-model 时使用修饰符:

vue
<UserName
  v-model:first-name.capitalize="first"
  v-model:last-name.uppercase="last"
/>
vue
<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')

console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true }
</script>

3.4 之前的用法

vue
<script setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: { default: () => ({}) },
lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])

console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true }
</script>

useSlots()

useSlots()可以在script setup中获取组件的插槽数据

但是插槽数据可以在模板中直接访问$slots来获取

vue
<script setup>
import { useSlots } from 'vue'

const slots = useSlots()
</script>

useAttrs()

useAttrs()可以在script setup中获取父组件传来但没被props接受的数据

但是插槽数据可以在模板中直接访问$attrs来获取

vue
<script setup>
import { useAttrs } from 'vue'

const att = useAttrs()
</script>

MIT Licensed