vue3的深入组件-Props

vue3的深入组件-Props
Props 声明

一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props
在使用 <script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明:

  • 基本声明
<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>

在没有使用 <script setup> 的组件中,props 可以使用 props 选项来声明:

export default {
  props: ['foo'],
  setup(props) {
    // setup() 接收 props 作为第一个参数
    console.log(props.foo)
  }
}
  • 声明类型
<template>
  <div>
    props
  </div>
</template>

<script setup lang='ts'>
import { defineProps } from 'vue'
// const props = defineProps(['foo','title'])
const props = defineProps({
  foo: { type: String,},
  bar: { type: Number, default: 100 },
  title: { type: String }, 
})
console.log(props.foo,props.bar,props.title)
</script>

  • 声明类型、默认值和校验
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    msg: {
      type: String,
      required: true,
      default: 'Hello World'
    },
    count: {
      type: Number,
      default: 0
    }
  }
});
响应式 Props 解构

Vue 的响应系统基于属性访问跟踪状态的使用情况。例如,在计算属性或侦听器中访问 props.foo 时,foo 属性将被跟踪为依赖项
因此,在以下代码的情况下:

const { foo } = defineProps(['foo'])

  watchEffect(() => {
    // 在 3.5 之前只运行一次
    // 在 3.5+ 中在 "foo" prop 变化时重新执行
    console.log(foo)
  })

在 3.4 及以下版本,foo 是一个实际的常量,永远不会改变。在 3.5 及以上版本,当在同一个 <script setup> 代码块中访问由 defineProps 解构的变量时,Vue 编译器会自动在前面添加 props.。因此,上面的代码等同于以下代码:

const props = defineProps(['foo'])

watchEffect(() => {
  // `foo` 由编译器转换为 `props.foo`
  console.log(props.foo)
})
  • 使用 toRefs 解构
<script setup lang='ts'>
import { defineProps,watchEffect } from 'vue'
const props = defineProps(['foo','title'])

const {foo,title} = toRefs(props)
console.log(foo.value, title.value);
</script>
传递 prop 的细节
  • 命名规则
    • 驼峰命名法:在 JavaScript 中使用驼峰命名法,在模板中可以使用 kebab-case 或驼峰命名法。
    • kebab-case:在模板中使用 kebab-case
单向数据流

所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。

另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告

const props = defineProps(['foo'])

// ❌ 警告!prop 是只读的!
props.foo = 'bar'

导致你想要更改一个 prop 的需求通常来源于以下两种场景:

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

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

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

子组件不应直接修改 props 的值,而应通过事件通知父组件进行修改

<template>
  <div>props{{ props.foo }}</div>
  <button @click="changeValue">更改数据</button> 
</template>

<script setup lang='ts'>
import { defineProps,watchEffect } from 'vue'

const props = defineProps(['foo','title'])
const emit = defineEmits(['changeValue'])
const changeValue = () => {
    emit('changeValue','bar')

}
</script>


Prop 校验

Vue 组件可以更细致地声明对传入的 props 的校验要求。比如我们上面已经看到过的类型声明,如果传入的值不满足类型要求,Vue 会在浏览器控制台中抛出警告来提醒使用者。这在开发给其他开发者使用的组件时非常有用。

要声明对 props 的校验,你可以向 defineProps() 宏提供一个带有 props 校验选项的对象,例如:

defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // 必传但可为 null 的字符串
  propD: {
    type: [String, null],
    required: true
  },
  // Number 类型的默认值
  propE: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propF: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // 自定义类型校验函数
  // 在 3.4+ 中完整的 props 作为第二个参数传入
  propG: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propH: {
    type: Function,
    // 不像对象或数组的默认,这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})
  • 所有 prop 默认都是可选的,除非声明了 required: true。

  • 除 Boolean 外的未传递的可选 prop 将会有一个默认值 undefined。

  • Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改——例如:设置为 default: undefined 将与非布尔类型的 prop 的行为保持一致。

  • 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。

Boolean 类型转换

为了更贴近原生 boolean attributes 的行为,声明为 Boolean 类型的 props 有特别的类型转换规则。以带有如下声明的 <propsTest> 组件为例:

<script setup lang='ts'>
import { defineProps,watchEffect } from 'vue'
// const props = defineProps(['foo','title','disabled'])
const props = defineProps({
  disabled: Boolean
})
const emit = defineEmits(['changeValue'])
const changeValue = () => {
    emit('changeValue','bar')

}
console.log(props.disabled)

</script>

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

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值