首页 > 其他分享 >前端 Typescript 入门

前端 Typescript 入门

时间:2024-03-29 16:22:40浏览次数:20  
标签:Typescript console 入门 前端 number let log string name

前端 Typescript 入门

Ant design vue4.x 基于 vue3,示例默认是 TypeScript。比如 table 组件管理。

vue3 官网介绍也使用了 TypeScript,例如:响应式 API:核心

华为的鸿蒙OS(HarmonyOS)开发中也可以使用 TypeScript

本篇目的用于对 TS 进行扫盲

Tipts 路线图

ts 是什么

TS是TypeScript的缩写,由微软开发的一种开源的编程语言

以前官网说“ts 是 js 超级”,现在改为: TypeScript是具有类型语法的JavaScript。

目前 TypeScript 5.4 已经发布(2024-03) —— ts 官网

Tip:ts缺点:开发更费麻烦,要多写东西了,看个人取舍。

环境

基于笔者博文《vue3 入门》,就像这样:

<template>
  <section>
  </section>
</template>

<script  lang="ts" setup name="App">
// ts
</script>

<style>
</style>

也可以直接在ts在线运行环境进行。

推导类型和显示注解类型

TS = 类型 + javascript

ts 编译过程:

  • TypeScript源码 -> TypeScript AST
  • 类型检查器检查AST
  • TypeScript AST -> JavaScript 源码

显示注解类型,语法:value:type 告诉类型检查器,这个 value 类型是 type。请看示例:

<template>
  <p>{{ a }}</p>
  <p>{{ b }}</p>
  <p>{{ c }}</p>
</template>

<script  lang="ts" setup name="App">
// 显示注解类型
let a: number = 1 // a 是数字
let b: string = 'hello' // b 是字符串
let c: boolean[] = [true, false]; // 布尔类型数组
</script>

如果将 a 写成 let a: number = '3',vscode 中 a 就会出现红色波浪,移上去会看到提示:不能将类型“string”分配给类型“number”。

如果想让 typescript 推到类型,就去掉注解,让 ts 自动推导。就像这样:

// 推导类型
let a = 1 // a 是数字
let b = 'hello' // b 是字符串
let c = [true, false]; // 布尔类型数组

去掉注解后,类型并没有变。并且如果尝试修改 a 的类型,ts 也会报错。就像这样:

let a = 1 // a 是数字

// 尝试替换成字符串,vscode 会提示:不能将类型“boolean”分配给类型“number”。
a = true

Tip:有人说“最好让 ts 推导类型,少数情况才需要显示注解类型”。

另外虽然大量错误 ts 在编译时无法捕获,例如堆栈溢出、网络断连,这些属于运行时异常。ts 能做的是将 js 运行时的报错提到编译时。比如以下代码:

const obj = { width: 10, height: 15 };
// 提示 heigth 属性写错了
const area = obj.width * obj.heigth;

let a = 1 + 2
let b = a + 3
// 鼠标以上 c,可以看到 c 对应的类型
let c = {
  apple: a,
  banana: b
}

类型断言

请看示例:

let arr = [1, 2, 3]
// r 为3
const r = arr.find(item => item > 2)

// “r”可能为“未定义”。ts(18048)
// const r: number | undefined
r * 5 // {1}

行 {1} 处的 r 会错误提示,说 r 可能会是 undefined。

要解决这个问题,可以使用类型断言:用来告诉编译器一个值的具体类型,当开发者比编译器更了解某个值的具体类型时,可以使用类型断言来告诉编译器应该将该值视为特定的类型。

类型断言有两种形式,分别是尖括号语法as 语法。这里说一下 as 语法:value as type。请看示例:

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

上述示例改成这样,r 就不会报错了。

// 告诉编译器,r 一定是一个 number
const r = arr.find(item => item > 2) as number

r * 5

Tip:由于需要人为干预,所以使用起来要谨慎

基础类型

js 基本类型大概有这些:

let num1 = 10;

let str1 = 'Hello';

let isTrue = true;

let undefinedVar;

let nullVar = null;

const symbol1 = Symbol('description');

const bigIntNum = 9007199254740991n;

let notANumber = NaN;
let infinite = Infinity;

