typescript学习笔记(三)

webpack打包样式

  1. 安装包 npm i -D less less-loader css-loader style-loader postcss postcss-loader postcss-preset-env
    less-loader:用于加载.less文件,将less转化为css
    css-loader:用于加载.css文件,将css转化为commonjs
    style-loader:将样式通过<style>标签插入到head中
    postcss:一种对css编译的工具,类似babel对js的处理,常见的功能如:使用下一代css语法;自动补全浏览器前缀;自动把px代为转换成rem;css 代码压缩等等
  2. 修改 webpack.config.js
// ...
module.exports = {
	// ...
	module:{
		// ...
		rules:[
			// ...
			{
                test: /\.less/,
                use: [
                    "style-loader",
                    "css-loader",
                    // 引入postcss
                    {
                        loader: "postcss-loader",
                        options: {
                            postcssOptions: {
                                plugins:[
                                    ["postcss-preset-env", {
                                        browsers: "last 2 versions"
                                    } ]
                                ]
                            },
                        },
                    },
                    "less-loader",
                ],
            },
		],
	},
	//...
};

常用特殊符号

  1. ! 非空断言操作符
    具体而言,x! 将从 x 值域中排除 null 和 undefined,用于类型时,忽略 undefined 和 null 类型;用于函数时,调用函数时忽略 undefined 类型;
    因为 ! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。例如:
    const a: number | undefined = undefined;
    const b: number = a!;
    console.log(b);
    
    编译生成的 ES5 代码中,! 非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出 undefined。
  2. ?. 运算符
    TypeScript 3.7 实现了呼声最高的 ECMAScript 功能之一:可选链(Optional Chaining)。有了可选链后,我们编写代码时如果遇到 null 或 undefined 就可以立即停止某些表达式的运行。可选链的核心是新的 ?. 运算符,它支持以下语法:
    obj?.prop // 可选属性访问
    obj?.[expr]
    arr?.[index] // 可选元素访问
    func?.(args) // 可选函数访问
    
    (1)可选属性访问 举例:
    const val = a?.b;
    // 编译后
    var val = a === null || a === void 0 ? void 0 : a.b;
    // 自动检查对象 a 是否为 null 或 undefined,如果是立即返回 undefined,这样就可以立即停止某些表达式的运行
    
    注 意 \color{Red}{注意} :?. 与 && 运算符行为略有不同:
    && 专门用于检测 falsy 值,比如空字符串、0、NaN、null 和 false 等;
    ?. 只会验证对象是否为 null 或 undefined,对于 0 或空字符串来说,并不会出现 “短路”;
    (2)可选元素访问 举例
    function tryGetArrayElement(arr?: T[], index: number = 0) {
      return arr?.[index];
    }
    // 编译后
    "use strict";
    function tryGetArrayElement(arr, index) {
        if (index === void 0) { index = 0; }
        return arr === null || arr === void 0 ? void 0 : arr[index];
    }
    
    (3)可选函数访问 举例
    let result = obj.customMethod?.();
    // 编译后 
    var result = (_a = obj.customMethod) === null  || _a === void 0 ? void 0 : _a.call(obj);
    
    注意:
    (1) 如果存在一个属性名且该属性名对应的值不是函数类型,使用 ?. 仍然会产生一个异常
    (2) 可选链的运算行为被局限在属性的访问、调用以及元素的访问 —— 它不会沿伸到后续的表达式中,也就是说可选调用不会阻止 a?.b / someMethod() 表达式中的除法运算或 someMethod 的方法调用

3.?? 空值合并运算符
TypeScript 3.7 版本中除了引入可选链 ?. 之外,也引入了一个新的逻辑运算符 —— 空值合并运算符 ??。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。
与逻辑或 || 不同,逻辑或会在左操作数为 falsy 值时返回右侧操作数。也就是说,如果你使用 || 来为某些变量设置默认的值时,你可能会遇到意料之外的行为。比如为 falsy 值(”、NaN 或 0)时。

const foo = null ?? 'default string';
console.log(foo); // 输出:"default string"
const baz = 0 ?? 42;
console.log(baz); // 输出:0
// 编译后
"use strict";
var _a, _b;
var foo = (_a = null) !== null && _a !== void 0 ? _a : 'default string';
console.log(foo); // 输出:"default string"
var baz = (_b = 0) !== null && _b !== void 0 ? _b : 42;
console.log(baz); // 输出:0

(1) ?? 的短路

