Typescript+vue项目常见提示报错的解决方案

132 阅读7分钟

简介:

TypeScript 是一种静态类型的 JavaScript 语言,它最初由微软开发并于 2012 年首次公开发布。它扩展了 JavaScript 的语法,为开发大型应用程序而设计。

为什么使用typescript:

  • 程序更容易理解(函数或者方法输入输出的参数类型,外部条件等);

  • 效率更高,在不同代码块和定义中进行跳转,代码补全等;

  • 更少的错误,编译期间能够发现大部分的错误,杜绝一些比较常见的错误;

  • 好的包容性,完成兼容 JavaScript,第三方库可以单独编写类型文件;

1. 类型注解和类型推断

类型注解和类型推断是 TypeScript 的核心概念。类型注解是指手动给变量、参数和函数返回类型进行指定,而类型推断是指 TypeScript 根据上下文自动推断变量或函数的类型。

// 类型注解
let myString: string = "Hello World";

// 类型推断
let myNumber = 42;

2. 类型别名和接口

类型别名和接口都用于定义自定义类型,不同之处在于:

  1. 类型别名可以声明原始值,联合类型,元组以及其它任何你需要手写的类型,而接口则只能声明对象。
  2. type不会自动合并,接口会。
  3. 当出现声明同名数据时,type会报错,interface会进行合并。
// 类型别名
type jenkinsID = string | number;

// 接口
interface Person {
  name: string;
  age: number;
  job?: string; // 可选属性
  readonly id: number; // 只读属性
}

3. 类和继承

TypeScript 支持面向对象式的编程,包括类和继承。使用关键字 class 可以创建一个新的类,并使用关键字 extends 来实现继承。

class Animal {
  public name: string;

  constructor(name: string) {
    this.name = name;
  }

  move(distance: number = 0) {
    console.log(`The ${this.name} moved ${distance}m.`);
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
  }

  move(distance: number = 5) {
    console.log(`The ${this.name} crept ${distance}m.`);
  }
}

const cat = new Cat("Tom");
cat.move();

4. 枚举

枚举是一种特殊的类型,它可以用于定义一组命名的常量。在 TypeScript 中,枚举默认是从 0 开始索引,但是可以手动修改索引值。

enum Direction {
  Up,
  Down,
  Left,
  Right,
}

const myDirection: Direction = Direction.Up;
console.log(myDirection); // Output: 0

5. 泛型

泛型是一种可重用的代码模板,它可以用于支持多种类型的数据。在 TypeScript 中,使用泛型可以让我们更加灵活地处理不同类型的数据。

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}

console.log(reverse<number>([1, 2, 3])); // Output: [3, 2, 1]
console.log(reverse<string>(["hello", "world"])); // Output: ["world", "hello"]

6. 接口继承类

接口可以继承普通的接口,也可以继承一个类的类型,这个类中只能包含方法的声明。

class Car {
  constructor(public brand: string, public model: string) {}

  start() {
    console.log(`Starting ${this.brand} ${this.model}`);
  }

  stop() {
    console.log(`Stopping ${this.brand} ${this.model}`);
  }
}

interface ICar extends Car {
  wheels: number;
}

const myCar: ICar = {
  brand: "Toyota",
  model: "Camry",
  wheels: 4,
  start() {
    console.log(`Starting ${this.brand} ${this.model} with ${this.wheels} wheels.`);
  }
};

myCar.start();

7. 联合类型和交叉类型

联合类型和交叉类型是 TypeScript 的高级类型概念。联合类型表示一个变量可以是多种类型之一,而交叉类型表示一个变量必须同时满足多种类型的要求。

// 联合类型
let myValue: string | number;
myValue = "hello";
myValue = 42;

// 交叉类型
type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge;

const myPerson: Person = {
  name: "Tom",
  age: 28,
};

8. 函数类型和箭头函数

在 TypeScript 中,函数也可以像变量一样具有类型。函数类型定义了函数的参数类型和返回值类型,箭头函数则是一种简化的函数定义。