console.log(typeof num1); // number
console.log(typeof str1,); // string
console.log(typeof isTrue); // boolean
console.log(typeof undefinedVar); // undefined
console.log(typeof nullVar); // object
console.log(typeof symbol1); // symbol
console.log(typeof bigIntNum); // bigint
console.log(typeof notANumber); // number
console.log(typeof infinite); // number

ts 中基本类型有:

// let v1: String = 'a' - 大写 String 也可以
let v1: string = 'a'
let v2: number = 1
let v3: boolean = true
let v4: null = null
let v5: undefined = undefined

// 字符串或者null
let v6: string | null = null
// 错误:不能将类型“5”分配给类型“1 | 2 | 3”
let v7: 1 | 2 | 3 = 5
// 正确
let v8: 1 | 2 | 3 = 2

联合类型

数组

ts 数组有两种方法,看个人喜好即可。请看示例:


// 方式一
// 定义一个由数字组成的数组
let arr1: number[] = [2, 3, 4]

// 报错:不能将类型“string”分配给类型“number”
let arr2: number[] = [2, 3, 4, '']

// 方式二
let arr3: Array<string> = ['a', 'b', 'c']

// 报错:不能将类型“number”分配给类型“string”。
let arr4: Array<string> = ['a', 'b', 'c', 4]
元组

在 TypeScript 中,元组(Tuple)是一种特殊的数组类型,它允许您指定一个固定长度和对应类型的数组

let arr5:[string, number, string] = ['a', 1, 'b']

// 报错:不能将类型“[string, number]”分配给类型“[string, number, string]”。源具有 2 个元素,但目标需要 3 个。
let arr6:[string, number, string] = ['a', 1]

// 正确
arr6[0] = 'a2'
// 错误:不能将类型“number”分配给类型“string”。
arr6[0] = 1

// 第三个添加 ? 表明可选,这样只传入 2 个数也不会报错
let arr7:[string, number, string?] = ['a', 1, 'b']

枚举

枚举需要使用关键字 enum。请看示例:

// 就像定义对象,不过不需要 =
enum TestEnum {
  a,
  b,
  c,
}
// 1
console.log(TestEnum.b);
// b
console.log(TestEnum[1]);
// string
console.log(typeof TestEnum[1]);

ts 可以自动为枚举类型中的各成员推导对应数字。上面示例推导结果:

enum TestEnum {
  a = 0,
  b = 1,
  c = 2,
}

也可以自己手动设置:

enum TestEnum2 {
  a = 3,
  b = 13,
  c = 23,
}
// 13
console.log(TestEnum2.b);

比如这个,c 就是 b 的下一个数字:

enum TestEnum3 {
  a,
  b = 13,
  c,
}
// 14
console.log(TestEnum3.c);

使用场景:比如你之前根据订单状态写了如下代码,可以用枚举来增加可读性。

if(obj.state === 0){

}else if(obj.state === 1){

}else if(obj.state === 2){

}else if(obj.state === 3){

}
// 优化后
enum 订单状态{
  取消,
  上线,
  发送,
  退回,
  ...
}

if(obj.state === 订单状态.取消){

}else if(obj.state === 订单状态.上线){

}else if(obj.state === 订单状态.发送){

}else if(obj.state === 订单状态.退回){

}

函数

定义一个函数,参数报错:

// 参数 a 和 b报错。例如:a - 参数“a”隐式具有“any”类型。
function fn1(a, b){
  return a + b
}

定义参数类型:

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

定义参数 b 可选,返回值是 number类型。请看示例:

// b是可选。
// 必选的放左侧,可选的放后侧
function fn5(a: number, b?: number): number{
  return 10
}
// 应有 1-2 个参数,但获得 0 个。
fn5()

定义参数 a 的默认值,rest是一个字符串数组:

// a 有一个默认值 10
function fn7(a = 10, b?: number, ...rest:string[]): number{
  return 10
}

fn7(1,2, 'a', 'b')

void

通常用于函数,表示没有 return 的函数。

function fn3(a: number, b : number):void{
  // 不能将类型“number”分配给类型“void”。
  return a + b
}

function fn4(a: number, b : number): void{
  
}

接口

通常用于对象的定义。请看示例:

interface Person{
  name: string,
  age: number
}

