ref reactive computed define props define emits 的类型注解

在Vue3中,可以使用TypeScript对ref、reactive、computed以及定义组件属性和发射事件进行类型注解,以增强代码的类型安全。例如,为ref指定number类型的泛型,用接口定义reactive对象的结构,以及在defineProps和defineEmits中明确属性和事件类型。

在Vue 3中,你可以使用TypeScript来为refreactivecomputeddefinePropsdefineEmits等函数提供类型注解。下面是每个函数的类型注解示例:

ref

import { ref } from 'vue';
const count = ref<number>(0); // 注解为ref类型并指定泛型为number,<>位置在ref后边

reactive

import { reactive } from 'vue';
interface Person { name: string; age: number;}
const person = reactive<Person>({ name: 'John', age: 25,}); // 注解为reactive类型并指定泛型为Person接口类型

computed

import { ref, computed } from 'vue';
const count = ref<number>(0);
const double = computed<number>(() => count.value * 2); // 注解为computed类型并指定泛型为number
console.log(double.value);

defineProps 

import { defineProps } from 'vue';
interface Props {  message: string;  count: number;  }
export default {  props: defineProps<Props>(), // 注解为defineProps并指定泛型为Props接口类型
  // ...
};

defineEmits 

import { defineEmits } from 'vue';
export default {
  emits: defineEmits(['increment', 'decrement']), // 注解为defineEmits并指定可触发事件列表
  // ...
};

