Vue3 中 使用 TypeScript

2023-11-05 23:44:32 浏览数 (1)

单文件用法

在单文件组件中使用 TypeScript,需要在 <script> 标签上加上 lang="ts" 的 attribute。当 lang="ts" 存在时,所有的模板内表达式都将享受到更严格的类型检查

小结:

代码语言:txt复制
<script lang="ts">

</script>


<script setup lang="ts">

</script>

注意

当 script 中使用了 ts ,模板 template 在绑定表达式时也支持ts。

如果在表达式中不指名类型时,编译器会报警告提示。

正确写法

表达式指定类型

组合式API TS

Props 标注 类型

基于运行时声明

当使用 <script setup> 时,defineProps() 宏函数支持从它的参数中推导类型

代码语言:txt复制
<script setup lang="ts">
  const props = defineProps({
    name: String,
    data:{
      type: Object,
      required: true
    }
  })
    </script>

基于类型声明

通过泛型来定义 Props

代码语言:txt复制
<script setup lang="ts">
interface MyProps {
    phone: string | number,
    name ?: string,
    age : number | string
    hobby: {
        type: string,
        required: true
    }
}


const props = defineProps<MyProps>()
</script>
语法规定

传递给 defineProps 的泛型参数必须是以下格式:

代码语言:txt复制
defineProps<{ /*... */ }>()     一个对象字面量

同一个文件中的一个接口或对象类型字面量的引用:

代码语言:txt复制
interface Props {/* ... */}

defineProps<Props>()

Props 默认值

当使用了 基于类型声明时,就失去了 默认值 能力。 Vue 提供了一个 Api 可以解决此问题, withDefaults 编译器哄解决。

withDefaults 可以提供默认值的类型检查

先来复习一下 基于运行时怎么 声明默认值 Props

代码语言:txt复制
const props = defineProps({
    name: {
      type: String,
      default: () => '默认值'  
    },
    data:{
        type: Object,
        required: true
    }
})

基于运行时 Props 默认值写法:

代码语言:txt复制
interface MyProps {
    phone: string | number,
    name ?: string,
    age : number | string
    hobby: {
        type: string,
        required: true
    }
}

const props =  withDefaults(defineProps<MyProps>(),{
    name:'海军',
    phone: '123123123123'
})  

没有使用 <script setup> 情况 写法

当没有使用 <script setup> 时,想要类型检查,那么必须使用 defineComponent() 传入setup() 的 props 对象类型是从 props 选项中推导而来

代码语言:txt复制
import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    message: String
  },
  setup(props) {
    props.message // <-- 类型:string
  }
})

注意

  1. 传递给 defineProps 的参数本身不能是一个导入类型, 只能是当前文件下的一个对象或者interface
代码语言:txt复制
因为 Vue 组件是单独编译的,编译器目前不会抓取导入的文件以分析源类型。我们计划在未来的版本中解决这个限制。
  1. 基于运行时声明 和 基于类型声明 不能混着用

Emits 类型标注

在 < script setup> 写法

在 < script setup> 中 , 给emit 函数 类型标注 可以通过两种形式来标注

  1. 运行时声明
  2. 类型声明

运行时声明写法

代码语言:txt复制
//运行时写法
const emit = defineEmits(['getData'])

  emit('getData', {
      code:200,
      msg: "传入数据成功",
      str:"我是子组件过来的数据"
  })

类型声明写法

代码语言:txt复制
const emit = defineEmits<{
    (e: 'getId', id: number): void
}>()


 emit('getId',2)

基于类型的声明使我们可以对所触发事件的类型进行更细粒度的控制。



在 defineComponent 中写法

defineComponent() 也可以根据 emits 选项推导暴露在 setup 上下文中的 emit 函数的类型:

代码语言:txt复制
import { defineComponent } from 'vue'

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

ref 标注类型

有时我们想给 ref 指定一个 复杂类型时, 可以通过 Ref 类型 声明 或者 调用 ref 时 传入一个泛型参数 来覆盖默认推倒行为。

Ref 声明

代码语言:txt复制
import type {Ref} from "vue"

const studentId: Ref< String | Number> = ref(231)

泛型声明

调用ref 时,传入一个泛型参数,来覆盖默认的推倒行为

代码语言:txt复制
const teacherId = ref<String | Number>('4')

注意

  • 如果泛型参数没有给定初始值,那么会得到一个 undefined 的联合类型

reactive 标注类型

通过 定义一个接口 来做类型约束

代码语言:txt复制
interface StudentInfoFormat {
    name: string,
    id: number,
    age: number,
    hobby ?: string
}




const info : StudentInfoFormat = reactive({name:'海军',id:2})

const getStudentInfo = (data : StudentInfoFormat) => {
    console.log(data)
}

getStudentInfo(info)

这样就有很好的类型提示。

上面定义来 一个 可选属性 hobby,在初始化时,没有传递 age属性, 下面提示了 缺少 age 。

computed 标注类型

computed() 会自动从其计算函数的返回值上推导出类型

代码语言:txt复制
import { ref, computed } from 'vue'

const count = ref(0)

// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)

// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')

泛型参数指定返回类型

若返回的参数不是指定的参数类型则会报错

代码语言:txt复制
computed<T>(() => {})
代码语言:txt复制
const num = computed<number>(() => {
    return 99 * 44
})

事件函数标注类型

在处理原生 DOM 事件时,应该为我们传递给事件处理函数的参数正确地标注类型。

代码语言:txt复制
 <input type="text" @change="change">
代码语言:txt复制
const change = (e : Event) => {
    
    console.log((e.target as HTMLInputElement).value)
}

