从零开始学习typescript系列4: typescript使用过程中遇到的常见使用场景和常见问题以及解决方案

全局配置与类型管理:解决VueTypeScript项目常见问题

全局: 如何忽略ts检查

// 单行忽略 
// @ts-ignore

// 忽略全文 
// @ts-nocheck

// 检查全文 
// @ts-check

全局: 如何设置类型声明

目标

  • projec/types/xxx.d.ts 类型声明文件有效
{
  "compilerOptions": {
    "typeRoots": [
      "./node_modules/@types/",
      "./types" // 设置1
    ] 
  },
  "include": [
    "src/**/*.ts", 
    "src/**/*.vue", "types/**/*.d.ts"], // 设置2
}

number: ts 定义一个num: number 但是 num 默认值不想写数字 怎么破?

const num1: number | null  = 1
const num2: number | undefined = 1
num?:number // 同上,用于interface的key 

string: 限制取值范围

// 方式1 显示定义字符串范围
type sColor = "black" | "white" | "green"; // 取值限制范围
const s1: sColor = "black"  // ok
const s2: sColor = "white"  // ok
const s3: sColor = "green"  // ok
const s4: sColor = "yellow"  // error


// 方式2:通过枚举定义字符串范围 
enum eColor {
    black = 'black',
    white = 'white',
    green = 'green'
}
// 取值限制范围 同上面等价 
type sColor = keyof typeof eColor; 
const s1: sColor = 'black'  // ok
const s2: sColor = "white"  // ok
const s3: sColor = "green"  // ok
const s4: sColor = "yellow"  // error
// 优点: 枚举可以用于其他地方 
if(data.background === eColor.black)  // 判断 
data[eColor.black] // 作为对象的key 

enum: 定义枚举 && 使用枚举

// 定义eCheckStatus(ts类型): 枚举值,用于checkStatusMap对象的key名 
export enum eCheckStatus {
    toVerify = 0,
    pass = 1,
    notPass = 2,
}
// 定义iTag(ts类型): 接口,key是任意数字,value是嵌套接口
interface iTag {
    [key:number]:{
        label: string, // 页面显示文字内容 
        type?: string, // 页面显示文字颜色
    }
}
// 使用ts类型: eCheckStatus(内部key名)+ iTag(整体结构), 在页面中展示需要
// 注意: 枚举 eCheckStatus 使用姿势是中括号
export const checkStatusMap:iTag = {
  [eCheckStatus.toVerify]:{
    label: '待审核',
    type: ''
  },
  [eCheckStatus.pass]:{
    label: '通过',
    type: 'success'
  },
  [eCheckStatus.notPass]:{
    label: '未通过',
    type: 'info'
  }
}
// 使用ts类型: eCheckStatus 在 js 逻辑中需要,用于添加判断
v-if="scope.row.checkStatus === eCheckStatus.toVerify"

Array: 可能是一维数组或二维数组

// 需求: 可能是一维数组或二维数组 
type iArr  = (string|string[])[]
const a1: iArr = ['1','2','3'] // 一维数组
const a2: iArr = [['1'],['2'],['3']] // 二维数组

Array: 数组里面是对象

const cells: Array<{ label: string; value: string }> = [
    {
        label: '费用',
        value: '10,000.00'
    }, {
        label: '总计',
        value: '10,000.00'
    }, {
        label: '余额',
        value: '10,000.00'
    }
]

interface: 如何继承和覆盖某些字段?

目标

  • 继承A的大部分类型,但要调整个别类型
  • 通过extends实现
interface A {
    a: number;
    b: number;
}
// Omit<A, 'a'> 等价于 剔除 a 后的接口 {b:number}
interface B extends Omit<A, 'a'> {
    a: boolean;
    c: string;
}
// 最终B的类型为 {a:boolean, b:number, c:string}
const b: B = {
    a:true, // 在A的基础上调整 
    b:1, // 继承A的类型
    c:'hi' // 在A的基础上新增
}

interface: key可能为任意字符串 && 嵌套对象

interface TagValue {
    min: number,
    max: number
}
interface Tag {
    // 1. 对象嵌套对象 TagValue
    // 2. 对象key propname: 可能为任意字符串或数字 
    [propname: string | number]: TagValue,
}

const t: Tag = {
    "apple": { min: 3, max: 5 },
    100: { min: 1, max: 100 },
}

interface: 定义多个key,必须和非必须

interface IBasicLayout {
    loading: any; // 必须key 
    [key: string]: any; // 非必须key 可有可无
}
let data1: IBasicLayout = {
    loading: false
}
let data2: IBasicLayout = {
    loading: true,
    userName: 'abc'
}


export {
    print
}

interface: key名不确定

//  方式1: 使用interface
interface iMenu1 {
    [key: string]: string;
};
const im1: iMenu1 = {M: '目录',C: '菜单',F: '按钮',}; // ok
const im2: iMenu1 = {age:100}; // error 因为 100是 number
//  方式2: 使用Record 
type iMenu2 = Record<string, string>
const rm1: iMenu2 = {M: '目录',C: '菜单',F: '按钮',}; // ok
const rm2: iMenu2 = {age:100}; // error 因为 100是 number

