TypeScript

HaoOuBa
2021-03-09 / 2 评论 / 631 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2021年05月26日,已超过1295天没有更新,若内容或图片失效,请留言反馈。

Typescript的基本使用

Typescript的安装,通过npm全局安装Typescript
npm install typescript -g
编译Typescript的文件,typescript的文件以ts文件格式结尾,需要借助编译器编译成js文件后才可以运行,编译指令
tsc xxxx.ts
通过上面的方式编译,比较繁琐,因此都会在项目根目录中新建一个 tsconfig.json 的配置文件,用于设置ts文件的编译方式等
{
    /*
     *
     * 包含的路径
     * ** 所有目录,包括子目录, * 所有文件
     */
    "include": ["./src/**/*"],
    /*
     *
     * 不需要被编译的文件目录
     *
     */
    "exclude": [],
    /*
     *
     * 编译选项
     *
     */
    "compilerOptions": {
        /* 编译后的目标兼容版本 */
        "target": "ES6",
        /* 模块化规范 */
        "module": "ES6",
        /* 打包后目录 */
        "outDir": "./dist",
        /* 是否编译JS文件 */
        "allowJs": false,
        /* 是否检查JS代码语法规范 */
        "checkJs": false,
        /* 是否移除注释 */
        "removeComments": false,
        /* 不生产编译后的文件 */
        "noEmit": false,
        /* 当有错误时,不生产编译后的文件 */
        "noEmitOnError": true,
        /* 设置编译后的文件为严格模式 */
        "alwaysStrict": true,
        /* 不允许隐式的any类型 */
        "noImplicitAny": true,
        /* 不允许不明确类型的this */
        "noImplicitThis": true,
        /* 严格检查空值 */
        "strictNullChecks": true,
        /* 开启全部严格检查 */
        "strict": true
        // ...
    }
}

Typescript的基本语法

定义变量的基本类型
// 定义数字类型
const num: number = 1;
// 定义字符串类型
const str: string = 'joe';
// 定义布尔值类型
const bool: boolean = true

注意:nullundefined 有且只有1个值,因此声明变量为 nullundefined 后就不能再进行赋值操作

let _test1: null;
let _test2: undefined;
_test1 = 1;    // ×
_test2 = 1;    // ×

如果声明了一个变量,但是没有为这个值赋值,那么这个值默认为 undefined

let num: number;    // num的值为undefined

如果既没有声明类型,也没有赋值,那么这个值默认为 any 类型

let num;    // num被推导为any类型

nullundefined 在某些情况下,会有隐藏的问题。例如

let num: num = 1;
num = null;
num.toFixed(2);    // 这种情况下,编辑器不会报错,但是实际运行是有问题的

为了避免出现上面的情形,就需要对 nullundefined 开启严格检查模式,开启方式是在 tsconfig.json 文件内加入一行

{
    "compileOptions": {
        ...
        // 设置严格检查
        "strictNullChecks": true
    }
}
基本类型类型的包装类型

在上面定义基本变量的时候,: 后面跟的类型都为小写,小写表示这是一个基本类型的数据,如果大写,那么它表示为基本类型的包装类型,例如

let str: string = 'joe'    // 定义一个基本类型的字符串
str = new String("1")    // × new 出来的字符串,就是个包装类型,不能赋值给简单类型
定义变量为对象类型

对象内的属性比较少的时候,直接写成行内式

const obj: {x: number; y: string} = {
    x: 1,
    y: 'joe'
}

对象内的属性如果多的时候,写成 接口

// 使用 interface 关键字定义接口
interface MyObject {
    x: number;
    y: string;
    // 任意类型
    [prop: string]: any;
}
// 对象使用接口
const obj: MyObject {
    x: 1,
    y: 'joe'
}
定义变量为数组类型

typescript中数组的存储类型必须一致,所以在标注数组类型时,同时要标注数组中存储的数据类型

let arr: Array<number> = [1, 2, 3];    // 简写形式:let arr: number[] = ...
arr.push('11'); // 错误
arr.push(22); // 正确
定义变量为元组类型

元组类似数组,但是初始化的个数及对应位置,需要和标注的一致,并且越界数据,必须是标注中的类型之一

let arr: [string, number] = ['hello', 2];
arr.push('1'); // 正确
arr.push(1); // 正确
arr.push(true); // 越界数据类型错误
定义枚举类型
enum HTTP_CODE {
    SUCCESS = 200,
    ERROR = 201,
    URL
}
HTTP_CODE.URL; // 202

枚举类型的注意点:

  1. key不能是数字
  2. value可以是数字,称为数字类型枚举,如果没有值默认是0
  3. 枚举值可以省略,如果省略,则:

    • 第一个枚举值默认0
    • 非第一个枚举值为上一个数字枚举值 + 1
  4. 枚举值为只读(常量),定义后不可以被修改
