基本使用
// 使用 <script setup>
defineProps({
  title: String,
  likes: Number
})


// 非 <script setup>
export default {
  props: {
    title: String,
    likes: Number
  }
}
直接使用defineProps

defineProps() 宏函数支持从它的参数中推到类型:

<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})
 
props.foo // string
props.bar // number / undefined
</script>

通过泛型参数来定义 props 的类型:

<script setup lang="ts">
const props = defineProps<{
  foo: string
  bar?: number
}>()
</script>
 
/* or
<sctipt setup lang="ts">
interface Props {
  foo: string
  bar?: number
}
const props = defineProps<Props>()
</script>
*/
针对类型的 props声明
// 2.defineProps<泛型参数>()的方式    没办法做默认赋值
type propsType = {
  options: {
    label: string
    value: string | number
  }[]
}
const props = defineProps<propsType>()

这被称为 基于类型的声明,编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。这种方式的不足之处在于,失去了定义 props 默认值的能力。为了解决这个问题,我们可以使用 withDefaults 编译器:

withDefaults
// 3.withDefaults  编译器宏 泛型加默认值
type propsType = {
  options: {
    label: string
    value: string | number
  }[]
}
const props = withDefaults(defineProps<propsType>(), {
  options: () => [
    {
      label: '男',
      value: 1
    }
  ]
})
PropType
// PropsType需要提前引入
import { type PropType } from 'vue';

// 忽略上述一大段的类型声明

defineProps({
  data: {
    type: [String, Number],
    default: '--',
    validaor(value: string) {
      if(value === '暂无数据') {
        return false
      }
    }
  },
  position: {
    type: Object as PropType<Position>,
    required: false,
    deault: {
      left: 0,
      top: 0,
      zIndex: 0,
    }
  },
  options: {
    type: Object as PropType<DataNumberOptionsType>,
    require: true,
    default: {
      animate: false,
      thousandsCharacter: false,
      direction: "horizontal",
      label: "待入馆",
      suffix: "人",
    }
  }
});

这样既能利用TS的类型声明,又能完整的使用Props的配置功能。

但仍然不完美,类型声明代码太多了,能不能抽取到独立文件,从外部文件引入呢?

Vue官方文档给出了解释:“因为 Vue 组件是单独编译的,编译器目前不会抓取导入的文件以分析源类型。计划在未来的版本中解决这个限制。”

PropType+ExtractPropTypes

我们先把类型声明放到DataNumber.d.ts文件,然后通过ExtractPropTypes进行导出

// ExtractPropTypes需要提前引入
import { ExtractPropTypes } from 'vue';

enum DirectionEnum {
  horizontal = 'HORIZONTAL',
  vertical = 'VERTICAL',
}

type Position = {
  top: string | number;
  left: string | number;
  zIndex: number;
  width?: string | number;
  height?: string | number;
};

type DataNumberOptionsType = {
  animate?: boolean;
  thousandsCharacter?: boolean;
  direction: DirectionEnum;
  label: string;
  suffix: string;
};

// export暴露出去
export type Position = ExtractPropTypes<typeof Position>;
export type DataNumberOptionsType = ExtractPropTypes<
  typeof DataNumberOptionsType
>;
<script setup lang="ts">
import { type PropType } from 'vue';
// 使用的时候,从类型文件引入
import { Position, DataNumberOptionsType } from './DataNumber.d'

defineProps({
  data: {
    type: [String, Number],
    default: '--',
    validaor(value: string) {
      if (value === '暂无数据') {
        return false
      }
    }
  },
  position: {
    type: Object as PropType<Position>,
    required: false,
    deault: {
      left: 0,
      top: 0,
      zIndex: 0,
    }
  },
  options: {
    type: Object as PropType<DataNumberOptionsType>,
    require: true,
    default: {
      animate: false,
      thousandsCharacter: false,
      direction: "horizontal",
      label: "待入馆",
      suffix: "人",
    }
  }
});
</script>

特点:单向数据流

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

const props = defineProps(['foo'])

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

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

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

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

// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.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' }
    }
  },
  // 自定义类型校验函数
  // 在 3.4+ 中完整的 props 作为第二个参数传入
  propF: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propG: {
    type: Function,
    // 不像对象或数组的默认,这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})
Vue3+Ts Props类型自定义
要点
  • 像Vue2一样的那些定义自然还是OK的,比如Number、String等等
  • 因为有Ts在,Vue3可以有更方便、快捷的复杂类型定义

简单用例
比如说现在需要定义一个string[]的prop,那么就像下面这样写

defineProps({
  acb: Array as () => string[]
})

同样地,如果想定义一个类似{name: string, age: number}这样的,可以如下这样写

defineProps({
  abc: Object as () => ({name: string, age: number})
})

当然,你也可以把类型分离出去,像这样

type TAbc = {
  name: string
  age: number
}

defineProps({
    abc: Object as () => TAbc
})

函数自然也是可以的,可以像这样

import {PropType} from "vue";

type TFunction = (args?: string[]) => string

defineProps({
    abc: Function as PropType<TFunction>
})

这里使用了vue提供的PropType,前面提到的那几个定义使用PropType来实现当然也是可以的

非 <script setup>

如果没有使用 <script setup>,那么为了开启 props 的类型推导,必须使用 defineComponent() 。传入 setup() 的 props 对象类型是从 props 选项中推导而来。

import { defineComponent } from 'vue'
 
export default defineComponent({
  props: {
    message: String
  },
  setup(props){
    props.message // 类型:string
  }
})

若没有使用 <script setup>,defineComponent() 也可以根据 emits 选项推导暴露在 setup 上下文中的 emit 函数的类型:

import { defineComponent } from 'vue'
 
export default definComponent({
  emits: ['change'],
  setup(props, { emit }) {
    emit('change') //类型检查 / 自动补全
  }
})

为 ref() 标注类型

默认推导类型

ref 会根据初始化时的值自动推导其类型:

import { ref } from 'vue'
 
// 推导出的类型:Ref<number>
 
const year = ref(2022)
 
// TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2022'

通过接口指定类型

有时我们可能想为 ref 内的值指定一个更复杂的类型,可以通过使用 Ref 这个接口:

import { ref } from 'vue'
import type { Ref } from 'vue'
 
const year: Ref<string | number> = ref(2022)
year.value = 2022 //成功
Logo

前往低代码交流专区

更多推荐