当 参数 e 没有标注类型时, 它的类型为 any 。 如果在tsconfig.json 中配置了 "strict": true 或 "noImplicitAny": true 时报出一个 TS 错误。

我们可以显式强制转化 event 属性 , 让浏览器更好的知道类型。

Provide / inject 标注类型

在组件传值时,有时组件嵌套太深时,组件通信就变的麻烦起来了。 如果子子孙组件需要获取顶级组件状态时,那么它可以 通过 Vuex / Pinia / 事件总线 来通信,不过,不建议这样做。一般用 Vuex / Pinia 一般存储一些全局的状态时使用,这里用就小题大做了。 我们可以通过在顶级组件 Provide 提供需要的值,然后在它所嵌套的组件中注入需要的值即可,这样状态也好管理。

在Vue3 中,如果我们要给 提供的值 标注类型,可以借助这个 接口 来实 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型。

单独封装key

单独封装key 的好处,这样我们在别的组件也可以使用该key 做为类型标注。

代码语言:txt复制
//provideKey.ts

import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'


export  const key = Symbol() as InjectionKey<string>

组件中 Provide

在顶级组件中提供 key , 供下级组件使用key

代码语言:txt复制
import {key} from "../../common/provideKey"
import {provide} from "vue"

provide(key,'标注类型')

如果没有key 的value 类型不是指定的类型,则会报警告提示

下级组件 inject

下级组件注入 key

代码语言:txt复制
import { inject } from "vue"


const key = inject('token')

模板ref 标注类型

有时,我们需要通过原生DOM 做一些操作,就需要获取到原生DOM.

下面是 获取表单元素,进入该组件时,自动聚焦。

代码语言:txt复制
<el-input v-model="str" placeholder="" size="normal" clearable @change="" ref="formInputRef"></el-input>
代码语言:txt复制
const str = ref("测试")

const formInputRef = ref<HTMLInputElement | null>(null)


onMounted(() => {
    formInputRef.value?.focus()
})
   

模板引用需要通过一个显式指定的泛型参数和一个初始值 null 来创建

获取子组件 类型

有时候,我们需要直接操作子组件来获取它的状态和方法。 在Vue2.x 中,我们可以直接在子组件中绑定ref,然后通过 this.$refs.绑定的ref 就可以使用了。

在 Vue 3中,我们也是如此。但是在组合式API 中,调用的时候,不用this了,通过 ref.value 来操作。

想要给给子组件标注类型时:

我们就需要先通过 typeof 来 获取组件的类型,然后通过TypeScript 内置的InstanceType 工具类型来获取其实例类型,就可以操作子组件了。

代码语言:txt复制
  <ts-component   ref="tsRef" ></ts-component>

一般不标准类型写法:

代码语言:txt复制
<script setup lang="ts">
  import TsComponent from '../TsComponent/index.vue'
	const tsRef = ref(null)


  tsRef.value?.alerTest('测试')    //调用子组件方法
</script>

标注类型写法:

代码语言:txt复制
<script setup lang="ts">
  import TsComponent from '../TsComponent/index.vue'

	const tsRef = ref<InstanceType<typeof TsComponent> | null>(null)

  tsRef.value?.alerTest('测试')    //调用子组件方法
</script>

选项式API TS

在Vue3 中 选项式 API 想要做类型推倒,得使用 defineComponent() 来包装组件。

Props 标注类型

需要使用 defineComponent() 包装组件包裹起来,

简单类型

只做简单类型推倒

代码语言:txt复制
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
    props:{
      code:{
        type:Number,
        default:22,
        required:true,
      },
      content:{
        type:String,
        default: () => "测试"
      }  
    },

})

</script>

复杂类型

有时,我们需要对Props 的属性要求是复杂类型或者多层级进行类型要求,按一般的写法是实现不了的,可以通过 PropType 这个工具类型来标记更复杂的 props 类型


代码语言:txt复制
import { defineComponent } from 'vue'
import type { PropType } from 'vue'

interface InfoFormat {
    code: {
        type: number,
        required: true
    }
    msg:{
        type: string
    }
}


export default defineComponent({
    props:{ 
      info: {
        type:  Object as PropType<InfoFormat>,
      }
    },
})
注意

如果你的 TypeScript 版本低于 4.7,在使用函数作为 prop 的 validator 和 default 选项值时需要格外小心——确保使用箭头函数

emits 标注类型

可以给 emits 选项提供一个对象来声明组件所触发的事件,以及这些事件所期望的参数类型。试图触发未声明的事件会抛出一个类型错误

事件加参数类型验证

代码语言:txt复制
    emits:{
        getData(ctx:{name:string,age:number}) {
            // 内部可以加 一些验证
            // return
            return 
        }
    },


//触发事件
this.$emit('getData',{name:'海军',age:22})

如果我们给emit 事件加了参数类型验证,当触发事件时,没有传递参数或者参数类型错误 都会警告提示。

计算属性 标注类型

计算属性会自动根据其返回值来推导其类型。

在某些场景,我们需要显示的标记出 计算属性的类型。因为在某些 TypeScript 因循环引用而无法推导类型的情况下,可能必须进行显式的类型标注。

代码语言:txt复制
  computed:{
      filterData(): Array<{code:number,msg:string}> {
          if(this.arrList.length == 0) {
              return []
          } else{
              return this.arrList.filter(i => i.code == 200)
          }
      }
  },

事件处理函数 标注类型

代码语言:txt复制
import { defineComponent } from 'vue'

export default defineComponent({
  methods: {
    handleChange(event: Event) {
      console.log((event.target as HTMLInputElement).value)
    }
  }
})

0 人点赞