定义任意类型

有时候并不确定这个值到底是什么类型,就可以标注为 any 类型

const a: any = 1;

any 类型的注意点:

  1. 一个变量申明了未赋值且未标注情况下,默认为 any
  2. 任何类型值都可以赋值给any类型
  3. any类型有任意属性和方法
  4. 标注为any类型,意味着放弃对该值的类型检查,同时也放弃了编辑器的提示
定义unknow类型

unknow类型属于安全版的any类型。例如,由于any可以赋值给任意类型,下面的demo,会报错,并且无法检查到

let a: any = '1';
let b: number = 1;
b.toFixed(2);
b = a;
// 无法检测出
b.toFixed(2);

换成 unknow 类型

let a: unknown = '1';
let b: number = 1;
b.toFixed(2);
b = a;  // 报错。不能将unknow类型赋值给其他类型
b.toFixed(2);

使用 unknow 的注意点:

  1. unknow类型只能赋值给unknow、any
  2. unknow类型没有任何属性和方法
定义函数无返回值类型

表示没有任何数据的类型,通常用于标注无返回值函数的返回值类型,函数默认标注类型为 void

function fn():void {
    console.log("joe")
}
定义函数为never类型

当一个函数永远不可能执行 return 的时候,返回的就是 never,与 void 不同,void是执行了 return ,只是没有值,never是不会执行 return,比如抛出错误,导致函数终止执行

function fn(): never {
    throw new Error('error');
}
定义函数类型的返回值

函数类型可以标注返回值类型、参数的类型,例如

function fn(x: number): number {
    return x;
}

接口的使用

接口的基本使用
interface Point {
    x: number;
    y: number;
}
const point: Point = {
    x: 1,
    y: 1
}
接口可选属性

接口可以定义可选的属性,使用 ? 进行标注, ? 表示该值是可选的

interface Point {
    x: number;
    y: number;
    z?: string;
}
接口属性只读

接口可以定义只读的属性,使用 readonly进行标注,标注后,表示该属性值不可被修改

inteface Point {
    readonly x: number;
    readonly y: number;
}
const point: Point = {
    x: 1,
    y: 2
}
接口任意类型
interface Point {
    x: number;
    y: string;
    [key: string]: any;
}
const point = {
    x: 1,
    y: 'hello',
    z: 1
}
通过接口描述函数
// 通过接口定义
interface Func {
    (e: Event): void;
}
const fn: Func = (e: Event) => {};

// 直接定义
function fn(el: HTMLElement, callback: (e: Event) => number) {}
const div = document.querySelector('div');
fn(div, function () {
    return 1;
});
接口重名合并

如果写了2个重名的接口,会将这2个接口进行合并

interface Point {
    x: number;
    y: number;
}
interface Point {
    z: string;
}
const point: Point = {
    x: 1,
    y: 1,
    z: '11'
};
联合类型

如果一个变量可以使用多个类型,可以使用 | 进行分割表示或

function fn(el: HTMLElement, attr: string, value: string | number) {}
const div = document.querySelector('div');
fn(div, 'a', 1);
交叉类型
interface O1 {
    x: number;
}
interface O2 {
    y: number;
}
const o: O1 & O2 = Object.assign({}, { x: 1 }, { y: 2 });

注意:上面会报错,说Object上面没有assign方法,此时只需要在tsconfig.json中,添加es的lib库即可解决

{
    "compilerOptions": {
        ...
        "lib": ["ES6"]
    }
}
字面量类型

在实际使用中,经常会有这种情况,只允许接受几个固定的值,其他的值不允许接受时,就可以使用字面量类型

function fn(direction: 'left' | 'right') {}
fn('left');
类型别名

继续上方的字面量类型,如果字面量类型的值过多,此时就应当使用类型别名

// 普通使用
type direction = 'left' | 'right';
function fn(direction: direction) {}
fn('left');

// 定义函数
type fn = (a: number) => number
类型推导

在实际开发中,每次定义变量,参数等都要写这些类型,很是麻烦,于是就有了类型推导,会自动根据上下文代码进行推导出类型

const a = '111'    // 此时,a的类型会自动认定为字符串类型
类型断言

默认的类型推导范围可能比较广,如果需要缩小范围,就需要使用类型断言

const img = <HTMLImageElement>document.querySelector('img');
if (img) {
    img.src = '';
}

函数标注

函数标注有以下几种方式

/* 第一种 */
function fn(a: number): number {
    return 1;
}
fn(1);
/* 第二种 */
const fn2: (a: number) => number = function (a) {
    return 1;
};
fn2(2);
/* 第三种 */
type CallBack = (num: number) => number;
const fn3: CallBack = function (num) {
    return 1;
};
fn3(1);
/* 第四种 */
interface Fn {
    (num: number): number;
}
let fn4: Fn = function (num) {
    return 1;
};
fn4(2);

