強型別指的是程式中表達的任何物件所從屬的型別都必須能在編譯時刻確定。
對於強型別語言,一個變數不經過強制轉換,它永遠是這個數據型別,不允許隱式的型別轉換。
假設定義了一個 double 型別變數 a,不經過強制型別轉換那麼程式 int b = a 是無法通過編譯。
// 編譯失敗
double a;
int b = a;
弱型別語言允許變數型別的隱式轉換,允許強制型別轉換等,如字串和數值可以自動轉化
let a = '100';
let b = 50;
console.log(a - b);
// 50 將a隱式轉換爲Number
console.log(a + b);
// 10050 將b隱式轉換爲String
靜態型別語言中,變數的型別必須先宣告,即在建立的那一刻就已經確定好變數的型別,而後的使用中,你只能將這一指定型別的數據賦值給變數。如果強行將其他不相幹型別的數據賦值給它,就會引發錯誤。
動態型別語言中,變數的型別可以隨時改變。
Flow 是 JavaScript 的靜態型別檢查器
安裝和設定專案的流程
首先,您需要設定一個編譯器以剝離 Flow 型別。您可以在 Babel 和 flow-remove-types 之間進行選擇。
這邊以 Babel 爲例:
Babel 是 JavaScript 程式碼的編譯器,具有對 Flow 的支援。Babel 可以將關於 Flow 程式碼剔除。
首先安裝@babel/core,@babel/cli 並@babel/preset-flow 使用 Yarn 或 npm。
npm install --save-dev @babel/core @babel/cli @babel/preset-flow
接下來,你需要在你的專案的根檔案下建立一個.babelrc。
{
"presets": ["@babel/preset-flow"]
}
剔除命令執行
babel 輸入需剔除的檔案或資料夾路徑 -d 輸出資料夾
安裝 flow-bin
npm install --save-dev flow-bin
將"flow"指令碼新增到您的 package.json:
{
"scripts": {
"flow": "flow"
}
}
首次安裝後,需要先初始化
npm run flow init
init 之後,執行 flow
npm run flow
/**
* Type Annotations(型別註解)
* flow
*/
// 參數新增型別註解
function add(x: number, y: number) {
return x + y;
}
// 正確
add(100, 100);
// 報錯
// add('100', 100);
// 宣告基本型別數據時新增型別註解
let num: number = 100; // 正確
// num = '100'; // 報錯
// 宣告函數時新增型別註解
function sum(): number {
return 100; // 只能返回number型別數據
// return '100'; // 報錯
}
/**
* 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
Flow 具有文字值的原始型別,但也可以將文字值用作型別。
例如,number 除了接受型別,我們可以只接受文字值 2。
/**
* Literal Types(文字型別)
* @flow
*/
function acceptsTwo(value: 2) {
// ...
}
acceptsTwo(2); // Works!
// $ExpectError
acceptsTwo(3); // Error!
// $ExpectError
acceptsTwo('2'); // Error!
將它們與聯合型別一起使用
/**
* 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 將接受任何型別的值。字串,數位,物件,函數等。
/**
* Mixed Types(混合型別)
* @flow
*/
function stringify(value: mixed) {
// ...
}
stringify('foo');
stringify(3.14);
stringify(null);
stringify({});
當您嘗試使用 mixed 型別的值時,必須首先弄清楚實際的型別是什麼,否則最終會出錯。
/**
* Mixed Types(混合型別)
* @flow
*/
function stringify(value: mixed) {
return '' + value; // Error!
}
stringify('foo');
通過 typeof 來確保該值是某種型別
/**
* Mixed Types(混合型別)
* @flow
*/
function stringify(value: mixed) {
if (typeof value === 'string') {
return '' + value; // Works!
} else {
return '';
}
}
stringify('foo');
使用any是完全不安全的,應儘可能避免使用。
/**
* Any Types(任何型別)
* @flow
*/
function division(one: any, two: any): number {
return one / two;
}
division(1, 2); // Works.
division('1', '2'); // Works.
division({}, []); // Works.
使用 Flow 可以將 Maybe 型別用於這些值。可能型別可以與其他任何型別一起使用,只需在其前面加上一個問號即可,例如?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 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(物件型別)
* @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' };
TypeScript 是 JavaScript 型別的超集,可編譯爲普通 JavaScript,支援 ECMAScript 6 標準,可執行在任何瀏覽器上。
TypeScript 是漸進式的
目前官網上已更新到 TypeScript 4.0 ,而中文官網更新到 TypeScript 3.1
這裏是針對專案,不進行全域性安裝
npm i typescript -D
使用 ts-node 可以直接在 node 環境下執行 ts 檔案,方便開發環境測試
npm i ts-node -D
執行
ts-node 檔案路徑
const test = (name: string) => console.log(`hello ${name}`);
test('typescript');
編譯 ts 程式碼,生成一個 index.js 檔案,並被轉換爲 es5
tsc index
index.js
var test = function (name) {
return console.log('hello ' + name);
};
test('typescript');
生成組態檔 tsconfig.json
tsc --init
具體設定可以檢視Compiler Options(編譯選項)
爲了使程式有用,我們需要能夠使用一些最簡單的數據單元:數位,字串,結構,布爾值等。 在 TypeScript 中,我們支援與 JavaScript 中期望的型別幾乎相同的型別,並拋出了方便的列舉型別以幫助處理問題。
let isDone: boolean = true;
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
let str: string = 'bob';
str = 'smith';
str = `smith${str}`;
let list: number[] = [1, 2, 3];
let list1: Array<number> = [1, 2, 3];
let x: [string, number];
x = ['hello', 10]; // OK
// x = [10, 'hello']; // Error
Enum 型別是對 JavaScript 標準數據型別的一個補充。
像 C#等其它語言一樣,使用列舉型別可以爲一組數值賦予友好的名字。
enum Color {
Red = 8,
Green,
Blue,
} // 預設0,1,2
let c: Color = Color.Green;
let cName: string = Color[9];
console.log(c);
console.log(cName);
// 9
// Green
let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;
notSure = 1;
某種程度上來說,void 型別像是與 any 型別相反,它表示沒有任何型別。 當一個函數沒有返回值時,你通常會見到其返回值型別是 void:
function warnUser(): void {
console.log('This is my warning message');
}
宣告一個 void 型別的變數沒有什麼大用,因爲你只能爲它賦予 undefined 和 null:
let unusable: void = undefined;
TypeScript 裡,undefined 和 null 兩者各自有自己的型別分別叫做 undefined 和 null。 和 void 相似,它們的本身的型別用處不是很大
let u: undefined = undefined;
let n: null = null;
never 型別表示的是那些永不存在的值的型別。 例如, never 型別是那些總是會拋出異常或根本就不會有返回值的函數表達式或箭頭函數表達式的返回值型別; 變數也可能是 never 型別,當它們被永不爲真的型別保護所約束時。
never 型別是任何型別的子型別,也可以賦值給任何型別;然而,沒有型別是 never 的子型別或可以賦值給 never 型別(除了 never 本身之外)。 即使 any 也不可以賦值給 never。
// 返回never的函數必須存在無法達到的終點
function error(message: string): never {
throw new Error(message);
}
// 推斷的返回值型別爲never
function fail() {
return error('Something failed');
}
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
function add(x: number, y: number): number {
return x + y;
}
const division = (x: number, y: number): number => {
return x / y;
};
// 書寫完整函數型別
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會隱式型別推斷其爲number
let name = '18'; // typescript會隱式型別推斷其爲string
let className; // typescript會隱式型別推斷其爲any
有時候你會遇到這樣的情況,你會比 TypeScript 更瞭解某個值的詳細資訊。 通常這會發生在你清楚地知道一個實體具有比它現有型別更確切的型別。
通過型別斷言這種方式可以告訴編譯器,「相信我,我知道自己在幹什麼」。 型別斷言好比其它語言裡的型別轉換,但是不進行特殊的數據檢查和解構。 它沒有執行時的影響,只是在編譯階段起作用。 TypeScript 會假設你,程式設計師,已經進行了必須的檢查。
型別斷言有兩種形式。 其一是「尖括號」語法:
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;
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 Person {
name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
在 TypeScript 裡,成員都預設爲 public
可以自由的存取程式裡定義的成員
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
console.log(new Person('zs').getName()); //zs
當成員被標記成 private 時,它就只能在類的內部存取。
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
new Person('zs').name; // 錯誤: 'name' 是私有的.
protected 修飾符與 private 修飾符的行爲很相似,但有一點不同, protected 成員在派生類中仍然可以存取。
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,不能被存取
}
getAge(): number {
return this.age; // OK,可以被存取
}
}
你可以使用 readonly 關鍵字將屬性設定爲只讀的。 只讀屬性必須在宣告時或建構函式裡被初始化
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'; // 錯誤! name 是隻讀的.
與 C#或 Java 裡介面的基本作用一樣,TypeScript 也能夠用它來明確的強制一個類去符合某種契約。
interface Run {
run(): void;
}
class Car implements Run {
run(): void {
console.log('我會跑...');
}
}
new Car().run();
抽象類做爲其它派生類的基礎類別使用。 它們一般不會直接被範例化。 不同於介面,抽象類可以包含成員的實現細節。 abstract 關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。
abstract class Animal {
run(): void {
console.log('我會跑...');
}
}
class Doc extends Animal {
eat(): void {
console.log('我會吃...');
}
}
let doc = new Doc();
doc.eat();
doc.run();
軟體工程中,我們不僅要建立一致的定義良好的 API,同時也要考慮可重用性。 元件不僅能夠支援當前的數據型別,同時也能支援未來的數據型別,這在建立大型系統時爲你提供了十分靈活的功能。
在像 C#和 Java 這樣的語言中,可以使用泛型來建立可重用的元件,一個元件可以支援多種型別的數據。 這樣使用者就可以以自己的數據型別來使用元件。
// 普通函數
function createArray<T>(...args: T[]): T[] {
return args;
}
console.log(createArray<number>(1, 2, 3));
console.log(createArray<string>('jack', 'tom'));
// 箭頭函數
const createArrayArrow = <T>(...args: T[]): T[] => {
return args;
};
console.log(createArrayArrow<number>(1, 2, 3));
console.log(createArrayArrow<string>('jack', 'tom'));
import Vue from 'vue';
export default Vue.extend({
name: 'Button',
data() {
return {
count: 1,
};
},
methods: {
increment() {
this.count++;
},
},
});
在 TypeScript 下,Vue 的元件可以使用一個繼承自 Vue 型別的子類表示,這種型別需要使用 Component 裝飾器去修飾
裝飾器是 ES 草案中的一個新特性,提供一種更好的面向切面程式設計的體驗,不過這個草案最近有可能發生重大調整,所以個人並不推薦。
裝飾器函數接收的參數就是以前的元件選項物件(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');
}
}
其它特性:例如 components, props, filters, directives 之類的,則需要使用修飾器參數傳入
使用這種 class 風格的元件宣告方式並沒有什麼特別的好處,只是爲了提供給開發者多種編碼風格的選擇性
這種方式繼續放大了 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')
}
}
No Class APIs,只用 Options APIs。
使用 Options APIs 最好是使用 export default Vue.extend({ … }) 而不是 export default { … }。
其實 Vue.js 3.0 早期是想要放棄 Class APIs 的,不過無奈想要相容,所以才保留下來了。
外掛的型別擴充套件,使用型別補充宣告
import { AxiosInstance } from 'axios'
declare module 'vue/types/vue' {
interface Vue {
readonly $api: AxiosInstance
}
}
JavaScript 專案中如何有更好的型別提示: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