TypeScript学习

TypeScript学习

众所周知,JavaScript并不是完美的,不care类型既是优点,也是缺点,虽然开发起来比较方便,但是有时候因为类型不确定找起来还是比较麻烦,以及缺少一些其他的约束规范,因此有TypeScript这个东西,微软开发的,从字面理解,最重要的功能就是加上了Type类型,能够给变量指定参数了。这个层面是指的是文件代码管理层面的,并非是实际运行的时候,也就是TypeScript是不能直接运行的(应该是这样子的,不排除可以直接运行,但是目前大部分是再重新转换成JavaScript文件再运行),单纯编写TypeScript的话,需要安装一下。

npm install -g typescript

然后运行的话,需要使用tsc命令把.ts结尾编译成.js的文件,再运行.js

感觉是不是挺拉跨的,所以TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解,在编辑器里能直接报错,但tsc命令还是能够通过,并不影响相同语句的js文件运行,不过这就足够了。

在工程项目里,建议使用TypeScript,比如React和Vue3都在支持TypeScript。平时的刷算法可以直接使用JavaScript,毕竟那么短也不需要什么代码分析,算法才是关键。

因为TypeScript出来的比较早,那时候还没有ES6的语法,但是在TypeScript中实现了,这些ES6特性就不介绍了,毕竟JavaScript现在也差不多默认都支持ES6语法了,其中包括let、const、解构语法、class类。

放几个工具链接:

tsconfig.json

上来先讲tsconfig.json配置文件,有这个文件意味着这个目录是TypeScript项目的根目录,这个文件制定了用来编译这个项目的根文件和编译选项。建议放在根目录下,下面为一个示例

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules",
        "**/*.spec.ts"
    ]
}

compilerOptions可以省略,省略会用默认值编译选项。这里挑几个用过的讲讲

  • target——指定ECMAScript目标版本 "ES3"(默认), "ES5""ES6"/ "ES2015""ES2016""ES2017""ESNext"

  • module——指定生成哪个模块系统代码,Node.js一般就是CommonJS吧,或者ES6

  • noImplicitAny——在表达式和声明上有隐含的any类型报错

  • allowJs——允许编译JavaScript文件,一般配合checkJs一块使用

  • baseUrl——设置baseUrl来告诉编译器到哪里去查找模块。 所有非相对模块导入都会被当做相对于 baseUrlbaseurl的路径如果是相对路径,那么就是相对tsconfig.json文件。

  • paths——有些模块不是在baseUrl下的,所以用paths扩展,这里的路径是相当于baseUrl的路径。比如vue中,@/就是表示在src下的目录

    "paths": {
          "@/*": ["src/*"]
    }
    
  • declaration——生成对应的.d.ts文件

  • strict——启用所有严格类型检查选项,建议开启严格规范代码

  • moduleResolution——决定如何处理模块。或者是"Node"对于Node.js/io.js,或者是"Classic"(默认)

  • outDir——重定向输出目录

"files"指定一个包含相对或绝对文件路径的列表。 "include""exclude"属性指定一个文件glob匹配模式列表。 所以一般都用include。支持的glob通配符有:

  • * 匹配0或多个字符(不包括目录分隔符)
  • ? 匹配一个任意字符(不包括目录分隔符)
  • **/ 递归匹配任意子目录

watch,表示一直监听

类型注解

这个最简单,直接给例子

let isDone: boolean = false;	//布尔类型
let decLiteral: number = 6;		//数字类型
let name: string = "bob";		//字符串类型

/* 数组类型 有两种方式,我都用 */
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

/* 元组类型 不怎么常用,因为一般来说数组的类型基本上都是一致的 */
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error

/* 枚举类型,默认从0开始编号,也可以指定编号 */
enum Color {Red, Green, Blue} // enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green; // 此时c为0
//还可以反向映射,这个挺有用的,要注意的是 不会为字符串枚举成员生成反向映射。
let nameOfA = Color[c]; // "Red"


/* Any,这个最有用了,TypeSCript最终有可能变为AnyScript,哈哈 */
let list: any[] = [1, true, "free"];
let notSure: any = 4;
notSure.ifItExists(); // okay

/* Void类型,一般作为函数的返回值,是的,函数返回值也可以有类型,仿佛回到C++,go语言 */
function warnUser(): void {
    console.log("This is my warning message");
}

/* Nerver类型,表示永远不存在的值,这个自己写的时候不会用到,但是有时候调用库的时候会有这个类型 */
function error(message: string): never {
    throw new Error(message);
    // 或者 return error("Something failed");
}

// 还有 undefined、null、symbol、Object这些不怎么常用的类型,不说了

/* 类型断言 没用过 */
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// 或者用as
let strLength: number = (someValue as string).length;

接口

这其实是TypeScript第二常用的特性了,挺好用的,经常拿来指定对象属性的类型

