Web前端笔记-18、TS-类型注解2-泛型、ref、reactive、computed、e:Event、模板引用、组件的props、组件的emits、axios)、类型声明文件、共享TS类型

泛型(重要)

概念:泛型(Generics)是指在定义接口、函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,使用泛型可以复用类型并且让类型更加灵活。

// 泛型
interface User {
  name: string
  age: number
}
interface UserData {
  code: number
  msg: string
  data: User
}
/////////////////////////////////////////
interface Good {
  id: number
  goodsName: string
}
interface GoodsData {
  code: number
  msg: string
  data: Good
}

泛型接口

语法:在接口类型的名称后面使用<T>即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型

通用思路:

  1. 找到可变的类型部分通过泛型<T>抽象为泛型参数(定义参数),公共部分照常写。
  2. 在使用泛型的时候,把具体类型传入到泛型参数位置(传参)
// 泛型
interface User {
  name: string
  age: number
}
interface Good {
  id: number
  goodsName: string
}
interface ResData<T> {
  msg: string
  code: number
  data: T
}
let Userdata: ResData<User> = {
  code: 200,
  msg: 'success',
  data: {
    name: 'jack',
    age: 18,
  },
}
let GoodData: ResData<Good> = {
  code: 200,
  msg: 'success',
  data: {
    id: 3,
    goodsName: '可乐',
  },
}

泛型别名

语法:在类型别名type的后面使用<T>即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型。

// 泛型type
type User = {
  name: string
  age: number
}
type Good = {
  id: number
  goodsName: string
}
type ResData<T> = {
  msg: string
  code: number
  data: T
}
let Userdata: ResData<User> = {
  code: 200,
  msg: 'success',
  data: {
    name: 'jack',
    age: 18,
  },
}
let GoodData: ResData<Good> = {
  code: 200,
  msg: 'success',
  data: {
    id: 3,
    goodsName: '可乐',
  },
}

泛型函数

语法:在函数名称的后面使用<T>即可声明一个泛型参数,整个函数中(参数、返回值、函数体)的变量都可以使用该参数的类型

需求:设置一个函数createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型)。

function createArray<T>(len: number, value: T) {
  let result = []
  for(let i=0;i<len;i++){
    result[i] = value
  }
  return result
}
createArray<string>(4, '默认字符串')
createArray<number>(4, 0)

泛型约束

作用:泛型的特点就是灵活不确定,有些时候泛型函数的内部需要访问一些特定类型的数据才有的属性,此时会有类型错误,需要通过泛型约束解决。

function logLen<T>(obj: T) {
  console.log(obj.length) //报错!提示类型T上不存在属性"length"
}

通过定义有length属性的接口,继承约束泛型。

interface LengthObj {
  length: number
}
function logLen<T extends LengthObj>(obj: T) {
  console.log(obj.length)
}

ref如何标注类型

为ref标注类型之后,既可以在给ref对象的value赋值时校验数据类型,同时在使用value的时候可以获得代码提示。

说明:本质上是给ref对象的value属性添加类型约束

ref函数和TS的配合通常分为俩种情况,取决于想为ref内的值指定为简单类型的数据还是复杂类型的数据。

  • 如果是简单的数据,推荐使用类型推导
  • 如果是较复杂的数据,通过泛型指定类型

通过泛型指定类型之后除了修改value做了类型约束,ref函数的初始值也有了类型约束

type Item = {
  id: string
  name: string
  price: number
}
const list = ref<Item[]>([])
list.value = [{ id: '1003', name: '鞋子', price: 200 }]

reactive如何标注类型

为reactive标注类型之后,既可以在响应式对象在修改属性值的时候约束类型,也可以在使用时获得代码提示

  • 如果根据默认参数对象推导的类型符合要求,推荐使用类型推导
  • 如果根据默认对象推导不出我们想要的类型,推荐使用类型别名给变量显式注解对应类型
type Item = {
  id: string
  name: string
  price?: number
}
const item:Item = reactive({
  id:'',
  name:'',
  price:0
})

computed如何类型标注

计算属性通常由已知的响应式数据计算得到,所以依赖的数据类型一旦确定通过自动推导就可以知道计算属性的类型。

另外根据最佳实践,计算属性多数情况下是只读的,不做修改,所以配合TS一般只做代码提示。

事件处理函数如何标注类型

原生dom事件处理函数的参数e默认会自动标注为any类型,没有任何类型提示,为了获得良好的类型提示,需要手动标注类型。