function A() { console.log('A was called'); return undefined;}
function B() { console.log('B was called'); return false;}
function C() { console.log('C was called'); return "foo";}

console.log(A() ?? C());
console.log(B() ?? C());
// 输出如下:
/*
A was called 
C was called 
foo 
B was called 
false
*/

当空值合并运算符的左表达式不为 null 或 undefined 时,不会对右表达式进行求值。

(2) ?? 不能与 && 或 || 并用

null || undefined ?? "foo"; // raises a SyntaxError	
// '||' and '??' operations cannot be mixed without parentheses.(5076)
true && undefined ?? "foo"; // raises a SyntaxError
// '&&' and '??' operations cannot be mixed without parentheses.(5076)

(null || undefined ) ?? "foo"; // 返回 "foo"

若空值合并运算符 ?? 直接与 AND(&&)和 OR(||)操作符组合使用 ?? 是不行的。这种情况下会抛出 SyntaxError。但当使用括号来显式表明优先级时是可行的。

(3) ?? 与可选链操作符 ?. 的关系

interface Customer {
  name: string;
  city?: string;
}	
let customer: Customer = {
  name: "Semlinker"
};	
let customerCity = customer?.city ?? "Unknown city";
console.log(customerCity); // 输出:Unknown city
  1. ?: 可选属性
    TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
    (1)工具类型
    • Partial<T> 快速把某个接口类型中定义的属性变成可选的

      interface PullDownRefreshConfig {
        threshold: number;
        stop: number;
      }
      
      /**
       * type PullDownRefreshOptions = {
       *   threshold?: number | undefined;
       *   stop?: number | undefined;
       * }
       */ 
      type PullDownRefreshOptions = Partial<PullDownRefreshConfig>
      

      该工具类实现方法:

      type Partial<T> = {
        [P in keyof T]?: T[P];
      };
      
    • Required<T> 把接口中所有的可选的属性变成必选的
      该工具实现方法:

      /**
       * Make all properties in T required
       */
      type Required<T> = {
        [P in keyof T]-?: T[P]; // 通过 -? 移除了可选属性中的 ?
      };
      
  2. & 运算符, 将多个类型合并为一个类型
    (1) 同名基础类型属性的&合并
    interface X {
      c: string;
      d: string;
    }
    interface Y {
      c: number;
      e: string
    }
    type XY = X & Y;
    type YX = Y & X;
    let p: XY;
    let q: YX;
    // 合并后成员 c 的类型为 string & number,这种类型是不存在的,
    // 所以合并后成员 c 的类型为 never
    
    (2)同名非基础类型属性的&合并
    interface D { d: boolean; }
    interface E { e: string; }
    interface F { f: number; }
    	
    interface A { x: D; }
    interface B { x: E; }
    interface C { x: F; }
    
    type ABC = A & B & C;
    
    let abc: ABC = {
      x: {
        d: true,
        e: 'semlinker',
        f: 666
      }
    };	
    console.log('abc:', abc);
    // 多个类型合并时,若存在相同名称的成员,且成员类型为非基本数据类型,那么是可以成功合并。
    
  3. | 分隔符
    联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型
    const sayHello = (name: string | undefined) => { /* ... */ };
    sayHello("semlinker");
    sayHello(undefined);
    let num: 1 | 2 = 1; // 字面量类型,用来约束取值只能是某几个值中的一个
    type EventNames = 'click' | 'scroll' | 'mousemove';
    
    (1)类型保护
    当使用联合类型时,我们必须尽量把当前值的类型收窄为当前值的实际类型,而类型保护就是实现类型收窄的一种手段。
    四种方式:
    • in 关键字
      interface Admin {
        name: string;
        privileges: string[];
      }
      
      interface Employee {
        name: string;
        startDate: Date;
      }
      
      type UnknownEmployee = Employee | Admin;
      
      function printEmployeeInformation(emp: UnknownEmployee) {
        console.log("Name: " + emp.name);
        if ("privileges" in emp) {
          console.log("Privileges: " + emp.privileges);
        }
        if ("startDate" in emp) {
          console.log("Start Date: " + emp.startDate);
        }
      }
      
    • typeof 关键字
      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}'.`);
      }
      
      注意:typeof 类型保护只支持两种形式:typeof v === "typename"typeof v !== typename,“typename” 必须是 “number”, “string”, “boolean” 或 “symbol”。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
    • instanceof 关键字
      interface Padder {
        getPaddingString(): string;
      }
      
      class SpaceRepeatingPadder implements Padder {
        constructor(private numSpaces: number) {}
        getPaddingString() {
          return Array(this.numSpaces + 1).join(" ");
        }
      }
      
      class StringPadder implements Padder {
        constructor(private value: string) {}
        getPaddingString() {
          return this.value;
        }
      }
      
      let padder: Padder = new SpaceRepeatingPadder(6);
      
      if (padder instanceof SpaceRepeatingPadder) {
        // padder的类型收窄为 'SpaceRepeatingPadder'
      }
      
    • 自定义类型保护的类型谓词(type predicate 类型断言)
      function isNumber(x: any): x is number {
        return typeof x === "number";
      }
      
      function isString(x: any): x is string {
        return typeof x === "string";
      }
      
  4. _ 数字分隔符
