TS入门学习资料

本文大量使用了TS入门学习资料的范例并且加以理解,但是也有省略很多的关键点,如需完整学习请移植上方链接

什么是 TypeScript&为什么选择TS

  • 包容

不会与js冲突,基本上直接引入js即可

  • 类型定义系统

完善js的弱类型,并且因为有类型定于 各大IDE也会有对应的提示

  • 拥抱ES6&最新的草案规划
  • TypeScript 编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件。
  • 巴拉巴拉什么社区完善啊 等等这些可以去看学习文档 这里不单独列出

劣势

  • 学习曲线高

虽然很强大,但是整个市场来说 对团队要求高导致ts目前阶段相比js还不算大范围的流行

  • 编译

需要额外编译,虽然写起来很爽,但是编译火葬场,如果开发团队全部用any类型的 等于没有用ts

  • 源码定义

如果前端团队中有人不了解ts 那么阅读源码就是一个学习成本的事情了

  • 复杂&构建流程

react和AJ可能是对TS支持最好的两个框架了,VUE&NODE.JS这两块还是有待完善

安装

  • npm install -g typescript

  • tsc xxx.ts

  • 编辑器

vscode webstorm balabala

vscode 调试 TypeScript

  • 我们学习测试的时候可以直接选择不编译为js,可以直接在vscode来调试ts文件
npm install typescript --save-dev
npm install ts-node --save-dev

废弃下面调试!!! 已经过时

配置 tsconfig.json

主要是将 sourceMap 设置为true。

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": true,
        "outDir": "./dist",
        "sourceMap": true
    },
    "include": [
        "src/**/*"
    ]
}

配置 launch.json

打开 DEBUG 界面,添加 配置
或者编辑 /.vscode/launch.json。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Current TS File",
            "type": "node",
            "request": "launch",
            "program": "${workspaceRoot}/node_modules/ts-node/dist/_bin.js",
            "args": [
                "${relativeFile}"
            ],
            "cwd": "${workspaceRoot}",
            "protocol": "inspector"
        }
    ]
}

最新调试方法

ts-node-gayhub

Visual Studio Code