事件处理函数的类型标注主要做俩事

  1. 给事件对象形参e标注为Event类型,可以获得事件对象的相关类型提示
  2. 如果需要更加精确的DOM类型提示可以使用断言(as)进行操作。如e.target
<script setup lang="ts">
const inputChange = (e: Event) => {
  console.log((e.target as HTMLInputElement).value)
}
</script>

<template>
  <div>
    <input type="text" @change="inputChange" />
  </div>
</template>

模板引用如何标注类型

模板引用:通过ref标识获取真实的dom对象或者组件实例对象。

  1. 调用ref函数生成一个ref对象 ref(null)
  2. 通过ref标识绑定ref对象到标签 ref=""

给模版引用标注类型,本质上是给ref对象的value属性添加了类型约束,约定value属性中存放的是特定类型的DOM对象,从而在使用的时候获得相应的代码提示。

方法:通过具体的DOM类型联合null作为泛型参数,比如我们想获取一个input dom元素

<script setup lang="ts">
// 页面加载 输入框定位

import { onMounted, ref } from 'vue'
// 具体的DOM类型联合null作为泛型参数
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
  // ?表示可选链,兼容null的情形,判断inputRef.value是不是null,不是null才执行focus
  // inputRef.value存放的数据在组件渲染完毕前是null
  inputRef.value?.focus()
})
</script>

<template>
  <div>
    <input type="text" ref="inputRef" />
  </div>
</template>

扩展:inputRef.value.focus()-对象的非空值处理

当对象的属性可能是null或undefined的时候,称之为“空值”,尝试访问空值身上的属性或者方法会发生类型错误。

inputRef变量在组件挂载完毕之前,value属性中存放的信为null,不是HTML DOM对象,通不过类型校验。

可选链方案

可选链?.是一种访问嵌套对象属性的安全的方式,可选链前面的值为undefined或者null时,它会停止运算。inputRef.value?.focus()

逻辑判断方案

通过逻辑判断,只有有值的时候才继续执行后面的属性访问语句。

if(inputRef.value){
  inputRef.value.focus()
}

非空断言方案

非空断言(!)是指我们开发者明确知道当前的值一定不是null或者undefined,让TS通过类型校验

inputRef.value!.focus()

注意:使用非空断言要格外小心,只是过了类型校验。

如何为组件的props标注类型以及可选参数的默认值

确保给组件传递的prop是类型安全的。在组件内部使用propsa和为组件传递prop属性的时候会有良好的代码提示。

语法:通过defineProps宏函数对组件orops进行类型标注。

需求:按钮组件有俩个prop参数,color类型为string且为必填,size类型为string且为可选,怎么定义类型?

子组件Button:

type Props = {
  color: string
  size?: string
}
const props = defineProps<Props>()

父组件App:

<Button :color="'red'"></Button>

场景:Props中的可选参数通常除了指定类型之外还需要提供默认值,可以使用withDefaults宏函数来进行设置。

需求:按钮组件的size属性的默认值设置为middle

type Props = {
  color: string
  size?: string
  btnType?: 'success' | 'danger' | 'warning'
}
const props = withDefaults(defineProps<Props>(), {
  size: 'middle',
  btnType: 'success',
})
<Button :color="'red'" btn-type="success"></Button>

注意对象中的btnType 和 标签属性的 btn-type 的对应关系

如何为组件的emits标注类型

  1. 父组件中给子组件标签通过@绑定自定义事件
  2. 子组件内部通过defineEmits方法生成emit方法。触发自定义事件,并传递参数

触发一个自定义事件,最需要关注的是什么?emits的两个参数:事件名称和事件参数(就是数据)

作用:可以约束事件名称并给出自动提示,确保不会拼写错误,同时约束传参类型,不会发生参数类型错误

语法:通过defineEmits宏函数进行类型标注

需求:子组件触发一个名称为'get-msg'的事件,并且传递一个类型为string的参数。

父:

<script setup lang="ts">
import Button from './components/Button.vue'

const getMessage = (msg: string) => {
  console.log(msg)
}
const getList = (list: any) => {
  console.log(list)
}
</script>
<template>
  <div>
    <Button
      :color="'red'"
      btn-type="success"
      @get-msg="getMessage"
      @get-list="getList"
    ></Button>
  </div>
</template>

子:

<script setup lang="ts">
type ItemList = {
  id: number
  name: string
}
type Emits = {
  (e: 'get-msg', msg: string): void
  (e: 'get-list', lsit: ItemList[]): void
}
const emit = defineEmits<Emits>()

