Study Notes
本博主會(huì)持續(xù)更新各種前端的技術(shù),如果各位道友喜歡,可以關(guān)注、收藏、點(diǎn)贊下本博主的文章。
類型系統(tǒng)
強(qiáng)類型語(yǔ)言
強(qiáng)類型指的是程序中表達(dá)的任何對(duì)象所從屬的類型都必須能在編譯時(shí)刻確定。
對(duì)于強(qiáng)類型語(yǔ)言,一個(gè)變量不經(jīng)過(guò)強(qiáng)制轉(zhuǎn)換,它永遠(yuǎn)是這個(gè)數(shù)據(jù)類型,不允許隱式的類型轉(zhuǎn)換。
假設(shè)定義了一個(gè) double 類型變量 a,不經(jīng)過(guò)強(qiáng)制類型轉(zhuǎn)換那么程序 int b = a 是無(wú)法通過(guò)編譯。
// 編譯失敗
double a;
int b = a;
強(qiáng)類型的優(yōu)點(diǎn)
- 編譯時(shí)刻能檢查出錯(cuò)誤的類型匹配,以提高程序的安全性;
- 可以根據(jù)對(duì)象類型優(yōu)化相應(yīng)運(yùn)算,以提高目標(biāo)代碼的質(zhì)量;
- 重構(gòu)更牢靠;
- 減少運(yùn)行時(shí)不必要的類型判斷。
弱類型語(yǔ)言
弱類型語(yǔ)言允許變量類型的隱式轉(zhuǎn)換,允許強(qiáng)制類型轉(zhuǎn)換等,如字符串和數(shù)值可以自動(dòng)轉(zhuǎn)化
let a = '100';
let b = 50;
console.log(a - b);
// 50 將a隱式轉(zhuǎn)換為Number
console.log(a + b);
// 10050 將b隱式轉(zhuǎn)換為String
靜態(tài)類型
靜態(tài)類型語(yǔ)言中,變量的類型必須先聲明,即在創(chuàng)建的那一刻就已經(jīng)確定好變量的類型,而后的使用中,你只能將這一指定類型的數(shù)據(jù)賦值給變量。如果強(qiáng)行將其他不相干類型的數(shù)據(jù)賦值給它,就會(huì)引發(fā)錯(cuò)誤。
動(dòng)態(tài)類型
動(dòng)態(tài)類型語(yǔ)言中,變量的類型可以隨時(shí)改變。
Flow
Flow 是 JavaScript 的靜態(tài)類型檢查器
安裝
安裝和配置項(xiàng)目的流程
安裝編譯器
首先,您需要配置一個(gè)編譯器以剝離 Flow 類型。您可以在 Babel 和 flow-remove-types 之間進(jìn)行選擇。
這邊以 Babel 為例:
Babel 是 JavaScript 代碼的編譯器,具有對(duì) Flow 的支持。Babel 可以將關(guān)于 Flow 代碼剔除。
首先安裝@babel/core,@babel/cli 并@babel/preset-flow 使用 Yarn 或 npm。
npm install --save-dev @babel/core @babel/cli @babel/preset-flow
接下來(lái),你需要在你的項(xiàng)目的根文件下創(chuàng)建一個(gè).babelrc。
{
"presets": ["@babel/preset-flow"]
}
剔除命令運(yùn)行
babel 輸入需剔除的文件或文件夾路徑 -d 輸出文件夾
配置流程
安裝 flow-bin
npm install --save-dev flow-bin
將"flow"腳本添加到您的 package.json:
{
"scripts": {
"flow": "flow"
}
}
首次安裝后,需要先初始化
npm run flow init
init 之后,運(yùn)行 flow
npm run flow
使用
Type Annotations(類型注解)
/**
* Type Annotations(類型注解)
* flow
*/
// 參數(shù)添加類型注解
function add(x: number, y: number) {
return x + y;
}
// 正確
add(100, 100);
// 報(bào)錯(cuò)
// add('100', 100);
// 聲明基本類型數(shù)據(jù)時(shí)添加類型注解
let num: number = 100; // 正確
// num = '100'; // 報(bào)錯(cuò)
// 聲明函數(shù)時(shí)添加類型注解
function sum(): number {
return 100; // 只能返回number類型數(shù)據(jù)
// return '100'; // 報(bào)錯(cuò)
}
Primitive Types(原始類型)
- Booleans
- Strings
- Numbers
- null
- undefined (void in Flow types)
- Symbols (new in ECMAScript 2015)
/**
* Primitive Types(原始類型)
* @flow
*/
const bol: boolean = true; // false Boolean(0) Boolean(1)
const str: string = 'abs';
const nums: number = 1; // 3.14 NaN Infinity
const emt: null = null;
const un: void = undefined;
const syb: symbol = Symbol(); // Symbol.isConcatSpreadable
Literal Types(文字類型)
Flow 具有文字值的原始類型,但也可以將文字值用作類型。
例如,number 除了接受類型,我們可以只接受文字值 2。
/**
* Literal Types(文字類型)
* @flow
*/
function acceptsTwo(value: 2) {
// ...
}
acceptsTwo(2); // Works!
// $ExpectError
acceptsTwo(3); // Error!
// $ExpectError
acceptsTwo('2'); // Error!
將它們與聯(lián)合類型一起使用
/**
* Literal Types(文字類型)
* @flow
*/
function getColor(name: 'success' | 'warning' | 'danger') {
switch (name) {
case 'success':
return 'green';
case 'warning':
return 'yellow';
case 'danger':
return 'red';
}
}
getColor('success'); // Works!
getColor('danger'); // Works!
// $ExpectError
getColor('error'); // Error!
Mixed Types(混合類型)
mixed 將接受任何類型的值。字符串,數(shù)字,對(duì)象,函數(shù)等。
/**
* Mixed Types(混合類型)
* @flow
*/
function stringify(value: mixed) {
// ...
}
stringify('foo');
stringify(3.14);
stringify(null);
stringify({});
當(dāng)您嘗試使用 mixed 類型的值時(shí),必須首先弄清楚實(shí)際的類型是什么,否則最終會(huì)出錯(cuò)。
/**
* Mixed Types(混合類型)
* @flow
*/
function stringify(value: mixed) {
return '' + value; // Error!
}
stringify('foo');
通過(guò) typeof 來(lái)確保該值是某種類型
/**
* Mixed Types(混合類型)
* @flow
*/
function stringify(value: mixed) {
if (typeof value === 'string') {
return '' + value; // Works!
} else {
return '';
}
}
stringify('foo');
Any Types(任何類型)
使用any是完全不安全的,應(yīng)盡可能避免使用。
/**
* Any Types(任何類型)
* @flow
*/
function division(one: any, two: any): number {
return one / two;
}
division(1, 2); // Works.
division('1', '2'); // Works.
division({}, []); // Works.
Maybe Types(可能類型)
使用 Flow 可以將 Maybe 類型用于這些值??赡茴愋涂梢耘c其他任何類型一起使用,只需在其前面加上一個(gè)問(wèn)號(hào)即可,例如?number 某種修飾符。
例如:?number 就意味著 number,null 或 undefined。
/**
* Maybe Types(可能類型)
* @flow
*/
function acceptsMaybeNumber(value: ?number) {
// ...
}
acceptsMaybeNumber(42); // Works!
acceptsMaybeNumber(); // Works!
acceptsMaybeNumber(undefined); // Works!
acceptsMaybeNumber(null); // Works!
acceptsMaybeNumber('42'); // Error!
Function Types(函數(shù)類型)
function concat(a: string, b: string): string {
return a + b;
}
concat('foo', 'bar'); // Works!
// $ExpectError
concat(true, false); // Error!
function method(func: (...args: Array<any>) => any) {
func(1, 2); // Works.
func('1', '2'); // Works.
func({}, []); // Works.
}
method(function (a: number, b: number) {
// ...
});
Object Types(對(duì)象類型)
/**
* Object Types(對(duì)象類型)
* @flow
*/
let obj1: { foo: boolean } = { foo: true }; // Works.
obj1.bar = true; // Error!
obj1.foo = 'hello'; // Error!
let obj2: {
foo: number,
bar: boolean,
baz: string,
} = {
foo: 1,
bar: true,
baz: 'three',
}; // Works.
let obj3: { foo: string, bar: boolean };
obj3 = { foo: 'foo', bar: true };
obj3 = { foo: 'foo' };
更多類型查看types
TypeScript
TypeScript 是 JavaScript 類型的超集,可編譯為普通 JavaScript,支持 ECMAScript 6 標(biāo)準(zhǔn),可運(yùn)行在任何瀏覽器上。
TypeScript 是漸進(jìn)式的
目前官網(wǎng)上已更新到 TypeScript 4.0 ,而中文官網(wǎng)更新到 TypeScript 3.1
TypeScript(官網(wǎng))
TypeScript(中文網(wǎng))
安裝
這里是針對(duì)項(xiàng)目,不進(jìn)行全局安裝
npm i typescript -D
使用 ts-node 可以直接在 node 環(huán)境下運(yùn)行 ts 文件,方便開(kāi)發(fā)環(huán)境測(cè)試
npm i ts-node -D
運(yùn)行
ts-node 文件路徑
簡(jiǎn)單使用
const test = (name: string) => console.log(`hello ${name}`);
test('typescript');
編譯 ts 代碼,生成一個(gè) index.js 文件,并被轉(zhuǎn)換為 es5
tsc index
index.js
var test = function (name) {
return console.log('hello ' + name);
};
test('typescript');
配置
生成配置文件 tsconfig.json
tsc --init
具體配置可以查看Compiler Options(編譯選項(xiàng))
操作手冊(cè)
Basic Types(基礎(chǔ)類型)
為了使程序有用,我們需要能夠使用一些最簡(jiǎn)單的數(shù)據(jù)單元:數(shù)字,字符串,結(jié)構(gòu),布爾值等。 在 TypeScript 中,我們支持與 JavaScript 中期望的類型幾乎相同的類型,并拋出了方便的枚舉類型以幫助處理問(wèn)題。
Boolean(布爾類型)
let isDone: boolean = true;
Number(數(shù)字)
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
String(字符串)
let str: string = 'bob';
str = 'smith';
str = `smith${str}`;
Array(數(shù)組)
let list: number[] = [1, 2, 3];
let list1: Array<number> = [1, 2, 3];
Tuple(元組)
let x: [string, number];
x = ['hello', 10]; // OK
// x = [10, 'hello']; // Error
Enum(枚舉)
Enum 類型是對(duì) JavaScript 標(biāo)準(zhǔn)數(shù)據(jù)類型的一個(gè)補(bǔ)充。
像 C#等其它語(yǔ)言一樣,使用枚舉類型可以為一組數(shù)值賦予友好的名字。
enum Color {
Red = 8,
Green,
Blue,
} // 默認(rèn)0,1,2
let c: Color = Color.Green;
let cName: string = Color[9];
console.log(c);
console.log(cName);
// 9
// Green
Any(任何類型)
let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;
notSure = 1;
Void
某種程度上來(lái)說(shuō),void 類型像是與 any 類型相反,它表示沒(méi)有任何類型。 當(dāng)一個(gè)函數(shù)沒(méi)有返回值時(shí),你通常會(huì)見(jiàn)到其返回值類型是 void:
function warnUser(): void {
console.log('This is my warning message');
}
聲明一個(gè) void 類型的變量沒(méi)有什么大用,因?yàn)槟阒荒転樗x予 undefined 和 null:
let unusable: void = undefined;
Null and Undefined
TypeScript 里,undefined 和 null 兩者各自有自己的類型分別叫做 undefined 和 null。 和 void 相似,它們的本身的類型用處不是很大
let u: undefined = undefined;
let n: null = null;
Never
never 類型表示的是那些永不存在的值的類型。 例如, never 類型是那些總是會(huì)拋出異?;蚋揪筒粫?huì)有返回值的函數(shù)表達(dá)式或箭頭函數(shù)表達(dá)式的返回值類型; 變量也可能是 never 類型,當(dāng)它們被永不為真的類型保護(hù)所約束時(shí)。
never 類型是任何類型的子類型,也可以賦值給任何類型;然而,沒(méi)有類型是 never 的子類型或可以賦值給 never 類型(除了 never 本身之外)。 即使 any 也不可以賦值給 never。
// 返回never的函數(shù)必須存在無(wú)法達(dá)到的終點(diǎn)
function error(message: string): never {
throw new Error(message);
}
// 推斷的返回值類型為never
function fail() {
return error('Something failed');
}
Object(對(duì)象類型)
object 表示非原始類型,也就是除 number,string,boolean,symbol,null 或 undefined 之外的類型。
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(function () {}); // OK
create([1]); // OK
create(null); // OK
// create(42); // Error
// create('string'); // Error
// create(false); // Error
// create(undefined); // Error
函數(shù)
function add(x: number, y: number): number {
return x + y;
}
const division = (x: number, y: number): number => {
return x / y;
};
// 書寫完整函數(shù)類型
let myAdd: (baseValue: number, increment: number) => number = function (
x: number,
y: number,
): number {
return x + y;
};
const myDivision: (baseValue: number, increment: number) => number = (
x: number,
y: number,
): number => {
return x / y;
};
隱式類型推斷
let age = 18; // typescript會(huì)隱式類型推斷其為number
let name = '18'; // typescript會(huì)隱式類型推斷其為string
let className; // typescript會(huì)隱式類型推斷其為any
Type assertions(類型斷言)
有時(shí)候你會(huì)遇到這樣的情況,你會(huì)比 TypeScript 更了解某個(gè)值的詳細(xì)信息。 通常這會(huì)發(fā)生在你清楚地知道一個(gè)實(shí)體具有比它現(xiàn)有類型更確切的類型。
通過(guò)類型斷言這種方式可以告訴編譯器,“相信我,我知道自己在干什么”。 類型斷言好比其它語(yǔ)言里的類型轉(zhuǎn)換,但是不進(jìn)行特殊的數(shù)據(jù)檢查和解構(gòu)。 它沒(méi)有運(yùn)行時(shí)的影響,只是在編譯階段起作用。 TypeScript 會(huì)假設(shè)你,程序員,已經(jīng)進(jìn)行了必須的檢查。
類型斷言有兩種形式。 其一是“尖括號(hào)”語(yǔ)法:
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
另一個(gè)為 as 語(yǔ)法:
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
Interfaces(接口)
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);
可選屬性
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = { color: 'white', area: 100 };
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({ color: 'black' });
console.log(mySquare);
只讀屬性
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // error!
Class(類)
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
修飾符
在 TypeScript 里,成員都默認(rèn)為 public
- public
可以自由的訪問(wèn)程序里定義的成員
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
console.log(new Person('zs').getName()); //zs
- private
當(dāng)成員被標(biāo)記成 private 時(shí),它就只能在類的內(nèi)部訪問(wèn)。
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
new Person('zs').name; // 錯(cuò)誤: 'name' 是私有的.
- protected
protected 修飾符與 private 修飾符的行為很相似,但有一點(diǎn)不同, protected 成員在派生類中仍然可以訪問(wèn)。
class Person {
private name: string;
protected age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class School extends Person {
constructor(name: string, age: number) {
super(name, age);
}
getName(): string {
return this.name; // error,不能被訪問(wèn)
}
getAge(): number {
return this.age; // OK,可以被訪問(wèn)
}
}
readonly(只讀)
你可以使用 readonly 關(guān)鍵字將屬性設(shè)置為只讀的。 只讀屬性必須在聲明時(shí)或構(gòu)造函數(shù)里被初始化
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor(theName: string) {
this.name = theName;
}
}
let dad = new Octopus('Man with the 8 strong legs');
dad.name = 'Man with the 3-piece suit'; // 錯(cuò)誤! name 是只讀的.
類實(shí)現(xiàn)接口
與 C#或 Java 里接口的基本作用一樣,TypeScript 也能夠用它來(lái)明確的強(qiáng)制一個(gè)類去符合某種契約。
interface Run {
run(): void;
}
class Car implements Run {
run(): void {
console.log('我會(huì)跑...');
}
}
new Car().run();
抽象類
抽象類做為其它派生類的基類使用。 它們一般不會(huì)直接被實(shí)例化。 不同于接口,抽象類可以包含成員的實(shí)現(xiàn)細(xì)節(jié)。 abstract 關(guān)鍵字是用于定義抽象類和在抽象類內(nèi)部定義抽象方法。
abstract class Animal {
run(): void {
console.log('我會(huì)跑...');
}
}
class Doc extends Animal {
eat(): void {
console.log('我會(huì)吃...');
}
}
let doc = new Doc();
doc.eat();
doc.run();
Generics(泛型)
軟件工程中,我們不僅要?jiǎng)?chuàng)建一致的定義良好的 API,同時(shí)也要考慮可重用性。 組件不僅能夠支持當(dāng)前的數(shù)據(jù)類型,同時(shí)也能支持未來(lái)的數(shù)據(jù)類型,這在創(chuàng)建大型系統(tǒng)時(shí)為你提供了十分靈活的功能。
在像 C#和 Java 這樣的語(yǔ)言中,可以使用泛型來(lái)創(chuàng)建可重用的組件,一個(gè)組件可以支持多種類型的數(shù)據(jù)。 這樣用戶就可以以自己的數(shù)據(jù)類型來(lái)使用組件。
// 普通函數(shù)
function createArray<T>(...args: T[]): T[] {
return args;
}
console.log(createArray<number>(1, 2, 3));
console.log(createArray<string>('jack', 'tom'));
// 箭頭函數(shù)
const createArrayArrow = <T>(...args: T[]): T[] => {
return args;
};
console.log(createArrayArrow<number>(1, 2, 3));
console.log(createArrayArrow<string>('jack', 'tom'));
更多
定義組件的幾種不同方式
使用 Options APIs
- 組件仍然可以使用以前的方式定義(導(dǎo)出組件選項(xiàng)對(duì)象,或者使用 Vue.extend())
- 但是當(dāng)我們導(dǎo)出的是一個(gè)普通的對(duì)象,此時(shí) TypeScript 無(wú)法推斷出對(duì)應(yīng)的類型,
- 至于 VSCode 可以推斷出類型成員的原因是因?yàn)槲覀兪褂昧?Vue 插件,
- 這個(gè)插件明確知道我們這里導(dǎo)出的是一個(gè) Vue 對(duì)象。
- 所以我們必須使用 Vue.extend() 方法確保 TypeScript 能夠有正常的類型推斷
import Vue from 'vue';
export default Vue.extend({
name: 'Button',
data() {
return {
count: 1,
};
},
methods: {
increment() {
this.count++;
},
},
});
使用 Class APIs
在 TypeScript 下,Vue 的組件可以使用一個(gè)繼承自 Vue 類型的子類表示,這種類型需要使用 Component 裝飾器去修飾
裝飾器是 ES 草案中的一個(gè)新特性,提供一種更好的面向切面編程的體驗(yàn),不過(guò)這個(gè)草案最近有可能發(fā)生重大調(diào)整,所以個(gè)人并不推薦。
裝飾器函數(shù)接收的參數(shù)就是以前的組件選項(xiàng)對(duì)象(data、props、methods 之類)
import Vue from 'vue';
import Component from 'vue-class-component';
@Component({
props: {
size: String,
},
})
export default class Button extends Vue {
private count: number = 1;
private text: string = 'Click me';
get content() {
return `${this.text} ${this.count}`;
}
increment() {
this.count++;
}
mounted() {
console.log('button is mounted');
}
}
- Data: 使用類的實(shí)例屬性聲明
- Method: 使用類的實(shí)例方法聲明
- Computed: 使用 Getter 屬性聲明
- 生命周期: 使用類的實(shí)例方法聲明
其它特性:例如 components, props, filters, directives 之類的,則需要使用修飾器參數(shù)傳入
使用這種 class 風(fēng)格的組件聲明方式并沒(méi)有什么特別的好處,只是為了提供給開(kāi)發(fā)者多種編碼風(fēng)格的選擇性
使用 Class APIs + vue-property-decorator
這種方式繼續(xù)放大了 class 這種組件定義方法。
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class Button extends Vue {
private count: number = 1
private text: string = 'Click me'
@Prop() readonly size?: string
get content () {
return `${this.text} ${this.count}`
}
increment () {
this.count++
}
mounted () {
console.log('button is mounted')
}
}
個(gè)人最佳實(shí)踐
No Class APIs,只用 Options APIs。
使用 Options APIs 最好是使用 export default Vue.extend({ ... }) 而不是 export default { ... }。
其實(shí) Vue.js 3.0 早期是想要放棄 Class APIs 的,不過(guò)無(wú)奈想要兼容,所以才保留下來(lái)了。
相關(guān)擴(kuò)展
插件的類型擴(kuò)展,使用類型補(bǔ)充聲明
import { AxiosInstance } from 'axios'
declare module 'vue/types/vue' {
interface Vue {
readonly $api: AxiosInstance
}
}
JavaScript 項(xiàng)目中如何有更好的類型提示:JSDoc + import-types
https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html
https://www.typescriptlang.org/play/index.html?useJavaScript=truee=4#example/jsdoc-support