Create a new node.js configuration, add -r ts-node/register to node args and move the program to the args list (so VS Code doesn't look for outFiles).

{
    "type": "node",
    "request": "launch",
    "name": "Launch Program",
    "runtimeArgs": [
        "-r",
        "ts-node/register"
    ],
    "args": [
        "${workspaceFolder}/index.ts"
    ]
}

直接配置为launch.json
其中args配置为自己想要调试的文件

Hello TypeScript

function sayTs(name:String){
    console.log(`我是:${name}`)    
}
sayTs('soul')

如果没有传递name为字符串比如number类型或者其他类型则会报错

原始数据类型

原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol。

Boolean

const isBoolean:Boolean = false;
console.log(typeof isBoolean);  // boolean

number

const isNumber:Number = 123;
console.log(typeof isNumber);  // number

string

const isString:String = 'soul';
console.log(typeof isString);  // string

void 空值

JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:

const isVoid:void = null;
console.log(typeof isVoid);  // object

声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null;

null在js中是object,不过我建议所有的定义类型都用小写,比如isVoid:void中的void用的是小写。大写其实是用到了js相关的基本类型

null&undefined

let u: undefined = undefined;
let n: null = null;
// 这样不会报错
let num: number = undefined;

null&undefined是所有类型的子集,所以你定义了一个number类型的 后面赋值为undefined是没什么问题的

any 任意值

如果是一个普通类型,在赋值过程中改变类型是不被允许的:

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

但如果是 any 类型,则允许被赋值为任意类型。

let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
  • -所以说 any类型的这个基本上跟你写js是一样的

类型推论

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

上面的代码虽然会编译报错,但是TS在分析你代码的时候 会推论成当前变量后面跟了一个字符串 所以还是会把此变量定义为string类型

联合类型

let isUnion:string|number|object;
isUnion = '123';
isUnion = {};
isUnion = 455;

通过定义的时候以|分隔 来定义当前变量可以允许多种类型

对象的类型——接口

interface User{
    name:string,
    token:string|number,
    info?:string // ?为可选属性 
    
    /* 
        [propName: string]: string;
         // 这里定义的为 任意属性,一个接口所有的字段都是需要全部匹配的 那么比如一个接口多了一个属性,这个属性没有定义
         // 但是又不想在后续添加属性的时候报错 这个时候就可以定义 没有匹配到的属性后续添加是什么类型的。
         // 这里指的注意的是 我设置为了string,所以接口里的所有定义必须是string的子类,比如我token写了一个string|number
         // number不是string的子类 所以在编译的时候就会报错
     */
    [propName: string]: any; 
    readonly id: number; // 只读属性 在接口第一次实现的时候就需要赋值, 比如 let u1:User = {id:233},这里已经实现好了 再次赋值就不行 比如u1.id=255;
    
}
let u2 = {
    id:233,
    name:'soul',
    info:'c',
    extra:'2'
}

console.log('u2',u2)

这里几个关键词

  • 只读属性
  • 可选属性
  • 接口定义的类型[propName]

数组类型

  • 创建数组
let array1:number[] = [1,2,3];
let array2:Array<number> = [4,5,6]

两种方式都可以创建数组,其中number规定了数组的子元素只能是number类型

  • 类数组
function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

arguments在js中不是一个数组类型,那么这个时候就可以来定义类数组了。

[index:number] // 规定索引为number类型,那么其中数组中的元素必须都得为number类型

事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等 这些已经是在ts内置对象中已经定义好了

function sum() {
    let args: IArguments = arguments;
}

函数的类型

/**
 * 
 * @param x 定义为数字类型
 * @param y 定义为数字类型
 */
function sum1(x: number, y: number): number {
    return x + y;
}
sum1(1, 2); // 传递的时候 参数必须匹配 多一个少一个都不行
// : number 规定了函数返回的类型为number

关键词

  • 函数参数类型定义
  • 函数返回定义

函数表达式

let mySum = function (x: number, y: number): number {
    return x + y;
};

上面虽然可以通过编译 但实际上mySum变量是通过ts类型推导出来的 严格意义的定义如下

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

这里的箭头函数跟es6的箭头函数不一样
在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

用接口定义函数输入&输出

// 接口形式定义
interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}
  • 可选参数&默认参数&剩余参数

// 可选参数
function t1(age:number,sex:string='男',name?:string,...res:any[]):string{
    console.log('res',res);
    return name+sex+age
}

console.log('t1',t1(18,undefined,'soul','t2','t3')) // soul男18

如果有默认值的话 自动为可选参数,通过用...扩展符则可以获取到剩余的传递的参数

类型断言

function getLength(something: string | number): number {
    if (something.length) {
        return something.length;
    } else {
        return something.toString().length;
    }
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.
// index.ts(3,26): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

在使用变量判断的时候 ts不知道当前值是string还是number,这个时候可以用到类型断言

function getLength(something: string | number): number {
    if ((<string>something).length) {
        return (<string>something).length;
    } else {
        return something.toString().length;
    }
}

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。

声明文件

新语法索引

由于本章涉及大量新语法,故在本章开头列出新语法的索引,方便大家在使用这些新语法时能快速查找到对应的讲解:

  • declare var 声明全局变量
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明(含有子属性的)全局对象
  • interface 和 type 声明全局类型
  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6 默认导出
  • export = commonjs 导出模块
  • export as namespace UMD 库声明全局变量
  • declare global 扩展全局变量
  • declare module 扩展模块
  • /// 三斜线指令

这里讲解的比较长,可以去→原文进行查看declaration-files

简单来说,首先讲解了d.ts文件是个什么东西,上面的指令并不会编译到js文件中,而是一种声明 只有有了这种声明 我们才能获得对应的代码补全、接口提示等功能。、

内置对象

TS中有很多内置对象,这样就免去了额外编写定义文件

ECMAScript 的内置对象

let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

DOM 和 BOM 的内置对象

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

其他的都可以参考TS核心库定义文件

类型别名

字面意思

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

字符串字面量类型

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'

// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

用来约束当前传递的参数值只能为上面定义的三种一种

元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

let tom: [string, number] = ['Tom', 25];

let soul1:[Array<string>,number];
soul1 = [['age'],18];
soul1.push(['c1'])
console.log('元组',soul1) // Array(3) [Array(1), 18, Array(1)]

接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。

let tom: [string, number];
tom = ['Tom', 25];

越界的元素

let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);