const sendMsg = () => {
  emit('get-msg', 'this is a message')
}
const demoList = [{ id: 1001, name: '袜子' }]
const sendList = () => {
  emit('get-list', demoList)
}
</script>

<template>
  <button @click="sendMsg">msg</button>
  <button @click="sendList">list</button>
</template>

类型声明文件

概念:在TS中以d.ts为后缀的文件就是类型声明文件,主要作用是为js模块提供类型信息支持,从而获得类型提示。

  • d.ts是如何生效的?

在使用js某些模块的时候,TS会自动导入模块对应的ts文件,以提供类型提示。

  • d.ts如何来的?
  1. 如axios库本身使用ts编写,打包的时候经过配置自动生成对应的d.ts文件
  2. 有些库本身并不是采用TS编写的,无法直接生成配套的d.ts文件,但是也想获得类型提示,此时需要DefinitelyTyped提供类型声明文件。DefinitelyTyped是一个TS类型定义的仓库,专门为JS编写的库可以提供类型声明,比如可以安装@types/jquery为jquery提供类型提示。
  3. TS为JS运行时可用的所有标准化内置AP都提供了声明文件,这些文件既不需要编译生成,也不需要三方提供

axios如何定义返回数据类型

定义axios的返回数据类型需要配合一个axios的request方法通过泛型指定。

const getChannelList = async ()=>{
  const res = await axios.request<ChannelRes>({
    url:'',
  })
}

使用Vue+TS开发比较重要的是哪些类型的定义?

  1. 响应式数据(ref)的类型标注-ref<类型>
  2. axios返回数据(res.data)的类型标注-axios.request<类型>

凡是需要存储数据的地方都会涉及到类型标注,比如:普通变量/对象属性

自定义类型声明文件

dts文件在项目中是可以进行自定义创建的,通常有俩种作用,第一个是共享TS类型(重要),第二种是给js文件提供类型(了解)。

共享TS类型

src/types/data.d.ts:

export type Goods = {
  id: string
  name: string
  price: number
}

app.vue:

<script setup lang="ts">
import type { Goods } from './types/data'

const goods: Goods[] = [{ id: '1001', name: '鞋', price: 200 }]
console.log(goods)
</script>

给JS提供类型

通过declare关键词可以为js文件中的变量声明对应类型,这样js导出的模块在使用的时候也会获得类型提示。

在js同目录下定义同名d.ts文件。通过declare定义类型,同样导出。

const add = (a,b) => a+b
export {add}
declare const add:(a:number, b:number) => number
export { add }

ts文件

  1. 既可以包含类型信息也可以写逻辑代码
  2. 可以被编译为js文件

d.ts文件

  1. 只能包含类型信息不可以写逻辑代码
  2. 不会被编译为jS文件,仅做类型校验检查

案例

// 记录当前页面的刷新次数和刷新时的时间,
// 每次刷新都自动自增一次,并显示到页面中,
// 要求用TypeScript:实现
/**
 *
 * 1.从本地获取到当前最新列表,取出当前列表中的最后一条记录
 * 2.在最后一条记录的基础上把次数加一,重新把次数和当前时间添加
 * 到列表的尾部
 * 3.把最新列表渲染到页面
 * 4.把最新列表存本地
 * 先主线后细节
 */
function updateData() {
  // 1.获取最新列表,取到最后一项
  const list = getList()
  const lastItem = list[list.length - 1]
  // 2. +1
  list.push({
    count: lastItem ? lastItem.count + 1 : 1,
    time: getFormatTime(),
  })

  // 3. 列表渲染
  render(list)

  // 4. 存本地
  setList(list)
}

type Item = {
  count: number
  time: string
}
const KEY = 'ts-key'
function getList(): Item[] {
  // getItem获得的是字符串类型,解析成Item对象数组
  // 如果获取到的为null,就把'[]'解析成空数组
  return JSON.parse(localStorage.getItem(KEY) || '[]')
}
function render(list: Item[]) {
  const strArr = list.map((item) => {
    return `刷新次数为:${item.count},刷新时间为:${item.time}`
  })
  const app = document.getElementById('app') as HTMLDivElement
  app.innerHTML = strArr.join('<br>')
}
function setList(list: Item[]) {
  localStorage.setItem(KEY, JSON.stringify(list))
}
function getFormatTime() {
  const _time = new Date()
  const h = _time.getHours()
  const m = _time.getMinutes()
  const s = _time.getSeconds()
  return `${h}:${m}:${s}`
}
updateData()
posted @ 2025-06-02 18:20  subeipo  阅读(86)  评论(0)    收藏  举报