// 函数类型
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const subtract = (a: number, b: number): number => a - b;

9. 可选变量和默认参数

在 TypeScript 中,可以给函数参数指定可选类型和默认值。可选参数用 ? 标识,而默认参数则使用等号 = 进行定义。

function greet(name?: string, message: string = "Hello"): string {
  return `${message}, ${name || "world"}!`;
}

console.log(greet()); // Output: "Hello, world!"
console.log(greet("Tom")); // Output: "Hello, Tom!"
console.log(greet("Tom", "Hi")); // Output: "Hi, Tom!"

10. 类型断言

类型断言是指告诉编译器变量的类型是什么,通常用于处理一些类型不确定的情况。有两种类型断言方式:尖括号语法和 as 语法。

const myValue: any = "hello";
const length1: number = (<string>myValue).length;
const length2: number = (myValue as string).length;

11. 类型保护

在 TypeScript 中,类型保护指的是使用特定的语法结构,以便 TypeScript 编译器可以在一些条件下确定变量的类型。常见的类型保护结构包括 typeof、instanceof 和 in。

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
  }
}

12. 引入和导出模块

TypeScript 支持使用模块化的方式进行项目的组织和管理。使用关键字 importexport 可以实现模块的引入和导出。

// user.ts 文件
export interface IUser {
  id: number;
  name: string;
}

// main.ts 文件
import { IUser } from './user';
const myUser: IUser = {
  id: 1,
  name: "Tom",
};

13. 类型模板字符串

类型模板字符串允许程序员在字符串中嵌入表达式,并在编译时获得类型检查。使用反引号 `` 和 ${} 表示插值表达式。

let name = "Tom";
let age = 28;
let message: string = `My name is ${name} and I'm ${age} years old`;

14. 使用库和类型定义文件

TypeScript 可以使用 JavaScript 库和框架,但如果这些库没有提供 TypeScript 的类型定义文件,则需要手动编写。类型定义文件可以为库和框架提供 TypeScript 类型,以便编译器可以进行类型检查。

// 安装类型定义文件
npm install --save-dev @types/lodash

// 引入库和类型定义文件
import * as _ from 'lodash';

// 使用库
const myArray = _.chunk([1, 2, 3, 4, 5], 2);
console.log(myArray); // Output: [[1, 2], [3, 4], [5]]

15. 面向对象编程

TypeScript 支持面向对象式的编程,类和继承是面向对象编程的核心概念之一。使用关键字 class 定义类,使用关键字 extends 实现继承。

