百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

TypeScript 常见问题整理(60多个)「上」

ztj100 2024-12-22 22:02 11 浏览 0 评论


作者: 秋天不落叶

转发链接:https://mp.weixin.qq.com/s/KmDLcJ0jhB667ZouDB8tyg

前言

  • 用 React 全家桶 + TS 写项目快一年了,大大小小的坑踩了很多,在此整理了在项目中遇到的疑惑和问题。
  • 体会:不要畏惧 TS,别看 TS 官方文档内容很多,其实在项目中常用的都是比较基础的东西,像泛型运用、一些高级类型这种用的很少(封装库、工具函数、UI组件时用的比较多)。只要把常用的东西看熟,最多一个小时就能上手 TS。
  • 如果本文对你有所帮助,还请点个赞,谢谢啦~~

纯 TS 问题

1. TS 1.5 版本的改动

  • TypeScript 1.5 之前的版本:module 关键字既可以称做“内部模块”,也可以称做“外部模块”。这让刚刚接触 TypeScript 的开发者会有些困惑。
  • TypeScript 1.5 的版本: 术语名已经发生了变化,“内部模块”的概念更接近于大部分人眼中的“命名空间”, 所以自此之后称作“命名空间”(也就是说 module X {…} 相当于现在推荐的写法 namespace X {…}),而 "外部模块" 对于 JS 来讲就是模块(ES6 模块系统将每个文件视为一个模块),所以自此之后简称为“模块”。
  • 不推荐使用命名空间

之前

module Math {
    export function add(x, y) { ... }
}

之后

namespace Math {
    export function add(x, y) { ... }
}


2. null 和 undefined 是其它类型(包括 void)的子类型,可以赋值给其它类型(如:数字类型),赋值后的类型会变成 null 或 undefined

  • 默认情况下,编译器会提示错误,这是因为 tsconfig.json 里面有一个配置项是默认开启的。
// tsconfig.json 

{
      /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // 对 null 类型检查,设置为 false 就不会报错了
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */
}
  • `strictNullChecks` 参数用于新的严格空检查模式,在严格空检查模式下,null 和 undefined 值都不属于任何一个类型,它们只能赋值给自己这种类型或者 any


3. never 和 void 的区别

  • void 表示没有任何类型(可以被赋值为 null 和 undefined)
  • never 表示一个不包含值的类型,即表示永远不存在的值
  • 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。

4. 元祖越界问题

let aaa: [string, number] = ['aaa', 5];
// 添加时不会报错
aaa.push(6);
// 打印整个元祖不会报错
console.log(aaa); // ['aaa',5,6];
// 打印添加的元素时会报错
console.log(aaa[2]); // error

5. 枚举成员的特点

  • 是只读属性,无法修改
  • 枚举成员值默认从 0 开始递增,可以自定义设置初始值
enum Gender {
    BOY = 1,
    GRIL
}
console.log(Gender.BOY);// 1
console.log(Gender);// { '1': 'BOY', '2': 'GRIL', BOY: 1, GRIL: 2 }
  • 枚举成员值
  • 可以没有初始值
  • 可以是一个对常量成员的引用
  • 可以是一个常量表达式
  • 也可以是一个非常量表达式
enum Char {
    // const member 常量成员:在编译阶段被计算出结果
    a,                 // 没有初始值
    b = Char.a,// 对常量成员的引用
    c = 1 + 3, // 常量表达式

    // computed member 计算成员:表达式保留到程序的执行阶段
    d = Math.random(),// 非常量表达式
    e = '123'.length,
    // 紧跟在计算成员后面的枚举成员必须有初始值
    f = 6,
    g
}


6. 常量枚举与普通枚举的区别

  • 常量枚举会在编译阶段被删除
  • 枚举成员只能是常量成员
const enum Colors {
    Red,
    Yellow,
    Blue
}
// 常量枚举会在编译阶段被删除
let myColors = [Colors.Red, Colors.Yellow, Colors.Blue];

编译成 JS

"use strict";
var myColors = [0 /* Red */, 1 /* Yellow */, 2 /* Blue */];
  • 常量枚举不能包含计算成员,如果包含了计算成员,则会在编译阶段报错