interface LabelledValue {
	label: string;
	color?: string;	// 问号表示此属性可选
    readonly x: number;	//表示这个值只能在刚创建的时候有,之后不能被修改,没用过
	[propName: string]: any;	// 如果不是上述的属性,那么就是any值,没用过
}

function printLabel(labelledObj: LabelledValue) {
	console.log(labelledObj.label);
}

也可以用来指定函数的类型,比如传入的一个参数是函数,那么就可以指定了,参数名称并不需要和接口里的一样,好像C++也是这样子的?

interface SearchFunc {
  (source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
}

还有个索引签名,没见过也没用过,感觉很怪,不说了。

类类型,像Java一样,用implements,表示要实现的接口,这个接口只对实例部分进行类型检查,对于静态部分不检查,constructor不检查,反正我从来没用过。

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

接口也可以继承,也可以继承多个,也是比较有用的吧

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: 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;

还有一个接口集成类?感觉就很迷,把类所有的成员继承过来,包括它的成员但不包括实现,这就不展示了,感觉有点脱裤子放屁。

class类这个es6就有了,继承也有了,所以不展示了。

公有私有,这个JavaScript好像还是提案?反正没有正式的支持,默认都是public,私有private,保护protected

class Animal {
    public color: string;
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

抽象类,这个是JavaScript没有在语法上支持的,在ES6中也没有吧。

abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。在抽象类中定义的抽象方法,继承的子类必须要实现,普通方法可以不用去实现,而没有定义的抽象方法不能在子类中去定义,否则会报错。

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}

函数

完整的函数类型,不过一般不这么写,因为实在是太长了

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

一般写成这样子,只留个参数类型,返回的类型靠他自己去推断,只要你返回的是一个明确类型的值,就能够推断出来。然后前面的类型也不用写了,因为他自己会判断出来的,能省则省,否则代码太长了

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

剩余参数的类型

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

为this显式的指定类型,提供一个显式的 this参数。 this参数是个假的参数,它出现在参数列表的最前面。注意这里createCardPicker里面的返回值用箭头函数, 箭头函数能保存函数创建时的 this值,而不是调用时的值。这里deck.createCardPicker()创建了函数,由于是对象的方法调用,所以createCardPicker里面的this值就是Deck类型的对象,箭头函数保存了这个值,所以里面的this也是这个值(箭头函数本身没有this值,但是能够使用他外面函数的this值)

interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

还有函数重载,这个没用过,不说了

泛型函数,这个还能用一用。

// 定义泛型的函数
function identity<T>(arg: T): T {
    return arg;
}
// 调用1
let output = identity<string>("myString");  
// 或者根据类型推断
let output = identity("myString"); 

// 泛型约束,传入的类型必须具备length属性
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

高级类型

交叉类型?从来没用过,用&把多个类型叠加到一起成为一种类型

联合类型经常用,就是用(|)来分隔多个类型,只允许这几个类型中的其中一个

function padLeft(value: string, padding: string | number) {
    // ...
}

类型断言?

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

/* 为了让这段工作,可以用下面这个类型断言
let pet = getSmallPet();

// 每一个成员访问都会报错
if (pet.swim) {
    pet.swim();
}
else if (pet.fly) {
    pet.fly();
}
*/

let pet = getSmallPet();
// 类型断言
if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}

或者用类型保护,pet is Fish就是类型谓词。 谓词为 parameterName is Type这种形式, parameterName必须是来自于当前函数签名里的一个参数名。

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

// 'swim' 和 'fly' 调用都没有问题了
if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

类型别名,和C++的很像

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 Container<T> = { value: T };

接口和类型别名的差别难以描述,反正能用接口就用接口,用不了接口再用类型别名。

索引类型和字符串索引签名

interface Map<T> {
    [key: string]: T;
}
let keys: keyof Map<number>; // string
let value: Map<number>['foo']; // number

映射类型,这个看起来比较牛,好像在库中有见到过

比如想要对象中的每个属性都变成可选的。

type Partial<T> = {
    [P in keyof T]?: T[P];
}
type PersonPartial = Partial<Person>;

或者对象中的每个属性都变成只读的

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}
type ReadonlyPerson = Readonly<Person>;

模块解析

相对导入是以/./../开头的,其他的都是非相对的。

相对导入在解析时是相对于导入它的文件,并且不能解析为一个外部模块声明。 你应该为你自己写的模块使用相对导入,这样能确保它们在运行时的相对位置。

非相对模块的导入可以相对于baseUrl或通过下文会讲到的路径映射来进行解析。 它们还可以被解析成外部模块声明(.d.ts文件)。 使用非相对路径来导入你的外部依赖。

模块解析策略有两种:NodeClassicAMD | System | ES2015时的默认值为Classic,其他情况默认为Node。

Classic主要是为了向后兼容,相对导入就正常去找.ts.d.ts文件,非相对导入,就会从导入文件的目录一级一级往上找,指导根目录。