const inhabitantsOfMunich = 1_464_301;
const distanceEarthSunInKm = 149_600_000;
const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
// 编译后 
"use strict";
var inhabitantsOfMunich = 1464301;
var distanceEarthSunInKm = 149600000;
var fileSystemPermission = 504;
var bytes = 262926349;
  • 使用限制
// Numeric separators are not allowed here.(6188)
3_.141592 // Error
3._141592 // Error

// Numeric separators are not allowed here.(6188)
1_e10 // Error
1e_10 // Error

// Cannot find name '_126301'.(2304)
_126301  // Error
// Numeric separators are not allowed here.(6188)
126301_ // Error

// Cannot find name 'b111111000'.(2304)
// An identifier or keyword cannot immediately follow a numeric literal.(1351)
0_b111111000 // Error

// Numeric separators are not allowed here.(6188)
0b_111111000 // Error

// Multiple consecutive numeric separators are not permitted.(6189)
123__456 // Error 不能连续使用多个_
  • 不支持分隔符的函数
    Number()
    parseInt()
    parseFloat()
    解决办法:使用字符串的 replace() 移除分隔符
  1. <Type>语法
    (1)类型断言的两种方式:

    • 尖括号
      let someValue: any = "this is a string";
      let strLength: number = (<string>someValue).length;
      
    • as 语法
      let someValue: any = "this is a string";
      let strLength: number = (someValue as string).length;
      

    (2) 泛型
    T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
    K(Key):表示对象中的键类型;
    V(Value):表示对象中的值类型;
    E(Element):表示元素类型

    function identity <T, U>(value: T, message: U) : T {
      console.log(message);
      return value;
    }
    
    console.log(identity(68, "Semlinker"));
    
  2. @XXX 装饰器
    @Plugin({…}) ,装饰器语法,装饰器的本质是一个函数,通过装饰器我们可以方便地定义与对象相关的元数据。

    @Plugin({
      pluginName: 'Device',
      plugin: 'cordova-plugin-device',
      pluginRef: 'device',
      repo: 'https://github.com/apache/cordova-plugin-device',
      platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],
    })
    @Injectable()
    export class Device extends IonicNativePlugin {}
    // 编译后
    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    };
    
    var Device = /** @class */ (function (_super) {
        __extends(Device, _super);
        function Device() {
            return _super !== null && _super.apply(this, arguments) || this;
        }
        Device = __decorate([
            Plugin({
                pluginName: 'Device',
                plugin: 'cordova-plugin-device',
                pluginRef: 'device',
                repo: 'https://github.com/apache/cordova-plugin-device',
                platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],
            }),
            Injectable()
        ], Device);
        return Device;
    }(IonicNativePlugin));
    /*
    通过生成的代码可知,@Plugin({...}) 和 @Injectable() 最终会被转换成普通的方法调用,它们的调用结果最终会以数组的形式作为参数传递给 __decorate 函数,而在 __decorate 函数内部会以 Device 类作为参数调用各自的类型装饰器,从而扩展对应的功能
    */
    

    装饰器分为类装饰器、属性装饰器、方法装饰器和参数装饰器四大类
    (1)类装饰器

    // 类装饰器声明
    declare type ClassDecorator = <TFunction extends Function>(
      target: TFunction
    ) => TFunction | void;
    // 例子
    function Greeter(target: Function): void {
      target.prototype.greet = function (): void {
        console.log("Hello Semlinker!");
      };
    }	
    @Greeter
    class Greeting {
      constructor() {
        // 内部实现
      }
    }	
    let myGreeting = new Greeting();
    myGreeting.greet(); // console output: 'Hello Semlinker!';
    

    (2) 属性装饰器

    // 属性装饰器声明
    declare type PropertyDecorator = (target:Object, propertyKey: string | symbol ) => void;
    // 例子
    function logProperty(target: any, key: string) {
      delete target[key];
      const backingField = "_" + key;
      Object.defineProperty(target, backingField, {
        writable: true,
        enumerable: true,
        configurable: true
      });
    
      // property getter
      const getter = function (this: any) {
        const currVal = this[backingField];
        console.log(`Get: ${key} => ${currVal}`);
        return currVal;
      };
    
      // property setter
      const setter = function (this: any, newVal: any) {
        console.log(`Set: ${key} => ${newVal}`);
        this[backingField] = newVal;
      };
    
      // Create new property with getter and setter
      Object.defineProperty(target, key, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true
      });
    }
    
    class Person { 
      @logProperty
      public name: string;
    
      constructor(name : string) { 
        this.name = name;
      }
    }
    
    const p1 = new Person("semlinker");
    p1.name = "kakuqo";
    

    (3) 方法装饰器

    // 声明
    declare type MethodDecorator = &lt;T&gt;(target:Object, propertyKey: string | symbol, 
    descriptor: TypePropertyDescript&lt;T&gt;) =&gt; TypedPropertyDescriptor&lt;T&gt; | void;
    // 例子
    function LogOutput(tarage: Function, key: string, descriptor: any) {
      let originalMethod = descriptor.value;
      let newMethod = function(...args: any[]): any {
        let result: any = originalMethod.apply(this, args);
        if(!this.loggedOutput) {
          this.loggedOutput = new Array<any>();
        }
        this.loggedOutput.push({
          method: key,
          parameters: args,
          output: result,
          timestamp: new Date()
        });
        return result;
      };
      descriptor.value = newMethod;
    }
    
    class Calculator {
      @LogOutput
      double (num: number): number {
        return num * 2;
      }
    }
    
    let calc = new Calculator();
    calc.double(11);
    // console ouput: [{method: "double", output: 22, ...}]
    console.log(calc.loggedOutput);
    

    (4) 参数装饰器

    // 声明
    declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, 
    parameterIndex: number ) => void
    // 例子
    function Log(target: Function, key: string, parameterIndex: number) {
      let functionLogged = key || target.prototype.constructor.name;
      console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
        been decorated`);
    }
    
    class Greeter {
      greeting: string;
      constructor(@Log phrase: string) {
        this.greeting = phrase; 
      }
    }
    
    // console output: The parameter in position 0 
    // at Greeter has been decorated
    
  3. #XXX 私有字段

    class Person {
      #name: string;
    
      constructor(name: string) {
        this.#name = name;
      }
    
      greet() {
        console.log(`Hello, my name is ${this.#name}!`);
      }
    }
    
    let semlinker = new Person("Semlinker");
    
    semlinker.#name;
    //     ~~~~~
    // Property '#name' is not accessible outside class 'Person'
    // because it has a private identifier.
    

    与常规属性(甚至使用 private 修饰符声明的属性)不同,私有字段要牢记以下规则:

    • 私有字段以 # 字符开头,有时我们称之为私有名称;
    • 每个私有字段名称都唯一地限定于其包含的类;
    • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
    • 私有字段不能在包含的类之外访问,甚至不能被检测到。
    class Person {
      #name: string;
    
      constructor(name: string) {
        this.#name = name;
      }
    
      greet() {
        console.log(`Hello, my name is ${this.#name}!`);
      }
    }
    
    // 编译后
    "use strict";
    var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) 
      || function (receiver, privateMap, value) {
        if (!privateMap.has(receiver)) {
          throw new TypeError("attempted to set private field on non-instance");
        }
        privateMap.set(receiver, value);
        return value;
    };
    
    var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) 
      || function (receiver, privateMap) {
        if (!privateMap.has(receiver)) {
          throw new TypeError("attempted to get private field on non-instance");
        }
        return privateMap.get(receiver);
    };
    
    var _name;
    class Person {
        constructor(name) {
          _name.set(this, void 0);
          __classPrivateFieldSet(this, _name, name);
        }
        greet() {
          console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);
        }
    }
    _name = new WeakMap();
    /* 使用 # 号定义的 ECMAScript 私有字段,会通过 WeakMap 对象来存储,同时编译器会生成 __classPrivateFieldSet 和 __classPrivateFieldGet 这两个方法用于设置值和获取值 */
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值