// 报错
const enum Color {Red, Yellow, Blue = "blue".length};
console.log(Colors.RED);


7. 枚举的使用场景

以下代码存在的问题:

  • 可读性差:很难记住数字的含义
  • 可维护性差:硬编码,后续修改的话牵一发动全身
function initByRole(role) {
    if (role === 1 || role == 2) {
        console.log("1,2")
    } else if (role == 3 || role == 4) {
        console.log('3,4')
    } else if (role === 5) {
        console.log('5')
    } else {
        console.log('')
    }
}

使用枚举后

enum Role {
  Reporter,
  Developer,
  Maintainer,
  Owner,
  Guest
}

function init(role: number) {
  switch (role) {
    case Role.Reporter:
      console.log("Reporter:1");
      break;
    case Role.Developer:
      console.log("Developer:2");
      break;
    case Role.Maintainer:
      console.log("Maintainer:3");
      break;
    case Role.Owner:
      console.log("Owner:4");
      break;
    default:
      console.log("Guest:5");
      break;
  }
}

init(Role.Developer);


8. 什么是可索引类型接口

  • 一般用来约束数组和对象
// 数字索引——约束数组
// index 是随便取的名字,可以任意取名
// 只要 index 的类型是 number,那么值的类型必须是 string
interface StringArray {
  // key 的类型为 number ,一般都代表是数组
  // 限制 value 的类型为 string
  [index:number]:string
}
let arr:StringArray = ['aaa','bbb'];
console.log(arr);


// 字符串索引——约束对象
// 只要 index 的类型是 string,那么值的类型必须是 string
interface StringObject {
  // key 的类型为 string ,一般都代表是对象
  // 限制 value 的类型为 string
  [index:string]:string
}
let obj:StringObject = {name:'ccc'};


9. 什么是函数类型接口

  • 对方法传入的参数和返回值进行约束
// 注意区别

// 普通的接口
interface discount1{
  getNum : (price:number) => number
}

// 函数类型接口
interface discount2{
  // 注意:
  // “:” 前面的是函数的签名,用来约束函数的参数
  // ":" 后面的用来约束函数的返回值
  (price:number):number
}
let cost:discount2 = function(price:number):number{
   return price * .8;
}

// 也可以使用类型别名
type Add = (x: number, y: number) => number
let add: Add = (a: number, b: number) => a + b


10. 什么是类类型接口

  • 如果接口用于一个类的话,那么接口会表示“行为的抽象”
  • 对类的约束,让类去实现接口,类可以实现多个接口
  • 接口只能约束类的公有成员(实例属性/方法),无法约束私有成员、构造函数、静态属性/方法
// 接口可以在面向对象编程中表示为行为的抽象
interface Speakable {
    name: string;

         // ":" 前面的是函数签名,用来约束函数的参数
    // ":" 后面的用来约束函数的返回值
    speak(words: string): void
}

interface Speakable2 {
    age: number;
}

class Dog implements Speakable, Speakable2 {
    name!: string;
    age = 18;

    speak(words: string) {
        console.log(words);
    }
}

let dog = new Dog();
dog.speak('汪汪汪');


11. 什么是混合类型接口

  • 一个对象可以同时作为函数和对象使用
interface FnType {
    (getName:string):string;
}

interface MixedType extends FnType{
    name:string;
    age:number;
}
interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;