Node解析是参考了Node.js的文件解析,相对路径的话就去解析.ts.tsx.d.ts三个文件,按照文件名、文件夹名/package.json的types字段、文件夹名/index.ts来查找。讲的比较抽象,因为我懂了,因为简略一点。

命名空间

namespace关键词来定义,放在namespace里面的只会在命名空间内使用,如果命名空间外的想要使用,需要使用export关键词在里面导出。

多个文件也可以使用一个命名空间,然后通过三斜线引入。插播三斜线:三斜线只能通过在文件的开头出现,前面只能出现单行或者多行注释,不能出现语句。三斜线引用告诉编译器在编译过程中要引入的额外的文件。

/// <reference path="***.ts" />

.d.ts需要使用引入其他类型的文件,可以使用下面的语句

/// <reference types="..." />

/// <reference types="node" />
表示这个文件引用了@types/node/index.d.ts里面声明的名字

不要包含默认的库,比如lib.d.ts

/// <reference no-default-lib="true"/>

ok,插播完毕,回到命名空间。

由于每次使用命名空间导出的东西,都需要命名空间.东西这样子写法,可以使用import来取别名,注意别和import xxx = require() 模块混在一起,虽然长得很像。

namespace Shapes {
    export namespace Polygons {
        export class Triangle { }
        export class Square { }
    }
}

import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // Same as "new Shapes.Polygons.Square()"

命名空间和模块的区别,模块支持声明它的依赖,模块会把依赖添加到模块加载器。所以在我看来,模块吊打命名空间,没有什么必要使用命名空间。模块和命名空间只能二选一,所以选择模块。

声明文件

有些库他是JavaScript的库,并不能直接使用,需要导入相应的types库,或者写一个声明文件,也就是.d.ts后缀的文件。

全局变量的声明,使用declare var声明变量。 如果变量是只读的,那么可以使用 declare const。 你还可以使用 declare let如果变量拥有块级作用域。

declare var foo: number;

全局函数,使用declare function声明函数。

declare function greet(greeting: string): void;

带属性的对象,全局变量myLib包含一个makeGreeting函数, 还有一个属性 numberOfGreetings指示目前为止欢迎数量。

let result = myLib.makeGreeting("hello, world");
console.log("The computed greeting is:" + result);

let count = myLib.numberOfGreetings;

使用declare namespace描述用点表示法访问的类型或值。

declare namespace myLib {
    function makeGreeting(s: string): string;
    let numberOfGreetings: number;
}

函数重载getWidget函数接收一个数字,返回一个组件,或接收一个字符串并返回一个组件数组。

declare function getWidget(n: number): Widget;
declare function getWidget(s: string): Widget[];

接口

当指定一个欢迎词时,你必须传入一个GreetingSettings对象。 这个对象具有以下几个属性:

1- greeting:必需的字符串

2- duration: 可靠的时长(毫秒表示)

3- color: 可选字符串,比如‘#ff00ff’

interface GreetingSettings {
  greeting: string;
  duration?: number;
  color?: string;
}

declare function greet(setting: GreetingSettings): void;

经常使用的就是扩展window了

declare interface Window {
  log: any;
  writeFileSync: any;
  test: any;
  api: any;
}

类型别名

function getGreeting() {
    return "howdy";
}
class MyGreeter extends Greeter { }

greet("hello");
greet(getGreeting);
greet(new MyGreeter());

你可以使用类型别名来定义类型的短名:

type GreetingLike = string | (() => string) | MyGreeter;

declare function greet(g: GreetingLike): void;

组织类型

文档

greeter对象能够记录到文件或显示一个警告。 你可以为 .log(...)提供LogOptions和为.alert(...)提供选项。

代码

const g = new Greeter("Hello");
g.log({ verbose: true });
g.alert({ modal: false, title: "Current Greeting" });

声明

使用命名空间组织类型。

declare namespace GreetingLib {
    interface LogOptions {
        verbose?: boolean;
    }
    interface AlertOptions {
        modal: boolean;
        title?: string;
        color?: string;
    }
}

文档

你可以通过实例化Greeter对象来创建欢迎词,或者继承Greeter对象来自定义欢迎词。

代码

const myGreeter = new Greeter("hello, world");
myGreeter.greeting = "howdy";
myGreeter.showGreeting();

class SpecialGreeter extends Greeter {
    constructor() {
        super("Very special greetings");
    }
}

声明

使用declare class描述一个类或像类一样的对象。 类可以有属性和方法,就和构造函数一样。

declare class Greeter {
    constructor(greeting: string);

    greeting: string;
    showGreeting(): void;
}

外部模块

declare module

declare module "url" {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }

    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}

declare module "path" {
    export function normalize(p: string): string;
    export function join(...paths: any[]): string;
    export let sep: string;
}

然后使用

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");

简写,快速使用,声明一下这个模块就行了,导出的就都是any类型了,这个最常用了,哈哈哈。

declare module "hot-new-module";
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