美文网首页
typescript学习笔记

typescript学习笔记

作者: 苦苦修行 | 来源:发表于2018-12-08 23:03 被阅读0次

    重点:

    1. 解构与指示类型有时很像
    2. 关于类的部分
    3. 有些时候会有语法错误,比如不能这样或那样调用。这其实都是ts的语法要求。我们发现,其实将所谓的错误代码编译成js后,是可以正常运行的。

    数组类型:

    let list: number[] = [1, 2, 3];

    let list: Array<number> = [1, 2, 3];

    元组 Tuple:数量、类型已知的 数组

    let x: [string, number];

    枚举类型:

    enum Color {Red = 1, Green = 2, Blue = 4}
    let c: Color = Color.Green;

    void、null、undefined类型

    object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。注意什么是原始类型、什么是非原始类型

    类型断言,好比类型转换

    使用尖括号 let strLength: number = (<string>someValue).length;
    使用as let strLength: number = (someValue as string).length;

    解构

    https://www.tslang.cn/docs/handbook/variable-declarations.html
    [first, second]: [number, number]
    给属性以不同的名字 let { a: newName1, b: newName2 } = o;
    令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
    let {a, b}: {a: string, b: number} = o;
    对上面令人困惑的地方,我自己的理解及总结:
    类似于let {a,b}=o的这种表达式,我们先从大的方面确定,这是一个解构表达式;
    然后,对于解构的属性的类型,不能在其大括号内声明,只能在其大括号内设置别名;
    若要进行类型声明,必须在其大括号外面。

    默认值

    let { a, b = 1001 } = wholeObject;

    函数声明+解构+默认值

    function f({ a, b = 0 } = { a: "" }): void {
     // ...
    }
    

    表示函数接收一个参数,可以将其解构成{a,b},如果传入的参数没有b这个属性,则使用属性b的默认值0。如果没传参数,则使用参数的默认值{ a: "" }

    展开,符号为... 注意:展开仅包含对象 自身的可枚举属性

    let first = [1, 2];
    let second = [3, 4];
    let bothPlus = [0, ...first, ...second, 5];

    let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
    let search = { ...defaults, food: "rich" };

    接口的作用

    为你代码中对象的结构类型命名
    接口其实就是对一组规则的命名
    不使用接口定义时的类型声明

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

    只读属性,关键字readonly
    在接口中如何使用

    interface Point {
     readonly x: number;
     readonly y: number;
    }
    let p1: Point = { x: 10, y: 20 };
    

    是不是也可以这样用,不定义接口

    let p1:{readonly x: number,readonly y: number} = { x: 10, y: 20 };
    

    索引签名。TypeScript支持两种索引签名:字符串和数字。

    interface SquareConfig {
     color?: string;
     width?: number;
     [propName: string]: any;// 这就是字符串索引签名
    }
    

    举一个数字索引签名的例子

    interface StringArray {
     [index: number]: string;
    }
    let myArray: StringArray;
    myArray = ["Bob", "Fred"];
    

    函数类型,就是描述“某个方法应该长成什么样子”

    定义

    interface SearchFunc {
     (source: string, subString: string): boolean;
    }
    

    使用

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

    感想
    可能只对声明一个函数变量然后赋值的这种情况有意义;对于ionic中的那种方法声明好像就没有意义了。

    类类型,就是规定类应该长成什么样子

    其实和java中的某个类实现某个接口没什么区别
    看例子

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

    实例类型

    let greeter: Greeter,意思是 Greeter类的实例的类型是 Greeter
    备注:这里的Greeter是一个类

    混合类型

    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 Control {
     private state: any;
    }
    

    以下接口继承了上面这个类,并定义了一个方法

    interface SelectableControl extends Control {
     select(): void;
    }
    

    这个继承关系会将Control类的所有公有及私有属性和方法都继承过来,但不包括其具体实现(接口只可能有定义,不可能有实现)
    因为接口可以继承类的私有成员,所以只有Control或Control的子类可以实现该接口。
    错误实例

    // 错误:“Image”类型缺少“state”属性。
    class Image implements SelectableControl {
     select() { }
    }
    

    类是具有两个类型的:静态部分的类型和实例的类型。

    当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。

    关于类

    1. Private定义的属性或方法只能在本类中使用,protected定义的属性或方法可以在本类及子类中使用。它们统统都不能在类之外或者说在实例中使用
    2. 如果不想让一个类被直接调用其构造方法将其实例化,可以将其构造方法定义为protect的或者private的
    3. 在类中使用存取器,也就是使用getter和setter,其目的就是在对属性存取值的时候,可以加上逻辑控制,禁止直接访问属性,也就是把属性设置为private的, 将getter和setter设置成public的,通过getter和setter访问private的属性。
    4. 通过查看tsc编译ts文件后的代码可以知道:本质上,定义一个类,其实就是声明了一个方法(比如这个类叫Animal,那么编译后的这个方法也叫Animal)。在类中定义的方法(比如这个方法叫fun),其实是定义在了其(也就是Animal的)原型对象上。如果我们在类中定义的属性或方法是静态的,也就是static的,本质上就是将其绑定在了Animal这个方法上(Animal.fun)
    5. 把类当接口使用
    6. let greeterMaker: typeof Greeter = Greeter;如何解释?(备注:Greeter是定义的一个类)声明了一个变量,将Greeter这个类赋值给它,那么这个类是什么类型的呢?它是(typeof Greeter )类型的。其实,我们定义的某个类,它也是类类型(有点拗口,也不知道这么说对不对)的一个实例。就像我们平时 let str: string = ‘hello’; 它的意思是声明了一个变量str,它的类型是string的,‘hello’是string这个类型的一个实例。

    泛型,本质就是在使用时,再决定类型,其实也相当于传参,只不过这个参数是个类型

    定义

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

    使用

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

    如何定义一个泛型类?(泛型接口类似)

    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; };
    

    泛型约束,就是约束泛型只能是哪一类的

    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;
    }
    

    枚举

    一组相关的变量应该考虑使用枚举来定义
    常量枚举

    const enum Enum {
        A = 1,
        B = A * 2
    }
    

    常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员。

    类型推断,本质就是TypeScript类型检查器帮我们确定类型

    就是在我们没有明确指定类型的情况下,TypeScript类型检查器帮我们推断出类型来,如果我们的调用有问题,就会给出我们提示。

    类型兼容性,本质就是类型不一样但结构具有某种相似,就可以互相赋值

    这是涉及到了名义类型结构子类型。名义类型就是我们在java中用到的那种类型之间互相赋值的规则。结构子类型就是通过判断结构是否类似以决定是否可以赋值。搞不明白,typescript为何要这样搞?

    高级类型

    我不是很待见它,后面想起来的时候再来补充吧!

    Symbols 原生类型!原生类型!原生类型!就像string等一样

    它有点类似于字符串,但每个又是唯一且不相等的,它的作用也就是起了一个符号作用。这个单词本身的意思也是符号。它的用法和字符串有一点类似。

    //Symbols是不可改变且唯一的。
    let sym2 = Symbol("key");
    let sym3 = Symbol("key");
    sym2 === sym3; // false, symbols是唯一的
    

    迭代器

    当一个对象实现了Symbol.iterator属性时,我们认为它是可迭代的

    for..of vs. for..in 语句

    for..of 迭代的是值,for..in 迭代的是键

    模块

    在一个模块里,我们可以有多个导出,就比如

    export * from "./StringValidator"; // exports interface StringValidator
    export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator
    export * from "./ZipCodeValidator";  // exports class ZipCodeValidator
    

    我们在导入时使用这样的语句

    import { ZipCodeValidator } from "./ZipCodeValidator";
    let myValidator = new ZipCodeValidator();
    

    有没有想过,其实{ ZipCodeValidator }是一种解构?
    以下是一种自动执行的导入:
    import "./my-module.js";
    我们平时用的ts的导出,应该类似于nodejs中的export.x = ,我们在ts中的export default x应该类似于nodejs中的export =。
    但是 export default 语法并不能兼容CommonJS和AMD的exports。
    怎么办?
    TypeScript提供了export =语法。若使用export =导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块。
    例子:

    //ZipCodeValidator.ts
    let numberRegexp = /^[0-9]+$/;
    class ZipCodeValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
    export = ZipCodeValidator;
    
    //Test.ts
    import zip = require("./ZipCodeValidator");
    
    // Some samples to try
    let strings = ["Hello", "98052", "101"];
    
    // Validators to use
    let validator = new zip();
    
    // Show whether each string passed each validator
    strings.forEach(s => {
      console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
    });
    

    所谓命名空间,其实就是为某些对象(whatever)的集合起了个名字,然后通过命名空间以点的形式访问这些对象。

    模块里不要使用命名空间,为什么,嵌套太多了。模块本身已经具有命名空间的作用了。命名空间适用于为那些全局变量包裹一层,然后这些变量就变成命名空间下的一层了。些时,命名空间就起到了为变量归类和避免变量名称冲突的作用。

    对于typescript来说,啥叫外部模块?就是不是用typescript语言编写的库,比如用javascript编写的jquery等。typescript是有类型检查的,jquery没有怎么办?我们这些开发人员给它定义类型呗。(后面的命名空间讲到,外部模块改叫模块了?内部模块,也就是用ts写的,改叫命名空间了?)

    举个例子,比如有个库叫path,我们可以这样定义它的类型

    declare module "path" { // 在命名空间讲到,不建议使用module了,而是使用namespace?
        export function normalize(p: string): string;
        export function join(...paths: any[]): string;
        export let sep: string;
    }
    

    这里的module关键字和命名空间作用差不多。其实就是起了个包裹的作用。那怎么用呢?

    /// <reference path="path.d.ts"/>
    import * as PATH from "path";
    

    这里为啥要用reference呢?是不是declare module "path"的话就不需要用到reference

    命名空间

    使用命名空间是为了提供逻辑分组和避免命名冲突。模块文件本身就是一个逻辑分组。
    模块是要用import或require引入的,命名空间不能这样用。

    declare是用来声明全局变量的?
    如果没有将命名空间导出,引入它时就要用reference,类似于使用<script>引入?

    相关文章

      网友评论

          本文标题:typescript学习笔记

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