泛型: 接口请求

目标

  • 使用泛型T来抽象返回值类型
// 定义接口返回值类型 iRes
interface iRes {
    id: string;
    url: string;
    width: number;
    height: number;
}
// 发起请求函数getJson: 泛型用来定义返回值ts类型 
// 第1个T是定义泛型,第2个T是使用泛型(用于返回类型)
async function getJson<T>(url: string): Promise<T> {
    const response: Response = await fetch(url);
    const json: Promise<T> = await response.json();
    return json;
}
// 入口函数getData
async function getData(): Promise<void> {
    try {
        const url: string = 'https://xxx/search'
        const json: iRes[] = await getJson<iRes[]>(url)
        const data: iRes = json[0];
        console.log('请求正常: data=', data)
    } catch (error: Error | unknown) {
        let message: string = error instanceof (Error) ? error.message : String(error);
        console.error('发生错误: ', message)
    }
}

window.onload = function () {
    const button = document.querySelector('.button') as HTMLButtonElement;
    button && button.addEventListener('click', getData, false);
}

// 测试 
// 1. html中准备  <button class="button">按钮</button>
// 2. 必须服务器方式运行index.html才能请求成功,如 open with live server 

// 结果: 
// 请求正常: data= {
//     "id": "c2r",
//     "url": "https://xxx/images/c2r.jpg",
//     "width": 500,
//     "height": 374
// }

defineProps: 定义数组类型

interface List {
  id: number, 
  content: string
}
defineProps<{
  list: List[]
}>()

pit: vite.config.js Cannot find module ‘path’ ‘__dirname’

// 问题 vite.config.js 中 报错
import path from 'path';  
// 类似其他问题 找不到 __dirname 
path.resolve(__dirname, '../dist')

// 解决办法: 添加官方path声明,无需配置 
yarn add -D @types/node  

pit: Could not find a declaration file for module ‘vue-xxx‘

// 问题代码
import VueWechatTitle from 'vue-wechat-title'

// 解决办法1  cjs 代替 esm 导入  
const VueWechatTitle = require('vue-wechat-title')
// ❌ ReferenceError: require is not defined 应该是 vue-wechat-title 不是cjs导出

// 解决办法2  修改ts配置 tsconfig.json ,运行导入js 
{
  "compilerOptions":{
    "noImplicitAny": false,
    "allowJs": true,
  }
}

pit: Cannot find module ‘weixin-js-sdk’ or its corresponding type declarations.

// 问题代码
yarn add weixin-js-sdk //  "weixin-js-sdk": "^1.6.0"
import wx from "weixin-js-sdk";

// 解决办法
// 方法1. 直接使用ignore忽略语法检测: 因为实际不影响代码正常运行的,打印wx对象,正常的 包含52个方法 
// 方法2. 考虑换成 weixin-js-sdk-ts, 从github可以看到 js和ts的版本匹配,同样打印正常 并且包含52个方法

// 如何使用weixin-js-sdk-ts
// https://github.com/zhaoky/wx-jssdk-ts
// 1. 安装 2.引入
yarn remove weixin-js-sdk 
yarn add weixin-js-sdk-ts  // "weixin-js-sdk-ts": "^1.6.1"
import wx from "weixin-js-sdk-ts";

pit: Cannot find module ‘…/views/HomeView.vue’ or its corresponding type declarations.ts

// 问题 vue3 ts  ts无法识别 vue 文件 
// 同样的报错 Cannot find module './App.vue'

// 解决办法
// 在根路径 env.d.ts 或 src/types/env.d.ts  添加以下声明 
// ❗文件生效的前提是 tsconfig.json 中配置了 "include": ["env.d.ts"],
declare module '*.vue' {
  import { DefineComponent } from 'vue';
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

pit: Cannot find module ‘@babel/types’ or its corresponding type declarations.

// 解决办法
// 1. yarn add @babel/types
// 2. 配置 ts.config.json中的 paths "@babel/types" 配置
// 3. yarn add --dev @types/babel__types // 这个没有做也可以 
"compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@babel/types": ["node_modules/@babel/types"] // 新增
    },
}

pit: Cannot find module ‘lodash-es’ or its corresponding type declarations.

# 原因: 未找到 lodash-es 的类型声明 
解决办法
- yarn add lodash-es
- yarn add -D @types/lodash-es
- 在 tsconfig.json compilerOptions 中开启:  "esModuleInterop": true
- 重启vscode 

pit: document.querySelector 可能为空


// problem
function test2() {
  // 报错: button可能为nul
    const button = document.querySelector('button');
}

// solution
function test2() {
    // 解决办法1. 使用断言 非常确定的类型 
    const button1 = document.querySelector('button') as HTMLButtonElement
    // 解决办法2. 使用联合类型
    const button2: HTMLButtonElement | null = document.querySelector('button');
}