const p: Person = {
  name: 'peng',
  age: 18
}

// 报错:类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性。ts(2741)
const p2: Person = {
  name: 'peng',
}

类型别名

比如定义了一个变量 v1,其类型可以是 number 或 string,但是好多地方都是这个类型:

let v1: number | string = 3

我们可以通过 type 定义一个别名。就像这样:

// 定义别名 Message
type Message = number | string
let v2: Message = 'hello'
// 报错:不能将类型“boolean”分配给类型“Message”
let v3: Message = true

泛型

比如定义如下一个处理 number 的函数:

function fn1(a: number, b:number): number[]{
  return [a, b]
}

假如以后想把这个函数作为一个通用函数,除了可以处理 number,还可以处理 string 等其他类型,比如:

function fn1(a: string, b:string): string[]{
  return [a, b]
}

a: string | number 又交叉了。就像这样:

function fn1(a: string | number, b:string | number): string[]{
  return [a, b]
}

这里可以使用泛型,请看示例:

// 定义一个变量,比如 T
function fn1<T>(a: T, b:T): T[]{
  return [a, b]
}

fn1<number>(11, 11)
fn1<string>('a', 'a')
// 正确,ts 会自动推导
fn1('a', 'a')

再看一个泛型示例:

// 参数 arr 是 T 类型的数组
// 返回 T 类型或 undefined
function firstElement<T>(arr: T[]): T | undefined {
    return arr[0];
}

firstElement(['a', 'b'])

函数重载

java 中函数重载是定义多个方法,调用时根据参数类型数量的不同执行不同的方法。例如下面定义两个 add:

// 方法重载示例:两个参数的相加
public int add(int a, int b) {
    return a + b;
}

// 方法重载示例:三个参数的相加
public int add(int a, int b, int c) {
    return a + b + c;
}

ts 这里重载和 java 中的有些不同,可以称之为函数重载申明

比如首先我们写了一个数字相加字符串相加的方法:

// 数字相加
// 字符串相加
function combine(x: number | string, y: number | string): number | string {
  if (typeof x === 'number' && typeof y === 'number') {
    return x + y;
  } else if (typeof x === 'string' && typeof y === 'string') {
    return x + y;
  }
  // 处理其他情况
  return 'Invalid input';
}

console.log(combine(1, 2)); // 输出:3
console.log(combine('hello', 'world')); // 输出:helloworld

这里有两个问题:

// 问题一:鼠标移动到 combine 显示:
// function combine(x: number | string, y: number | string): number | string
console.log(combine(1, 2));
console.log(combine('hello', 'world'));

// 问题二:传入 number和 string 不合法,但不报错。鼠标移动到 combine 显示:
// function combine(x: number | string, y: number | string): number | string
console.log(combine(1, 'two')); // 输出:Invalid input

现在加上函数重载申明,就能解决上述两个问题。请看示例:

// 函数重载
function combine(x: number, y: number): number;
// 变量名可以不是x、y
function combine(x2: string, y2: string): string;
function combine(x: number | string, y: number | string): number | string {
  // 不变
}

// function combine(x: number, y: number): number (+1 overload)
console.log(combine(1, 2));
// function combine(x: string, y: string): string (+1 overload)
console.log(combine('hello', 'world'));

// 报错:没有与此调用匹配的重载。
//   第 1 个重载(共 2 个),“(x: number, y: number): number”,出现以下错误。
//   第 2 个重载(共 2 个),“(x: string, y: string): string”,出现以下错误。ts(2769)
console.log(combine(1, 'two'))

接口继承

直接看示例:

interface Person{
  name: string,
  age: number
}

// Student 继承 Person
interface Student extends Person{
  school: string
}

// 提示p缺少3个属性
// 类型“{}”缺少类型“Student”中的以下属性: school, name, agets(2739)
const p: Student = {

}

Student 继承 Person,有了3个属性。

类的修饰符

类的修饰符有:public、private、protected、static、readonly...。用法请看下文:

比如有这样一段正常的js代码:

class People{
    constructor(name){
        this.name =name;
    }
    // 不需要逗号
    sayName(){
        console.log(this.name)
    }
}
let people = new People('aaron')
people.sayName() // aaron