可选参数

function fn(el: HTMLElement, attr: string, val?: number) {}
fn(document.querySelector('div'), 'width', 1);

参数默认值

function fn(attr: 'left' | 'right' = 'left') {}
fn('left');

剩余参数

interface Rest {
    [key: string]: any;
}
function fn(target: Array<number>, ...rest: Array<Rest>) {}
fn([1]);

函数中this标注方式

interface T {
    x: number;
    fn:(x: number) => void
}
const obj: T = {
    x: 1,
    fn(this: T, x: number){
        this.x
    }
}

函数重载

例如有这样一个例子,以下函数中,第一个参数为dom元素,第二个参数为display或opacity,第三个参数为数字或字符串

function setStyle(el: HTMLElement, attr: 'display' | 'opacity', val: 'block' | 'none' | number) {
    ...
}

上面的参数这样写没问题,但是想要实现传入display时,第三个值必须是block或none,于是就可以利用函数重载实现

function fn(el: HTMLElement, attr: 'display', val: 'block' | 'none'): void
function fn(el: HTMLElement, attr: 'opacity', val: number): void
function fn(el: HTMLElement, attr: string, val: any){}

类的基本使用
class Person {
    /* 定义在实例上的属性 */
    name = '杜恒';
    /* 定义在类上的属性 */
    static age = 18;
    /* 定义在实例上的方法 */
    static say() {
        console.log(1);
    }
}
const duHeng = new Person()
console.log(duHeng)
console.log(Person)
类的构造器
class Dog {
    name: string;
    age: number;
    /* 实例化对象时,会调用这个构造器函数 */
    constructor(name: string, age: number) {
        /* this指向当前实例化出的实例 */
        console.log(this);
        this.name = name;
        this.age = age;
    }
}
const WangCai = new Dog('旺财', 1);
const XiaoBai = new Dog('小白', 2);
console.log(WangCai);
console.log(XiaoBai);
在typescript中,定义类的静态属性,使用public关键字,使用后,会自动设置成参数,并且会赋值,上面的写法等同于下面的
class Dog {
    constructor(public name: string, public age: number) {
        // ...
    }
}
类里面的属性修饰符
class User {
    constructor(
        // 公开属性,默认public
        public host: number
        // 可以访问,但是不能修改
        readonly id: number,
        // 只能在内部和子类访问,但是不能被外部修改
        protected username: string,
        // 外部、子类都不可以访问,只有当前能够访问
        private password: string
    ) {
        
    }
}
const user = new User(1, 'Joe', '123')
user.id; // 可以访问
user.id = 1; // × 不可以被修改
user.username; // × 不可读
user.password; // × 不可读
上面的password设置成私有属性后,外部不可以被访问修改,但是如果就想让外部读取和修改,就需要使用到 寄存器
class User {
    constructor(private _password: string) {}

    // 用于设置私有属性密码
    set password(newPassword: string) {
        this._password = newPassword;
    }
    // 用于获取私有属性密码
    get password(): string {
        return '****';
    }
}
const user = new User('1');
user.password = '111';
user.password
类当中的静态成员
type FileList = 'gif' | 'png' | 'jpg'
class User {
    // static 定义的属性只存在于类中,不存在于实例中
    static readonly FILE_LIST: Array<FileList> = ['gif', 'png']
}
抽象类
/*
 *
 * 声明成抽象的类,无法new这个类
 * 抽象类就是专门用来被继承的类
 *
 */
abstract class Component {
    constructor(public props: any) {}
    /* 如果声明了方法为抽象的,那么必须声明这个类也是抽象的 */
    abstract render(): void;
}

class SonComponent extends Component {
    render() {}
}
new SonComponent(1);

规范类,实现类继承多个接口

interface Info {
    getInfo(): string;
}
class A implements Info {
    getInfo() {
        return '';
    }
}

泛型

很多时候,标注的具体类型不能确定,因此就需要使用到泛型

interface Inter {
    length: number;
}
function fn<T extends Inter>(obj: T) {
    return obj.length;
}
fn('sdasd');
const obj1 = {
    x: 1,
    y: 2
}
const obj2 = {
    a: 1,
    b: 2
}
function fn<T>(obj: T, k: keyof T) {
    return obj[k]
}
fn<typeof obj1>(obj1, 'x')
interface Res<T> {
    code: number;
    message?: string;
    data: T;
}

interface data {
    name: string;
    age: number;
}

async function getData<U>(url: string) {
    let response = await fetch(url);
    let data: Promise<Res<U>> = await response.json();
    return data;
}

(async function () {
    const res = await getData<data>('/user');
})();
6

评论 (2)

取消
  1. 头像
    罗小黑
    Android · QQ Browser

    画图

    回复
  2. 头像
    久畔
    Windows 10 · FireFox
    该回复疑似异常,已被系统拦截!
    回复