// Argument of type 'true' is not assignable to parameter of type 'string | number'.

添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型

枚举

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

enum soulEnum {
    sex,
    name= 'soul',
    age = 20,
    ex
    // extra = <any>"default extra" // 含字符串的枚举不能使用计算属性 所以这一行会报错
}

// 字符串赋值后 枚举的索引递增不是从0开始了 建议字符串赋值都在后
console.log('soulEnum[0]',soulEnum[0]) //  sex
console.log('soulEnum[3]',soulEnum[3]) //  undefined
console.log('soulEnum["name"]',soulEnum['name']) //  soul

关键词

  • 计算属性
  • 递增设置

TS-枚举

TS-类

这块概念也比较多 就不做搬运工了 有兴趣的可以去看原文

// 类

class SOUL {
    public name:string = 'soul' // 共有方法
    public sayExName(exName:string = 'ext-'):string{
        return exName +this.name;
    } // 共有函数
    private age:number = 18; // 私有属性 外部无法访问 也无法继承和实例化
    public readonly sex:string = '男' // 外部只读
    protected getAge():number{ // 子类可以访问 允许继承
        return this.age
    }
}

class REALSOUL extends SOUL{
    constructor(){
        super()
        console.log("年龄",this.getAge());
        console.log('性别',this.sex);
    }
}

const soulC1 = new REALSOUL();
console.log('额外名字',soulC1.sayExName())

// 抽象类

abstract class JOB{
    public jobName:string = '前端工程师'
    public abstract sayJob():string; // 抽象类不能实现此方法 需要子类实现 不能直接实例化抽象类
}

class SOULJOB extends JOB{
    constructor(){
        super()
    }
    sayJob():string{
        return this.jobName
    }
}

const souljob1 = new SOULJOB();
console.log('职业',souljob1.sayJob())

类与接口

之前学习过,接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述。

这一章主要介绍接口的另一个用途,对类的一部分行为进行抽象。

实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。

interface SoulInter {
    sayName():string;
}

interface SoulInter2 {
    name:string;
}

class MainSoul{

}

class SOUL1 extends MainSoul implements SoulInter,SoulInter2{
    name:'soul1'
    constructor(){
        super()
    }
    sayName(){
        console.log('实现接口定义')
        return ''
    }
}

class SOUL2 extends MainSoul implements SoulInter,SoulInter2{
    name:'soul2'
    constructor(){
        super()
    }
    sayName(){
        console.log('实现接口定义')
        return ''
    }
}

// 上方两个类都有同一个函数,那么可以抽离出来实现为接口 后续的类需要继承都需要实现接口的定义

// 继承两个接口
interface SoulInter3 extends SoulInter2,SoulInter { 
    name:string;
}

泛型

TS-泛型

  • -待补
// 泛型
// 返回的类型为输入的类型 S是随意定义的名称
function getArrayValue<S>(val:S):Array<S>{
    let res = [val,val];
    return res;
}
console.log('getArrayValue',getArrayValue('66'))

// 泛型约束
interface nameInter {
    name:string
}

// 传递的值中必须包含name属性
function getNameValue<S extends nameInter>(obj:S):Array<S>{
    let res = [obj,obj];
    return res;
}

console.log('getNameValue',getNameValue({name:'我的名字叫soul'}))