pit: Property ‘resetFields’ does not exist on type 'Ref

背景:

  • ts + vue3 + element plus
  • 根据element plus 官方文档校验规则
// before 
import type { FormInstance } from 'element-plus'
const menuRef = ref<FormInstance>()
function reset() {
  form.value = initFormData();
   if (!menuRef) return
   menuRef.resetFields()
}
// after 
import type { FormInstance } from 'element-plus'
const menuRef = ref<FormInstance>()
function reset() {
  form.value = initFormData();
   if (!menuRef) return
   // ❗ref变量需要使用value
   menuRef.value?.resetFields()
}

pit: Element implicitly has an ‘any’ type because expression of type ‘any’ can’t be used to index type

// before 
const orderStatusMap = {
  101: '待付款',
  102: '用户取消',
  901: '已完成',
};
<el-table-column align="center" label="订单状态">
<template #default="scope">
    // ts报错: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type
    // 原因:  orderStatusMap key 的类型不确定
    <el-tag>{{ orderStatusMap[scope.row.status ]}}</el-tag>
</template>
</el-table-column>

// after 
// 解决办法: 目前 orderStatusMap key 类型为string 
type IFiledMap = {
    [key: string]: string
}
const orderStatusMap: IFiledMap = {
  101: '待付款',
  102: '用户取消',
  901: '已完成',
};
<el-table-column align="center" label="订单状态">
<template #default="scope">
    <el-tag>{{ orderStatusMap[scope.row.status ]}}</el-tag>
</template>
</el-table-column>

pit: 没有ts版本的第三方库,提示 Could not find a declaration file for module

note-work-pit/front-ts/[ts]Could not find a declaration file for module.md

pit:Property ‘style’ does not exist on type ‘Element’.

// 问题代码
firstEle.style.flexGrow = 1

// 解决办法: 声明 firstEle 的 ts类型
const firstEle:HTMLElement = document.querySelector('.accordion-item')!
firstEle["style"].flexGrow = '1'

pit: Property ‘Vue’ does not exist on type ‘Window & typeof globalThis’.ts(2339)

// src/types/index.d.ts
export {};

declare global {
  interface Window {
    example: string;
  }
}

pit: Property ‘offsetTop’ does not exist on type ‘Element’

// before 
// 原因:Element 类型上没有 offsetTop
const element = document.getElementById('my-element') as Element;
const offsetTop = element.offsetTop;
// 修改后
// 解决办法: HTMLElement 类型上有 offsetTop,可以在类型上点进去看到 
const element = document.getElementById('my-element') as HTMLElement;
const offsetTop = element.offsetTop;

pit: document.getElementById object is possibly ‘null’

// 问题代码
// 原因: ts告诉你 这个返回值可能为null
document.getElementById('app').scrollTop = 0;
// 解决办法
// 方法1. 很自信这个对象一定存在,可以用 !解决 
document.getElementById('app')!.scrollTop = 0;
// 方法2. 不确定这个对象是否存在,则判断为null请求处理即可
const appEle = document.getElementById('app') 
if(appEle){
  appEle.scrollTop = 0;
}

pit: Cannot find type definition file for ‘element-plus/global’.

// 问题 找不到 element plus 组件的类型声明
// 原因 https://github.com/element-plus/element-plus/issues/4716
// 解决办法: 修改 ts.config.json 配置
"compilerOptions": {
  // "types": ["element-plus/global"] // 删除
}
 "include": ["node_modules/element-plus/global.d.ts"] // 新增

pit: Option ‘suppressImplicitAnyIndexErrors’ is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption ‘“ignoreDeprecations”: “5.0”’ to silence this error.

// 原因:和ts版本某些属性废弃有关
"compilerOptions": {
  "suppressImplicitAnyIndexErrors":true, // 代码编译有问题 =》解决1.代码规范
  "ignoreDeprecations": "5.0",  // 解决办法2: 声明忽略5.5版本的废弃属性
}

pit ‘AbortSignal’ was also declared here.

# 报错
# node_modules/typescript/lib/lib.dom.d.ts:2071:13
#     2071 declare var AbortSignal: {
#                      ~~~~~~~~~~~
#     'AbortSignal' was also declared here.  

# 解决办法
yarn add --dev  @types/node@latest 

pit element-plus Options Cannot find name ‘Options’.

问题: 
node_modules/element-plus/es/components/select/index.d.ts:6468:24 - error TS2304: Cannot find name 'Options'.
6468         default: () => Options;
                            ~~~~~~~
node_modules/element-plus/es/components/tree-v2/index.d.ts:99:511 - error TS2300: Duplicate identifier 'event'.
# 解决办法 
官方issue: open状态,看起来未解决
https://github.com/element-plus/element-plus/issues/13177
https://github.com/element-plus/element-plus/discussions/10721  帖子里提到的例子 element-plus版本是 2.3.1
升级 element-plus 2.3.3 到 2.3.1或最新2.3.6  关注 yarn.lock
结果: 未解决
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值