美文网首页
TypeScript 笔记

TypeScript 笔记

作者: 风之化身呀 | 来源:发表于2018-10-13 18:10 被阅读11次

    TypeScript 是 JS 的超集,强调变量类型。让JS更加接近强类型语言

    基础类型

    相比于原始JS,多了 any,void,never,元组,枚举

    let a:any = 'tom'; // 为编程阶段还不清楚类型的变量指定一个类型
    let b:function():void = function(){} // 当一个函数没有返回值时
    let c:function():never = function(){ throw Error('error')}  // 返回never的函数必须存在无法达到的终点
    let d:[string,number] =['tom',123]  // 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
    enum Color = {Green,Red,Blue}  // 枚举类型可以为一组数值赋予友好的名字
    
    // 其他:类型断言(即类型转换)
    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;
    // 或者
    let strLength: number = (<string>someValue).length;
    

    高级类型

    • 交叉类型 &
      交叉类型是将多个类型合并为一个类型
    function extend<T, U>(first: T, second: U): T & U {
        let result = <T & U>{};
        for (let id in first) {
            (<any>result)[id] = (<any>first)[id];
        }
        for (let id in second) {
            if (!result.hasOwnProperty(id)) {
                (<any>result)[id] = (<any>second)[id];
            }
        }
        return result;
    }
    
    • 联合类型 |
      联合类型表示一个值可以是几种类型之一,如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员
    function padLeft(value: string, padding: string | number) {
        // ...
    }
    
    • typeof 和 instanceof 类型保护
      TypeScript可以将二者识别为一个类型保护
    function padLeft(value: string, padding: string | number) {
        if (typeof padding === "number") {
            return Array(padding + 1).join(" ") + value;
        }
        if (typeof padding === "string") {
            return padding + value;
        }
        throw new Error(`Expected string or number, got '${padding}'.`);
    }
    
    // 类型为SpaceRepeatingPadder | StringPadder
    let padder: Padder = getRandomPadder();
    
    if (padder instanceof SpaceRepeatingPadder) {
        padder; // 类型细化为'SpaceRepeatingPadder'
    }
    if (padder instanceof StringPadder) {
        padder; // 类型细化为'StringPadder'
    }
    
    // 去除联合类型中的null,语法是添加!后缀:identifier!从identifier的类型里去除了null和undefined
    function fixed(name: string | null): string {
      function postfix(epithet: string) {
        return name!.charAt(0) + '.  the ' + epithet; // ok
      }
      name = name || "Bob";
      return postfix("great");
    }
    
    • 类型别名 type
      类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。通常用类型别名定义联合类型:
    type a = 'string' | 'number' | null
    

    接口 interface

    TypeScript的核心原则之一是对值所具有的结构进行类型检查,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。接口的2个作用:1、类型检查;2、被类继承,强制类实现某种契约

    // 基本语法(前 readonly 只读,后 ? 可选属性)
    interface 类型名称 {
       ...
    }
    
    interface SquareConfig {
        color?: string;
        readonly width?: number;
        [propName: string]: any;
        new (hour: number, minute: number);  // 构造器签名
    }
    // 普通对象类型检查器
    interface LabelledValue {
      label: string;
    }
    
    function printLabel(labelledObj: LabelledValue) {
      console.log(labelledObj.label);
    }
    
    let myObj = {size: 10, label: "Size 10 Object"};
    printLabel(myObj);
    
    // 函数类型检查器
    interface SearchFunc {
      (source: string, subString: string): boolean;
    }
    let mySearch: SearchFunc;
    mySearch = function(src, sub) {
        let result = src.search(sub);
        return result > -1;
    }
    
    // 数组类型 索引检查器
    class Animal {
        name: string;
    }
    class Dog extends Animal {
        breed: string;
    }
    // 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!(共有支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 )
    interface NotOkay {
        [x: number]: Animal;
        [x: string]: Dog;
    }
    
    // 类类型检查器(当一个类实现了一个接口时,只对其实例部分进行类型检查。 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;
    }
    
    let square = <Square>{};
    square.color = "blue";
    square.sideLength = 10;
    square.penWidth = 5.0;
    
    // 接口继承类(会继承到类的private和protected成员)
    class Control {
        private state: any;
    }
    
    interface SelectableControl extends Control {
        select(): void;
    }
    
    class Button extends Control implements SelectableControl {
        select() { }
    }
    
    class TextBox extends Control {
        select() { }
    }
    
    // Error: Property 'state' is missing in type 'Image'.
    class Image implements SelectableControl {
        select() { }
    }
    
    class Location {
    
    }
    
    // 泛型接口
    interface GenericIdentityFn<T> {
        (arg: T): T;
    }
    
    function identity<T>(arg: T): T {
        return arg;
    }
    
    let myIdentity: GenericIdentityFn<number> = identity;
    

    • 成员修饰符 public , protected, private
      所以成员默认都是public.
      private成员不能在类外使用,即不能在实例上以及子类上使用
      protected成员可以在子类中使用,不能在自身及其子类的实例上使用。不过可以通过子类的实例方法访问
      若把构造函数声明为protected,则该类只能被子类实例化,本身不能实例化
    • 只读属性 readonly
      只读属性必须在声明时或构造函数里被初始化
    • 参数属性
      参数属性可以方便地让我们在一个地方定义并初始化一个成员,通过用在构造函数上。参数属性通过给构造函数参数添加一个访问限定符来声明
    class Animal {
        constructor(private name: string) { }
        move(distanceInMeters: number) {
            console.log(`${this.name} moved ${distanceInMeters}m.`);
        }
    }
    
    • 抽象类
      abstract 类中的抽象方法必须被子类所实现
    abstract class Animal {
        abstract makeSound(): void;
        move(): void {
            console.log('roaming the earch...');
        }
    }
    

    函数

    传递给一个函数的参数个数必须与函数期望的参数个数一致

    • 可选参数和默认参数
      可选参数必须跟在必须参数后面
      带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入undefined值来获得默认值
    function buildName(firstName: string, lastName?: string) {
        if (lastName)
            return firstName + " " + lastName;
        else
            return firstName;
    }
    
    function buildName(firstName: string, lastName = "Smith") {
        return firstName + " " + lastName;
    }
    
    • 剩余参数
    function buildName(firstName: string, ...restOfName: string[]) {
      return firstName + " " + restOfName.join(" ");
    }
    
    let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
    
    • 函数重载
      为同一个函数提供多个函数类型定义来进行函数重载
    function pickCard(x: {suit: string; card: number; }[]): number;
    function pickCard(x: number): {suit: string; card: number; };
    function pickCard(x): any {
        // Check to see if we're working with an object/array
        // if so, they gave us the deck and we'll pick the card
        if (typeof x == "object") {
            let pickedCard = Math.floor(Math.random() * x.length);
            return pickedCard;
        }
        // Otherwise just let them pick the card
        else if (typeof x == "number") {
            let pickedSuit = Math.floor(x / 13);
            return { suit: suits[pickedSuit], card: x % 13 };
        }
    }
    

    泛型

    泛型即类型变量,它是一种特殊的变量,只用于表示类型而不是值

    function identity<T>(arg: T): T {
        return arg;
    }
    

    我们定义了泛型函数后,可以用两种方法使用。
    第一种是,传入所有的参数,包含类型参数:

    let output = identity<string>("myString");  // type of output will be 'string'
    

    第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:

    let output = identity("myString");  // type of output will be 'string'
    

    如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。

    • 泛型接口
    interface GenericIdentityFn<T> {
        (arg: T): T;
    }
    
    function identity<T>(arg: T): T {
        return arg;
    }
    
    let myIdentity: GenericIdentityFn<number> = identity;
    
    • 泛型类
    class GenericNumber<T> {
        zeroValue: T;
        add: (x: T, y: T) => T;
    }
    
    let myGenericNumber = new GenericNumber<number>();
    myGenericNumber.zeroValue = 0;
    myGenericNumber.add = function(x, y) { return x + y; };
    
    • 泛型约束
      在泛型中使用 extends 关键字
    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;
    }
    

    类型兼容性

    • 普通类型和对象
      要将y复制给x,必须保证x的每个属性都在y中,且类型匹配。对于y多余的属性则不管。
    interface Named {
        name: string;
    }
    
    let x: Named;
    let y = { name: 'Alice', location: 'Seattle' };
    x = y;
    
    • 函数兼容
      与上面刚好相反
    let x = (a: number) => 0;
    let y = (b: number, s: string) => 0;
    
    y = x; // OK
    x = y; // Error
    

    模块

    模块的导入导出和ES6一致,import export,但也有新增部分

    • 整体导出
      使用export =导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块。
    let numberRegexp = /^[0-9]+$/;
    class ZipCodeValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
    export = ZipCodeValidator;
    
    // 导入
    import zip = require("./ZipCodeValidator");
    
    • 使用外部模块
      node里大部分模块都不是TS写的,如果要用就需要为这些模块做个TS声明,一般放在.d.ts文件里:
    // node.d.ts
    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> node.d.ts并且使用import url = require("url");或import * as URL from "url"加载模块。

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

    假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。

    declare module "hot-new-module";
    import x, {y} from "hot-new-module"; // 该模块的导出所有类型都将会是any
    x(y);
    
    • 导入其他类型文件,如txt,json等
    declare module "*!text" {
        const content: string;
        export default content;
    }
    // Some do it the other way around.
    declare module "json!*" {
        const value: any;
        export default value;
    }
    
    // 
    import fileContent from "./xyz.txt!text";
    import data from "json!http://example.com/data.json";
    console.log(data, fileContent);
    
    • 扩展模块
    // map.ts
    import { Observable } from "./observable";
    declare module "./observable" {
        interface Observable<T> {
            map<U>(f: (x: T) => U): Observable<U>;
        }
    }
    Observable.prototype.map = function (f) {
        // ... another exercise for the reader
    }
    
    // consumer.ts
    import { Observable } from "./observable";
    import "./map";
    let o: Observable<number>;
    o.map(x => x.toFixed());
    
    
    // observable.ts
    export class Observable<T> {
        // ... still no implementation ...
    }
    
    declare global {  // 全局扩展
        interface Array<T> {
            toObservable(): Observable<T>;
        }
    }
    
    Array.prototype.toObservable = function () {
        // ...
    }
    

    一些操作符和关键字

    • keyof T 索引类型查询操作符
      keyof T的结果为T上已知的公共属性名的联合
    • T[K] 索引访问操作符
      T[K] 返回属性的类型
    • extend 继承
      接口继承接口,接口继承类,类继承类
    • implement 实现
      类实现接口
    • extends 泛型约束关键字
    • new
    function createInstance<A>(c: new () => A): A {
        return new c();
    }
    
    function createInstance<A>(c: {new(): T; }): A {
        return new c();
    }
    
    

    构建一个TS的环境

    https://tasaid.com/blog/20171102225101.html

    相关文章

      网友评论

          本文标题:TypeScript 笔记

          本文链接:https://www.haomeiwen.com/subject/xgxroftx.html