<template> <div :class="`${prefixCls}`"> <h5>Driving Time(second)</h5> <div :class="`${prefixCls}__wrap`"> <table :class="`${prefixCls}__table`"> <thead> <th class="col-fix col-fix-1" style="width: 130px"><span>Time</span></th> <th style="width: 180px" v-for="(stop, stopIndex) in props.planLineRouteStopList" :key="'column_' + stopIndex" >{{ stop.stopDesc }} </th> </thead> <tbody> <tr v-for="(group, groupIndex) in scheduleTimingGroupInfoList" :key="'group' + groupIndex" :class="`${prefixCls}__time-row`" > <td class="col-fix col-fix-1" style="width: 130px" ><span :style="selectedDrivingRow?.index == groupIndex ? 'color:#5757ff' : ''" >{{ group.newTime }} <el-icon class="add-icon ml-8px" @click="insertTimeGroup(group, groupIndex)"> <Plus /> </el-icon> <el-icon class="add-icon ml-2px" v-show="groupIndex != 0" @click="deleteTimeGroup(group, groupIndex)" > <Delete /> </el-icon> </span> </td> <td class="col-fix col-fix-2" v-for="(travel, travelIndex) in group.stopTravelTimeList" :key="'travel_' + groupIndex + travelIndex" ><span> <el-input class="no-padding-input" v-model.number="travel.travelTime" :ref="'travel_' + groupIndex + travelIndex" /> </span> </td> </tr> </tbody> </table> </div> <!-- 插入时间段 --> <el-dialog v-model="drivingTimeDialog" title="New Driving Time" width="25%" :before-close="colseDriveTimeDialog" > <span>Enter the time </span> <el-time-picker prefix-icon="el-icon-date" v-model="newDrivingTime" format="HH:mm:ss" value-format="HH:mm:ss" /> <template #footer> <div class="dialog-footer"> <el-button @click="colseDriveTimeDialog">Cancel</el-button> <el-button type="primary" @click="handleAddTime"> Confirm </el-button> </div> </template> </el-dialog> </div> </template> <script lang="ts" setup> import type { TabsInstance } from 'element-plus' import type { PropType } from 'vue' import { ScheduleInfoParams, ScheduleInfoObj } from '@/api/planning/schedule/type' import { Plus, Delete } from '@element-plus/icons-vue' defineOptions({ name: 'OperationDrivingTime' }) // Define types for props interface StopTravelTime { travelTime: number } interface ScheduleTimingGroup { startTimeStr?: string endTimeStr?: string stopTravelTimeList?: StopTravelTime[] } const props = defineProps({ planLineRouteStopList: { type: [Array, Object] as PropType<Array<any> | Record<string, any>>, required: true }, scheduleTimingGroupInfoList: { type: [Array, Object] as PropType<Array<ScheduleTimingGroup> | Record<string, any>>, required: true } }) const emit = defineEmits(['update:scheduleTimingGroup']) const { getPrefixCls } = useDesign() const { t } = useI18n() const scrollPosition = ref(0) const isScrollable = ref(false) // 控制滚动条显示状态 const maxScroll = ref(0) const scrollRef = ref(null) const message = useMessage() const prefixCls = getPrefixCls('operation-driving-time') const drivingTimeDialog = ref(false) // 时间段弹窗 const newDrivingTime = ref('') const selectedDrivingRow = ref({}) const oldHeadwayInfoList = ref([]) // 时间段列表 const scheduleTimingGroupInfoList = computed(() => { // props.scheduleTimingGroupInfoLis中第一个新增一个newTime值为Start Time ,最后一个值为End Time,其他值都是原始的group.startTimeStr const modifiedList = (props.scheduleTimingGroupInfoList || []).map((group, index) => { const isLast = index === (props.scheduleTimingGroupInfoList || []).length - 1 const isFirst = index === 0 if (isFirst) { return { ...group, newTime: 'Start Time' } } else if (isLast) { return { ...group, newTime: 'End Time' } } else { return { ...group, newTime: group.startTimeStr } } }) return modifiedList }, { deep: true }) const scheduleInfoObj = ref<ScheduleInfoObj>({ baseVersion: 0, lineAbbr: '', lineId: '', lineNo: 0, routeScheduleInfoList: [] }) // 新增时间 const handleAddTime = () => { // 类型安全校验与空值保护 if (!newDrivingTime.value || !selectedDrivingRow.value) { message.warning(t('pleaseSelectOnTime')) return } // console.log('newDrivingTime.value', selectedDrivingRow.value) const { startTimeStr, endTimeStr } = selectedDrivingRow.value.row if (newDrivingTime.value >= endTimeStr || newDrivingTime.value <= startTimeStr) { const waring = 'please select ' + startTimeStr + ' until ' + endTimeStr + ' Timing ' message.warning(waring) console.warn('Time range validation failed:', { inputTime: newDrivingTime.value, validRange: `${startTimeStr}-${endTimeStr}` }) return } try { const newGroup = { ...selectedDrivingRow.value.row, startTimeStr: newDrivingTime.value, stopTravelTimeList: (selectedDrivingRow.value.row?.stopTravelTimeList || []).map((travel) => ({ ...travel, travelTime: '' })) } const updatedList = [...scheduleTimingGroupInfoList.value] updatedList.splice(Number(selectedDrivingRow.value.index ?? 0 + 1), 0, newGroup) emit('update:scheduleTimingGroup', updatedList) colseDriveTimeDialog() return true } catch (error) { console.error('Time insertion error:', error) message.error(t('scheduleUpdateFailed')) return false } } // 添加行 const insertTimeGroup = (group, groupIndex) => { console.log('insertTimeGroup', group, groupIndex) if (group.startTimeStr === '24:00:00') { message.warning('peakSegmentNotAfterMidnight') return } selectedDrivingRow.value = { index: groupIndex, row: JSON.parse(JSON.stringify(group)) } drivingTimeDialog.value = true } // 删除当前行 const deleteTimeGroup = (group, groupIndex) => { const updatedList = scheduleTimingGroupInfoList.value.filter((_, index) => index !== groupIndex) emit('update:scheduleTimingGroup', updatedList) } // 关闭对话框 const colseDriveTimeDialog = () => { newDrivingTime.value = '' selectedDrivingRow.value = {} drivingTimeDialog.value = false } watch( () => props.scheduleTimingGroupInfoList, (newVal) => { if (newVal) { emit('update:scheduleTimingGroup', JSON.parse(JSON.stringify(newVal))) } }, { deep: true } ) // 添加输入变更处理 const handleTimeInput = (value, groupIndex) => { const updatedList = scheduleTimingGroupInfoList.value.map((group, index) => { if (index === groupIndex) { return { ...group, startTimeStr: value } } return group }) emit('update:scheduleTimingGroup', updatedList) } onMounted(() => {}) // 组件卸载清理 onUnmounted(() => {}) </script> <style scoped lang="scss"> $prefix-cls: #{$namespace}-operation-driving-time; .#{$prefix-cls} { width: 100%; font-size: 14px; h5 { color: rgba(17, 43, 92, 1); font-size: 18px; line-height: 40px; } &__wrap { max-height: calc(100vh - 550px); overflow: auto; // Webkit浏览器滚动条样式 &::-webkit-scrollbar { width: 8px; height: 8px; background-color: transparent; } &::-webkit-scrollbar-track { background-color: transparent; } &::-webkit-scrollbar-thumb { background-color: rgba(73, 86, 197, 0.3); border-radius: 4px; background-clip: content-box; border: 2px solid transparent; } &::-webkit-scrollbar-thumb:hover { background-color: rgba(73, 86, 197, 0.5); } // IE/Edge滚动条样式 -ms-overflow-style: auto; scrollbar-width: thin; scrollbar-color: rgba(73, 86, 197, 0.3) transparent; } &__table { width: 100%; border-collapse: collapse; table-layout: fixed; td, th { height: 35px !important; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; border: 1px solid #ddd; box-sizing: border-box; padding: 0; span { display: flex; height: 100%; width: 100%; align-items: center; justify-content: center; } } th { position: sticky; top: -1px; // 调整表头上面不显示滚动内容 background: rgba(73, 86, 197, 0.2) !important; font-weight: 500; z-index: 3; } th.col-fix { z-index: 4; } .col-fix { position: sticky; z-index: 2; border: none; background: #f4f4f4; } .col-fix-1 { left: 0; } .col-fix-2 { left: 120px; } td.col-fix, th.col-fix { span { padding: 0 4px; border-bottom: 1px solid #ddd; border-left: 1px solid #ddd; box-sizing: border-box; } } } &__time-row { &:hover .add-icon { opacity: 1; font-weight: 600; cursor: pointer; } .add-icon { opacity: 0; transition: opacity 0.2s ease; } } } </style> <style lang="scss"> $prefix-cls: #{$namespace}-operation-driving-time; .#{$prefix-cls} { .no-padding-input .el-input__inner { text-align: center !important; padding: 0px !important; border: none !important; font-size: 14px !important; } } 当前scheduleTimingGroupInfoList 的数据通过新增、修改input、删除来触发update:scheduleTimingGroup 使其父组件保存时获取最新数据
08-15
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值