webpack打包样式
- 安装包
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 代码压缩等等 - 修改 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",
],
},
],
},
//...
};
常用特殊符号
- ! 非空断言操作符
具体而言,x! 将从 x 值域中排除 null 和 undefined,用于类型时,忽略 undefined 和 null 类型;用于函数时,调用函数时忽略 undefined 类型;
因为 ! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。例如:
编译生成的 ES5 代码中,! 非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出 undefined。const a: number | undefined = undefined; const b: number = a!; console.log(b);
- ?. 运算符
TypeScript 3.7 实现了呼声最高的 ECMAScript 功能之一:可选链(Optional Chaining)。有了可选链后,我们编写代码时如果遇到 null 或 undefined 就可以立即停止某些表达式的运行。可选链的核心是新的 ?. 运算符,它支持以下语法:
(1)可选属性访问 举例:obj?.prop // 可选属性访问 obj?.[expr] arr?.[index] // 可选元素访问 func?.(args) // 可选函数访问
注 意 \color{Red}{注意} 注意:?. 与 && 运算符行为略有不同:const val = a?.b; // 编译后 var val = a === null || a === void 0 ? void 0 : a.b; // 自动检查对象 a 是否为 null 或 undefined,如果是立即返回 undefined,这样就可以立即停止某些表达式的运行
&& 专门用于检测 falsy 值,比如空字符串、0、NaN、null 和 false 等;
?. 只会验证对象是否为 null 或 undefined,对于 0 或空字符串来说,并不会出现 “短路”;
(2)可选元素访问 举例
(3)可选函数访问 举例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]; }
注意: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
- ?: 可选属性
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]; // 通过 -? 移除了可选属性中的 ? };
-
&
运算符, 将多个类型合并为一个类型
(1) 同名基础类型属性的&合并
(2)同名非基础类型属性的&合并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
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); // 多个类型合并时,若存在相同名称的成员,且成员类型为非基本数据类型,那么是可以成功合并。
- | 分隔符
联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型
(1)类型保护const sayHello = (name: string | undefined) => { /* ... */ }; sayHello("semlinker"); sayHello(undefined); let num: 1 | 2 = 1; // 字面量类型,用来约束取值只能是某几个值中的一个 type EventNames = 'click' | 'scroll' | 'mousemove';
当使用联合类型时,我们必须尽量把当前值的类型收窄为当前值的实际类型,而类型保护就是实现类型收窄的一种手段。
四种方式:- 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 关键字
注意: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 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"; }
- in 关键字
- _ 数字分隔符
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() 移除分隔符
-
<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"));
- 尖括号
-
@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 = <T>(target:Object, propertyKey: string | symbol, descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | 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
-
#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 这两个方法用于设置值和获取值 */