放在 ts(比如 ts在线运行环境) 中会报错如下:

Parameter 'name' implicitly has an 'any' type.
Property 'name' does not exist on type 'People'.
Property 'name' does not exist on type 'People'.

需要修改如下两处即可消除所有错误:

 class People{
-    constructor(name){
+    // 消除ts报错:类型“People”上不存在属性“name”
+    name: string
+    constructor(name: string){       
         this.name =name;
     }
     // 不需要逗号

其中 name: string 的作用:声明 People 类有个必填属性。实例化 People 类的时候,必须传入一个 string 类型的 name 属性。

接着加一个可选属性 age:

  // 通过?将 age 改成可选。解决:属性“age”没有初始化表达式,且未在构造函数中明确赋值。
  age?: number

可以设置默认值:

  // 根据默认值推断类型,而且是必选属性
  money = 100

Tip:稍后我们会看到对应的 js 是什么样子。

属性默认是 public,自身可以用,继承的子类中也可以使用。public 还可以这么写,效果和上例等价:

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

另外还有 private 表明只能在类中使用。protected 只能在类和子类中使用。请看示例:

class People{
    ...
    // 属性默认是 public,自身可以用、继承也能用
    public money2 = 200
    private money3 = 300
    protected money4 = 400
    constructor(name: string){
        this.name =name;
    }
    sayName(){
        console.log(this.name)
    }
}
let people = new People('aaron')

console.log(people.money);

// 属性“money3”为私有属性,只能在类“People”中访问。ts(2341)
console.log('people.money3: ', people.money3); // 300
// 属性“money4”受保护,只能在类“People”及其子类中访问。ts(2445)
console.log('people.money4: ', people.money4); // 400

:虽然 vscode 报错,但浏览器控制台还是输出了。或许 ts 只是静态编译,对应的js 没有做特殊处理,比如 private 声明 money4,实际上并没有实现。请看ts在线运行环境 ts 对应的 js:

// ts
class People{
    name: string
    age?: number
    money = 100
    public money2 = 200
    private money3 = 300
    protected money4 = 400
    constructor(name: string){
        this.name =name;
    }
    sayName(){
        console.log(this.name)
    }
}
let people = new People('aaron')

console.log('people.money3: ', people.money3);
console.log('people.money4: ', people.money4);
// 对应的js
"use strict";
class People {
    constructor(name) {
        this.money = 100;
        this.money2 = 200;
        this.money3 = 300;
        this.money4 = 400;
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}
let people = new People('aaron');
console.log('people.money3: ', people.money3);
console.log('people.money4: ', people.money4);

js 中静态属性使用如下:

    protected money4 = 400
    // 静态属性
+   static flag = 110

console.log('People.flag: ', People.flag);

例如将静态属性设置成私有,只能在类中使用。请看示例:

  // 静态属性
  private static flag = 110

// 报错:属性“flag”为私有属性,只能在类“People”中访问。
console.log('People.flag: ', People.flag);

多个修饰符可以一起使用,但有时候需要注意顺序,vscode 也会给出提示。就像这样:

// “static”修饰符必须位于“readonly”修饰符之前。ts(1029)
readonly static flag = 110

比如定义一个静态只读属性:

  static readonly flag2 = 110

// 报错:无法为“flag2”赋值,因为它是只读属性。ts(2540)
People.flag2 = 111

类的存取器

感觉就是 js 的 get 和 set。比如下面就是一个 js 的get、set示例:

class People {
    constructor(name) {
        this.name = name;
    }
    get name() {
        return 'apple';
    }
    set name(v) {
        console.log('set', v);
    }
}
let people = new People('aaron') // set aaron

people.name = 'jia' // set jia
console.log(people.name); // apple

对应 ts 中的存取器就是这样:

class People{
    constructor(name: string){
        this.name = name;
    }
    get name(){
      return 'apple'
    }
    set name(v){
      console.log('set', v)
    }
}
let people = new People('aaron') // set aaron

people.name = 'jia' // set jia
console.log(people.name); // apple

注:这个例子很可能会栈溢出,就像这样:

class People{
    constructor(name: string){
        this.name = name;
    }
    get name(){
      return 'apple'
    }
    set name(v){
      console.log('v: ', v);
      // 栈溢出
      // 报错:VM47:10 Uncaught RangeError: Maximum call stack size exceeded
      this.name = v
    }
}
let people = new People('aaron')

所以可以这么写:

class People {
    private _name: string = ''
  
    get name(): string{
      return 'peng'
    }
  
    set name(val: string){
      this._name = val
    }
  }
  let people = new People()
  
  people.name

  // 报错:属性“_name”为私有属性,只能在类“People”中访问。ts(2341)
  people._name

不写类型,ts 也会自动推导,比如去除类型后也可以。就像这样:

// 自动推导类型
class People {
    private _name = 'peng'
  
    get name(){
      return 'peng'
    }
  
    set name(val){
      this._name = val
    }
  }
let people = new People()

抽象类

抽象类(abstract),不允许被实例化,抽象属性和抽象方法必须被子类实现。更像一个规范。请看示例

abstract class People {
  // 可以有抽象属性和方法
  abstract name: string
  abstract eat(): void
  // 也可以有普通属性和方法
  say() {
    console.log('hello: ' + this.name)
  }
}

// 如果不实现 name 和 eat 方法则报错
class Student extends People{
  name: string = '学生'

  // 既然没报错 - 抽象类中返回是 void,这里返回string
  eat(){
    return 'eat apple'
  }
}

const s1 = new Student()
s1.say()

console.log(s1.eat()); // eat apple

抽象类定义了一个抽象属性、一个抽象方法,一个具体方法。子类必须实现抽象属性和抽象方法,子类实例可以直接访问抽象类中具体的方法。请看对应的 js 代码,你就能很明白。

class People {
    say() {
        console.log('hello: ' + this.name);
    }
}
class Student extends People {
    constructor() {
        super(...arguments);
        this.name = '学生';
    }
    eat() {
        return 'eat apple';
    }
}
const s1 = new Student();
s1.say();
console.log(s1.eat());

类实现接口

前面我们用接口定义了一个类型:

interface Person{
  name: string,
  age: number
}

const p: Person = {
  name: 'peng',
  age: 18
}

抽象类如果只写抽象方法和属性,那么就和接口很相同了。另外接口用 interface 关键字定义,子类可以实现 implements(注意这个单词是复数) 多个接口(不能同时继承多个)。请看示例:

interface People {
  name: string
  eat(): void
}

interface A{
  age: number
}

// 实现两个接口,所有属性和方法都需要实现
class Student implements People, A{
  name: string = '学生'
  age = 100
  // 既然没报错
  eat(){
    return 'eat apple'
  }
}

const s1 = new Student()

console.log(s1.eat()); // eat apple

泛型类

使用类时,除了可以使用接口来规范行为,还可以将类和泛型结合,称为泛型类

比如现在 deal 是处理 string 的方法:

class People {
    value: string;

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

    deal(): string {
        return this.value;
    }
}

const p1 = new People('peng')
p1.deal()

后面我需要 deal 又能处理 number,这样就可以使用泛型。就像这样:

class People<T> {
    value: T;

    constructor(value: T) {
        this.value = value;
    }

    deal(): T {
        return this.value;
    }
}

const p1 = new People('peng')
p1.deal()

const p2 = new People(18)
p2.deal()

多个泛型写法如下:

class Pair<T, U> {
    private first: T;
    private second: U;

    constructor(first: T, second: U) {
        this.first = first;
        this.second = second;
    }

    public getFirst(): T {
        return this.first;
    }

    public getSecond(): U {
        return this.second;
    }
}

// 使用带有多个泛型类型参数的泛型类
let pair1 = new Pair<number, string>(1, "apple");
console.log(pair1.getFirst()); // 1
console.log(pair1.getSecond()); // apple

let pair2 = new Pair<string, boolean>("banana", true);
console.log(pair2.getFirst()); // banana
console.log(pair2.getSecond()); // true

其他

Error Lens:提供了一种更直观的方式来展示代码中的问题,如错误、警告和建议,以帮助开发者更快速地识别和解决问题。

vscode 直接安装后,会将红色错误提示直接显示出来,无需将鼠标移到红色波浪线才能看到错误提示。

标签:Typescript,console,入门,前端,number,let,log,string,name
From: https://www.cnblogs.com/pengjiali/p/18104072

相关文章

  • C++从入门到精通——函数重载
    函数重载前言一、函数重载概念二、函数重载的分类参数类型不同的函数重载参数个数不同的函数重载参数类型顺序不同的函数重载三、函数重载的具体代码展示main.cpp四、为什么为什么C++支持函数重载,而C语言不支持函数重载呢前言函数重载是指在同一个作用域内,可以定......
  • C++从入门到精通——缺省参数
    缺省参数前言一、缺省参数概念二、缺省参数分类位置参数的缺省参数全缺省参数半缺省参数关键字参数的缺省参数函数指针的缺省参数`lambda`表达式三、缺省参数的具体代码展示main.cpp前言缺省参数是在函数定义时指定的默认值,当调用函数时未提供该参数的值时,将使......
  • JavaScript快速入门笔记之七(String:字符串类型、RegExp:正则表达式)
    JavaScript快速入门笔记之七(String:字符串类型、RegExp:正则表达式)String:字符串类型什么是字符串?底层本质:一串字符组成的只读字符数组包装类型:临时封装原始类型数据,并提供对数据操作方法的对象——类型名和原始类型名相同!StringNumberBoolean何时使用:不必手动创建!......
  • 使用 CRXJS、Vite、TypeScript、React、Zustand、Antd 开发 Chrome 浏览器插件——自
    一、CRXJS一、什么是CRXJS?CRXJSVitePlugin是一款使用现代Web开发技术制作Chrome扩展的工具二、CRXJS的作用CRXJS支持热加载和静态资源导入,无需手动构建配置工具CRXJSVite插件通过将Vite的精细功能与简单的配置策略相结合,简化了Chrome扩展开发者体验二......
  • 14-前端工程化-Vue项目
      这里,我首先安装了一个nvm(node的版本管理工具),可以通过nvm管理多个版本的node.js,解决node.js各种版本存在不兼容的问题参考链接:nvm、node、vue安装教程_nvm安装vue_南柯ღk的博客-CSDN博客1)安装nvm Github的官方安装网址:https://github.com/coreybutler/nvm-windows/rel......
  • podman 入门实战
     一入编程深似海,从此节操是路人。最近使用podman,就想着写一篇总结性的笔记,以备后续参考。就如同写代码,不写注释,过了一段时间可能会想这是我写的吗?不会吧,还要理一下逻辑才能读懂,不利于后期维护。感觉整体体验下来,镜像获取、容器创建、容器监控、容器移除,和docker差不多,感觉可......
  • 为啥从我激情满满的要做前端开发,到现在不断的怀疑自己能不能学会?
    一开始,我对前端开发充满了热情和期待,认为只要努力就能掌握那些看似复杂的技能,比如HTML、CSS和JavaScript等基础技能,就能够构建出功能丰富、交互性强的网页应用。然而,随着学习的深入,我发现前端开发远比我想象的要复杂,这些挑战包括但不限于浏览器兼容性问题、性能优化、复杂的用户......
  • 前端学习-UI框架学习-Bootstrap5-012-进度条
    菜鸟教程链接创建一个基本的进度条的步骤如下:添加一个带有.progress类的。接着,在上面的内,添加一个带有class.progress-bar的空的。添加一个带有百分比表示的宽度的style属性,例如style="width:70%"表示进度条在70%的位置。注意:我发现如果这个contianer里面不放......
  • Bootloader/IAP零基础入门(1.1) —— 设计一个Bootloader引导进入APP的程序,包含中断向量
    前言(1)如果有嵌入式企业需要招聘湖南区域日常实习生,任何区域的暑假Linux驱动/单片机/RTOS的实习岗位,可C站直接私聊,或者邮件:[email protected],此消息至2025年1月1日前均有效(2)在上一章节中,我们详细介绍了如何让Bootloader引导进入APP程序。但是上一章节的工程是无法使用......
  • 前端学习-UI框架学习-Bootstrap5-011-徽章(Badges)
    菜鸟教程链接<template><divclass="containermt-3"><h3>徽章<spanclass="badgebg-success">new</span></h3><h3>药丸形状徽章<spanclass="badgebg-dangerrounded-pill">new</span&g......