class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance}m.`);
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }

  bark() {
    console.log("Woof! Woof!");
  }
}

const myDog = new Dog("Tommy");
myDog.move(10);
myDog.bark();

18. 接口

接口是 TypeScript 中一种常用的类型声明方式,可以描述对象、函数等结构的类型。使用 interface 关键字定义接口。

interface Person {
  name: string;
  age: number;
  job?: string;
  readonly id: number;
}

const myPerson: Person = {
  name: "Tom",
  age: 28,
  id: 123,
};
myPerson.job = "coder";

19. 函数

函数是 TypeScript 中的一个核心概念,可以定义函数参数类型、函数返回值类型等信息。

function add(a: number, b: number): number {
  return a + b;
}

console.log(add(2, 3)); // Output: 5

20. 类型别名

类型别名是一种类型定义方式,允许为现有类型起一个新名字。使用 type 关键字定义类型别名。

type Id = number | string;

function getId(id: Id): Id {
  return id;
}

console.log(getId(42)); // Output: 42
console.log(getId("foo")); // Output: "foo"

23. 空值和未定义

TypeScript 支持空值和未定义类型,可以使用 nullundefined 表示。

let myValue: string | null = null;
let myAnotherValue: string | undefined;

console.log(myValue); // Output: null
console.log(myAnotherValue); // Output: undefined

24. 非空断言

非空断言是 TypeScript 的一种特殊语法,可以告诉编译器变量不为 null 或 undefined。使用 ! 符号表示非空断言。

let myValue: string | null = "hello";
console.log(myValue!.length); // Output: 5

25. 迭代器和生成器

迭代器和生成器是 TypeScript 的高级概念之一,它们可以帮助实现通用、可重用的代码。迭代器是使用内部状态可遍历数据的一种方法,而生成器则是可以暂停并继续执行的迭代器。

function* fib() {
  let [prev, curr] = [0, 1];
  while (true) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

let generator = fib();
console.log(generator.next().value); // Output: 1
console.log(generator.next().value); // Output: 2
console.log(generator.next().value); // Output: 3

结合vue项目中使用ts

  • ts的最大意义就是,避免写错、漏写,这样基本能屏蔽掉低级错误
    • 编写公用的方法和全局配置对象,用于提醒使用者传穿错参数或者参数的值
    • 编写组件的时候用于提示使用者有没有写错props
    • 一些第三方库如果是ts编写,可以检测到你有没有调用错方法,写错配置
  1. 接口与ts
问题一:
 export interface listParams {
    page:number,
    pageSize:10|20|50
}
// 这个接口可以用于后面的类型,引入方式, import type {listParams} from './xxx'

function getList(params:listParams){
    return axios({
        url:'xxx',
        params
    })
}

getList({pages:1,pageSize:10}) // pages写错了,会直接提示
相对于不使用ts来说,可以减少很多低级错误

问题二:
// 如果你一开始没有声明类型,那么即使后面又重新赋值了,也会推断提示错误
let listparams = ref({})  // 首先赋值空

listparams.value = {
    page:1,
    pageSize:10  // 这里赋值了正确得参数  
}  

getList(listparams)  // 虽然后面赋值了正确的参数,但是这里依旧会报错

// 上述问题的解决方式
import type {listParams} from './xxx'
let myListparams = ref({} as listParams)  // 首先赋值空
listparams.value = {
    page:1,
    pageSize:10  // 这里赋值了正确得参数
}  
getList(myListparams)  // 上面使用断言后,这里就不会报错了
  1. props与ts
// test.d.ts  在这里声明接口
export interface testProps{
    a:number,
    b?:string
}

// test.vue 
// 引入testProps
defineProps<testProps>();


//除了可以减少低级错误外,还可以梳理开发思路

  1. 开发中的报错点 , 现阶段类型不匹配,ts报错
// vue文件 声明一个空对象
let list = ref({})

getList({page:1,pageSize:10})
.then(res=>{
    list.value = res.data  // 假设这里list赋值有a
}) 
console.log(list.value.a)  // 这里就会提示报错,list是一个空对象,list没有a,但实际list中已经赋值了a。
// 要解决这种报错,可以使用断言,先给上你要取得指,如下
interface listData {
    a:string  // 只需要定义 , 后续会用到得值。当然 ,更好得还是根据接口文档返回得值
}
let list = ref<listData>({} as listData)

  1. 调用用ts编写的第三方库时,需要定义某个东西为第三方库里的某个类型
// 比如 第三方库类型报错(比如传参报any类型错误),那么如何去寻找这个类型
// 解决方法:
// 1. 将鼠标指针放在该方法上面,会出现第三方库定义的类型,如router.addRoute()的类型就是RouteRecordRaw
// 2. 引入第三方库的类型,import type {RouteRecordRaw} from 'vue-router'
// 3. 使用
    function parseRoute(arr:RouteRecordRaw[]){  // 这样就可以了
        arr.forEach(item=>{
            router.addRoute(item)
        })
    }
//注:但是有的库,用import type的方式找不到类型,那么解决方式如下:以vue-router为例
//第一步:寻找项目中node_modules中的vue-router中的package.json中的types中的文件地址
  1. 一些常见的dom类型报错
// 比如 
<div @click="(e)=>{
    let target =  e.target as HTMLElement  // 使用类型断言,(因为ts自动推断错误)
    target.innerHtml =    // 这里如果不使用类型断言 就是报错
}"></div>