12. 什么是函数重载

  • 在 Java 中的函数重载,指的是两个或者两个以上的同名函数,参数类型不同或者参数个数不同。函数重载的好处是:不需要为功能相似的函数起不同的名称。
  • 在 TypeScript 中,表现为给同一个函数提供多个函数类型定义,适用于接收不同的参数和返回不同结果的情况。
  • TS 实现函数重载的时候,要求定义一系列的函数声明,在类型最宽泛的版本中实现重载(前面的是函数声明,目的是约束参数类型和个数,最后的函数实现是重载,表示要遵循前面的函数声明。一般在最后的函数实现时用 any 类型
  • 函数重载在实际应用中使用的比较少,一般会用联合类型或泛型代替
  • 函数重载的声明只用于类型检查阶段,在编译后会被删除
  • TS 编译器在处理重载的时候,会去查询函数申明列表,从上至下直到匹配成功为止,所以要把最容易匹配的类型写到最前面
function attr(val: string): string;
function attr(val: number): number;
// 前面两行是函数申明,这一行是实现函数重载
function attr(val: any): any {
    if (typeof val === 'string') {
        return val;
    } else if (typeof val === 'number') {
        return val;
    } 
}

attr('aaa');
attr(666);
  • 上面的写法声明完函数后,必须实现函数重载。也可以只声明函数
// 后写的接口中的函数声明优先级高
interface Cloner111 {
    clone(animal: Animal): Animal;
}
interface Cloner111 {
    clone(animal: Sheep): Sheep;
}
interface Cloner111 {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
}

// ==> 同名接口会合并
// 后写的接口中的函数声明优先级高
interface Cloner111 {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
    clone(animal: Sheep): Sheep;
    clone(animal: Animal): Animal;
}


interface Cloner222 {
        // 接口内部按书写的顺序来排,先写的优先级高
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
    clone(animal: Sheep): Sheep;
    clone(animal: Animal): Animal;
}


13. 什么是访问控制修饰符

class Father {
    str: string; // 默认就是 public
    public name: string;   // 在定义的类中、类的实例、子类、子类实例都可以访问
    protected age: number; // 只能在定义的类和子类中访问,不允许通过实例(定义的类的实例和子类实例)访问
    private money: number; // 只能在定义的类中访问,类的实例、子类、子类实例都不可以访问
    constructor(name: string, age: number, money: number) {
        this.name = name;
        this.age = age;
        this.money = money;
    }

    getName(): string {
        return this.name;
    }

    setName(name: string): void {
        this.name = name;
    }
}

const fa = new Father('aaa', 18, 1000);
console.log(fa.name);// aaa
console.log(fa.age);// error
console.log(fa.money);// error

class Child extends Father {
    constructor(name: string, age: number, money: number) {
        super(name, age, money);
    }

    desc() {
        console.log(`${this.name} ${this.age} ${this.money}`);
    }
}

let child = new Child('bbb', 18, 1000);
console.log(child.name);// bbb
console.log(child.age);// error
console.log(child.money);// error


14. 重写(override) vs 重载(overload)

  • 重写是指子类重写“继承”自父类中的方法 。虽然 TS 和JAVA 相似,但是 TS 中的继承本质上还是 JS 的“继承”机制—原型链机制
  • 重载是指为同一个函数提供多个类型定义
class Animal {
    speak(word: string): string {
        return '动作叫:' + word;
    }
}

class Cat extends Animal {
    speak(word: string): string {
        return '猫叫:' + word;
    }
}

let cat = new Cat();
console.log(cat.speak('hello'));

/**--------------------------------------------**/

function double(val: number): number
function double(val: string): string
function double(val: any): any {
    if (typeof val == 'number') {
        return val * 2;
    }
    return val + val;
}

let r = double(1);
console.log(r);


15. 继承 vs 多态

  • 继承:子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态:由继承而产生了相关的不同的类,对同一个方法可以有不同的响应
class Animal {
    speak(word: string): string {
        return 'Animal: ' + word;
    }
}

class Cat extends Animal {
    speak(word: string): string {
        return 'Cat:' + word;
    }
}

class Dog extends Animal {
    speak(word: string): string {
        return 'Dog:' + word;
    }
}

let cat = new Cat();
console.log(cat.speak('hello'));
let dog = new Dog();
console.log(dog.speak('hello'));


16. 什么是泛型

  • 泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时在去指定类型的一种特性。
  • 可以把泛型理解为代表类型的参数
// 我们希望传入的值是什么类型,返回的值就是什么类型
// 传入的值可以是任意的类型,这时候就可以用到 泛型

// 如果使用 any 的话,就失去了类型检查的意义
function createArray1(length: any, value: any): Array<any> {
    let result: any = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

let result = createArray1(3, 'x');
console.log(result);

// 最傻的写法:每种类型都得定义一种函数
function createArray2(length: number, value: string): Array<string> {
    let result: Array<string> = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

function createArray3(length: number, value: number): Array<number> {
    let result: Array<number> = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

// 或者使用函数重载,写法有点麻烦
function createArray4(length: number, value: number): Array<number>
function createArray4(length: number, value: string): Array<string>
function createArray4(length: number, value: any): Array<any> {
    let result: Array<number> = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray4(6, '666');

使用泛型

// 有关联的地方都改成 <T>
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

// 使用的时候再指定类型
let result = createArray<string>(3, 'x');

// 也可以不指定类型,TS 会自动类型推导
let result2 = createArray(3, 'x');
console.log(result);


17. 什么是类型谓词

  • 类型保护函数:要自定义一个类型保护,只需要简单地为这个类型保护定义一个函数即可,这个函数的返回值是一个类型谓词
  • 类型谓词的语法为 parameterName is Type 这种形式,其中 parameterName必须是当前函数签名里的一个参数名
interface Bird {
    fly()
    layEggs()
}
interface Fish {
    swim()
    layEggs()
}

function getSmallPet():Fish | Bird{
    return ;
}
let pet = getSmallPet();

pet.layEggs();
// 当使用联合类型时,如果不用类型断言,默认只会从中获取共有的部分
(pet as Fish).swim();
pet.swim();

image.png

interface Bird {
    fly()
    layEggs()
}
interface Fish {
    swim()
    layEggs()
}

function getSmallPet():Fish | Bird{
    return ;
}
let pet = getSmallPet();

// 使用类型谓词 
function isFish(pet:Fish | Bird):pet is Fish {
    return (pet as Fish).swim !== undefined;
}

if(isFish(pet)){
    pet.swim();
}else{
    pet.fly();
}

image.png


18. 可选链运算符的使用

  • 可选链运算符是一种先检查属性是否存在,再尝试访问该属性的运算符,其符号为 ?.
  • 如果运算符左侧的操作数 ?. 计算为 undefined 或 null,则表达式求值为 undefined 。否则,正常触发目标属性访问、方法或函数调用。
  • 可选链运算符处于 stage3 阶段,使用 @babel/plugin-proposal-optional-chaining 插件可以提前使用,TS 3.7版本正式支持使用,以前的版本会报错
a?.b;
// 相当于 a == null ? undefined : a.b;
// 如果 a 是 null/undefined,那么返回 undefined,否则返回 a.b 的值.

a?.[x];
// 相当于 a == null ? undefined : a[x];
// 如果 a 是 null/undefined,那么返回 undefined,否则返回 a[x] 的值

a?.b();
// 相当于a == null ? undefined : a.b();
// 如果 a 是 null/undefined,那么返回 undefined
// 如果 a.b 不是函数的话,会抛类型错误异常,否则计算 a.b() 的结果


19. 非空断言符的使用

  • TS 3.7版本正式支持使用
let root: any = document.getElementById('root');
root.style.color = 'red';

let root2: (HTMLElement | null) = document.getElementById('root');
// 非空断言操作符--> 这样写只是为了骗过编译器,防止编译的时候报错,打包后的代码可能还是会报错
root2!.style.color = 'red';


20. 空值合并运算符的使用

  • TS 3.7版本正式支持使用
  • `||` 运算符的缺点: 当左侧表达式的结果是数字 0 或空字符串时,会被视为false。
  • 空值合并运算符:只有左侧表达式结果为 `null` 或 `undefined` 时,才会返回右侧表达式的结果。通过这种方式可以明确地区分 `undefined、null` 与 `false` 的值
const data = {
    str:'',
    // num:0,
    flag:false,
    // flag: null,
};

// data.str 为 "" 时
let str1 = data.str || '空' // '空'
// data.num 为 0 时
let num1 =  data.num || 666 // 666
// data.flag 为 false 时
let status1 =  data.flag || true  // true


// data.str 为 "" 时,可以通过。仅在 str 为 undefined 或者 null 时,不可以通过
let st2r = data.str ?? '空';  
// data.num 为 0 时,可以通过。仅在 num 为 undefined 或者 null 时,不可以通过
let num2 = data.num ?? 666; 
// data.flag 为 false 时,可以通过。仅在 flag 为 undefined 或者 null 时,不可以通过
let status2 = data.flag ?? true;

console.log('str=>', str2);
console.log('num=>', num2);
console.log('status=>', status2);


总结

由于内容过长,分了上下两篇。本篇文章未完结,请关注下一篇:TypeScript 常见问题整理(60多个)「下」

推荐TypeScript知识点文章


深入TypeScript难点梳理讲解

Vue3.0之前你必须知道的TypeScript实战技巧

深入TypeScript难点梳理讲解

Vue3.0之前你必须知道的TypeScript实战技巧

你需要的 React + TypeScript 50 条规范和经验

TypeScript详细概括【思维导图】

Vue3.0 尝鲜 Hook TypeScript 取代 Vuex【项目实践】

TypeScript详细概括【思维导图】

「新消息」基于JavaScript/TypeScript 编程环境Deno1.0 即将发布

「干货」一张页面引起的项目架构思考(rax+Typescript+hooks)

深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】


作者: 秋天不落叶

转发链接:https://mp.weixin.qq.com/s/KmDLcJ0jhB667ZouDB8tyg

相关推荐

从IDEA开始,迈进GO语言之门(idea got)

前言笔者在学习GO语言编程的时候,GO语言在国内还没有像JAVA/Php/Python那样普及,绕了不少的弯路,要开始入门学习一门编程语言,最好就先从选择一个好的编程语言的开发环境开始,有了这个开发环...

基于SpringBoot+MyBatis的私人影院java网上购票jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于SpringBoot...

基于springboot的个人服装管理系统java网上商城jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...

基于springboot的美食网站Java食品销售jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...

贸易管理进销存springboot云管货管账分析java jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目描述贸易管理进销存spring...

SpringBoot+VUE员工信息管理系统Java人员管理jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍SpringBoot+V...

目前见过最牛的一个SpringBoot商城项目(附源码)还有人没用过吗

帮粉丝找了一个基于SpringBoot的天猫商城项目,快速部署运行,所用技术:MySQL,Druid,Log4j2,Maven,Echarts,Bootstrap...免费给大家分享出来前台演示...

SpringBoot+Mysql实现的手机商城附带源码演示导入视频

今天为大家带来的是基于SpringBoot+JPA+Thymeleaf框架的手机商城管理系统,商城系统分为前台和后台、前台用的是Bootstrap框架后台用的是SpringBoot+JPA都是现在主...

全网首发!马士兵内部共享—1658页《Java面试突击核心讲》

又是一年一度的“金九银十”秋招大热门,为助力广大程序员朋友“面试造火箭”,小编今天给大家分享的便是这份马士兵内部的面试神技——1658页《Java面试突击核心讲》!...

SpringBoot数据库操作的应用(springboot与数据库交互)

1.JDBC+HikariDataSource...

SpringBoot 整合 Flink 实时同步 MySQL

1、需求在Flink发布SpringBoot打包的jar包能够实时同步MySQL表,做到原表进行新增、修改、删除的时候目标表都能对应同步。...

SpringBoot + Mybatis + Shiro + mysql + redis智能平台源码分享

后端技术栈基于SpringBoot+Mybatis+Shiro+mysql+redis构建的智慧云智能教育平台基于数据驱动视图的理念封装element-ui,即使没有vue的使...

Springboot+Mysql舞蹈课程在线预约系统源码附带视频运行教程

今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的Springboot+Mysql舞蹈课程在线预约系统,系统项目源代码在【猿来入此】获取!https://www.yuan...

SpringBoot+Mysql在线众筹系统源码+讲解视频+开发文档(参考论文

今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的在线众筹管理系统,主要实现了普通用户在线参与众筹基本操作流程的全部功能,系统分普通用户、超级管理员等角色,除基础脚手架外...

Docker一键部署 SpringBoot 应用的方法,贼快贼好用

这两天发现个Gradle插件,支持一键打包、推送Docker镜像。今天我们来讲讲这个插件,希望对大家有所帮助!GradleDockerPlugin简介...

取消回复欢迎 发表评论: