Any application that can be written in Javascript, will eventually be written in Javascript. —— stackoverflow. Jeff Atwood
TypeScript 是 JavaScript 的類型的超集,它可以編譯成純 JavaScript。編譯出來的 JavaScript 可以運行在任何瀏覽器上。TypeScript 編譯工具可以運行在任何服務(wù)器和任何系統(tǒng)上颗圣。
“自從用了TypeScript鸽捻,我永遠不會回到JavaScript了”
Vue 3 重新用TypeScript寫
Ceate-React-App 2.1(2018年10月29號發(fā)布)開始支持生成TypeScript
優(yōu)點
- 靜態(tài)類型檢查
- IDE 智能提示
- 代碼重構(gòu)支持
- 可讀可維護性
- 增強的oo推捐,可以用更多設(shè)計模式,IoC暖途,AOP...
基礎(chǔ)類型
- 布爾值
let isRight: boolean = false;
- 數(shù)字
let num: number = 1;
- 字符串
let name1: string = "張三";
- 數(shù)組
let list1: number[] = [1, 2, 3];
// 或
let list2: Array<number> = [1, 2, 3];
- 元組
元組類型允許表示一個已知元素數(shù)量和類型的數(shù)組鳞溉,各元素的類型不必相同
let x: [string, number] = ["hello", 10];
- 枚舉
/* 枚舉 */
enum Colors {
Red,
Green,
Blue
}
// 編譯后
/*
var Colors;
(function () {
Colors[(Colors["Red"] = 1)] = "Red";
Colors[(Colors["Green"] = 2)] = "Green";
Colors[(Colors["Blue"] = 3)] = "Blue";
})(CoColorslor || (Colors = {}));
*/
// 輸出:{1: "Red", 2: "Green", 3: "Blue", Red: 1, Green: 2…}
// 字符串枚舉無法反向映射瘾带,如:
enum Colors2 {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
// 輸出: {Red: "RED", Green: "GREEN", Blue: "BLUE"}
// 字符串枚舉成員不能被反向映射到枚舉成員的名字。 換句話說穿挨,你不能使用 Colors["RED"]來得到"Red"月弛。
- unknown
/* unknown */
let notSure1: unknown = 1;
notSure1 = "maybe a string instead";
notSure1 = false;
const n1: number = notSure1;
// error:Type 'unknown' is not assignable to type 'number'.
// by the way肴盏,unknown和any的區(qū)別
/*
any 和 unknown 的最大區(qū)別是,
unknown 是 top type (任何類型都是它的 subtype) ,
而 any 即是 top type, 又是 bottom type (它是任何類型的 subtype ) ,
這導(dǎo)致 any 基本上就是放棄了任何類型檢查.
*/
{
const objUnknown: unknown = {
sayHello() {}
};
objUnknown.sayHello();
// error:Object is of type 'unknown'
// 可使用類型斷言縮小未知范圍
(objUnknown as { sayHello: () => void }).sayHello();
}
{
// 使用any科盛,不會檢查出錯誤,就會導(dǎo)致程序出現(xiàn)bug
const objAny: any = {
sayHello() {}
};
objAny.hello();
}
- any
let notSure: any = 1;
notSure = "張三";
notSure = { id: 1, name: "李四" };
- void / Undefined / null
它們的本身的類型用處不是很大
function fn1(): void {
console.log("1");
}
let u: undefined = undefined;
let n: null = null;
- never
never類型表示的是那些永不存在的值的類型菜皂。
例如贞绵, never類型是那些總是會拋出異常或根本就不會有返回值的函數(shù)表達式或箭頭函數(shù)表達式的返回值類型恍飘;
變量也可能是 never類型榨崩,當(dāng)它們被永不為真的類型保護所約束時谴垫。
never類型是任何類型的子類型,也可以賦值給任何類型母蛛;
然而翩剪,沒有類型是never的子類型或可以賦值給never類型(除了never本身之外)。
即使 any也不可以賦值給never彩郊。
自定義拋出異常
function error(message: string): never {
throw new Error(message);
}
// 推斷的返回值類型為never
function fail() {
return error("Something failed");
}
根本就不會有返回值的函數(shù)表達式
// 返回never的函數(shù)必須存在無法達到的終點
function infiniteLoop(): never {
while (true) {}
}
收窄類型&never
interface Worker {
type: "worker";
}
interface Police {
type: "police";
}
type All = Worker | Police ;
function personTest(person: All) {
switch (person.type) {
case "worker":
// 這里 person 被收窄為 Worker
console.log("worker");
break;
case "police":
// 這里 person 被收窄為 Police
console.log("police");
break;
default:
// 這里 person 為never
const exhaustiveCheck: never = person;
break;
}
}
如果在未來你有個工友擴展了All前弯,如:
interface Teacher {
type: "teacher";
}
type All = Worker | Police | Teacher;
// ...
但在switch分支上沒有加上 對于Teacher 的邏輯處理,
這個時候在default分支的person會被收窄成Teacher秫逝,導(dǎo)致無法賦值給never恕出,產(chǎn)生了一個編譯期間的錯誤。
通過這個辦法违帆,你可以確保 personTest 方法總是窮盡了所有對于All的可能類型浙巫。
- object
使用object類型,就可以更好的表示像Object.create這樣的API刷后。例如:
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
斷言
通過類型斷言這種方式可以告訴編譯器的畴,“相信我,我知道自己在干什么”
<type>
let str: any = "this is a string";
let strLen: number = (<string>str).length;
- as
等價于<type>
惠险,因為<type>
會和jsx/tsx代碼混淆苗傅,所以推薦用 ”as“
let str: any = "this is a string";
let strLen: number = (str as string).length;
- !
const obj: {
userInfo?: {
id?: string;
name?: string;
};
} = {
userInfo: {
id: "1"
}
};
const id = obj.userInfo.id;
// error:對象可能為“未定義”
const id2 = obj.userInfo!.id;
// ok
by the way,
?. 運算符
let val = obj?.a;
// 編譯成es5后
var val = obj === null || obj === void 0 ? void 0 : obj.a;
// void 0 即 undefined
應(yīng)用在一些場景上
// 用之前傳統(tǒng)的方式
if(obj && obj.a) { }
// 用ts
if(obj?.a){ }
- const
// readonly
const obj = {
name: "zhangsan",
age: 18
} as const;
obj.age = 10;
// error:Cannot assign to 'age' because it is a read-only property.
接口(interface)
TypeScript的核心原則之一是對值所具有的結(jié)構(gòu)進行類型檢查
// 規(guī)范首字母大寫以 ”I“ 開頭
interface IUserInfo {
id: string;
age:number;
name: string;
hobby?: number;
[key: string]: any;
}
// 對象檢查
let test: ITest = {
readonly x: 10,
color: 'red',
height: 100,
myProp: 'hello world'
};
test.x = 10;
// 報錯班巩,只讀屬性不能賦值
// 繼承
interface ITest2 extends ITest {
other: string
}
實現(xiàn)接口
與C#或Java里接口的基本作用一樣渣慕,TypeScript也能夠用它來明確的強制一個類去符合某種契約。
interface IWorkman {
hours: number;
working: () => void;
rest?: () => void;
}
// 在類中必須去實現(xiàn)接口非空成員
class I implements IWorkman {
currentTime: Date;
hours: number;
constructor(h: number, m: number) {
this.currentTime = new Date();
this.hours = 8;
}
working() {
console.log("Morning worker");
}
}
接口(interface)vs 類型別名(type)
/* 接口 interface */
interface IUserInfo {
id: string;
name: string;
hobby?: string;
readonly age: number;
[key: string]: any;
}
let user: IUserInfo = {
id: "1",
age: 10,
name: "張三",
sex: 1
};
user.age = 1;
// error:Cannot assign to 'age' because it is a read-only property.
// 繼承
interface IOther extends IUserInfo {
other: string;
// ...
}
/* 類型別名 type */
// 與interface類似抱慌,不贅述
/* interface vs type */
// 相同點:
// 1. 都可以描述一個對象
interface IUser {
name: string;
age: number;
}
type TUser = {
name: string;
age: number;
};
// 2.都允許拓展(extends)
interface IName2 {
name: string;
}
interface IUser2 extends IName2 {
age: number;
}
type TName2 = {
name: string;
};
type TUser2 = TName2 & { age: number };
// 不同點:
//1. type 可以逊桦,interface 不行
// 基本類型的別名
type Name = string;
// 高級類型:聯(lián)合類型
interface IDog {
wang: string;
}
interface ICat {
miao: string;
}
type IPet = IDog | ICat;
//2. interface 可以,type 不行
interface IUser3 {
name: string;
age: number;
}
interface IUser3 {
/*
第二個interface的key如果和上一個interface一樣抑进,那么都要一模一樣强经,否則報錯,也就是這里要
name: string;
但是這個的意義并不大
*/
// name:number;
sex: string;
}
/*
同名的接口會被自動合并寺渗,
IUser3的接口最終為匿情,
interface IUser3 {
name: string
age: number
sex: string
}
*/
如果不清楚什么時候用interface/type,能用 interface 實現(xiàn)信殊,就用 interface炬称,
如果不能就用 type∥芯校總之玲躯,先考慮用interface。
高級類型
- 交叉類型
interface IPerson {
id: string;
name: string;
}
interface ICompany {
companyName: string;
}
type TStaff = IPerson & ICompany;
const staff: TStaff = {
id: "1",
name: "wang",
companyName: "kt"
};
- 聯(lián)合類型
type TVal = string | number;
const val: TVal = "123";
const val2: TVal = 123;
type TCode = 2000 | 3000 | 5000;
const code: TCode = 1000;
// error:Type '1000' is not assignable to type 'TCode'.
interface IPerson2 {
id: string;
name: string;
}
interface ICompany2 {
companyName: string;
}
type TStaff2 = IPerson | ICompany;
const staff2: TStaff2 = {
companyName: "kt"
};
泛型<T>
軟件工程中,我們不僅要創(chuàng)建一致的定義良好的API跷车,同時也要考慮可重用性棘利。 組件不僅能夠支持當(dāng)前的數(shù)據(jù)類型,同時也能支持未來的數(shù)據(jù)類型朽缴,這在創(chuàng)建大型系統(tǒng)時為你提供了十分靈活的功能善玫。
- 泛型函數(shù)
// 假設(shè)這個函數(shù)會返回任何傳入它的值
function identity<T>(arg: T): T {
return arg;
}
// ts會進行類型推論,推論返回 string
const val1: string = identity("hello world!");
// ok
const val2: string = identity<string>("hello world!");
// ok
const val3: number = identity<string>("hello world!");
// error:Type 'string' is not assignable to type 'number'.
// 在react hook + ts中密强,部分hook蝌焚,如useState也是一個泛型函數(shù)
const [num, setNum] = useState<number>(1);
- 泛型接口
// 設(shè)置默認(rèn)值為undefined,即 可不用傳遞泛型
interface IResult<T = undefined> {
code: 2000 | 4000 | 5000;
data: T | null;
message: string;
// 預(yù)留一些其他的特殊的key
[otherKey: string]: any;
}
const $request = {
get(action: string): Promise<any> {
return new Promise((resolve, _) => {
if (true) {
const obj = {
code: 2000,
data: {
id: 1,
name: "zhangsan"
},
message: "請求成功"
};
resolve(obj);
} else {
const obj = {
code: 5000,
data: null,
message: "請求失敗"
};
resolve(obj);
}
});
}
};
// 1
// 開始請求
const getUserInfo = async () => {
const {
code,
data,
message,
other1,
other2
}: IResult<{ id: number; name: string }> = await $request.get(
"getUserInfo"
);
if (code === 2000) {
// ide自能提示誓斥,data的屬性
console.log(data.id);
console.log(data.name);
console.log(message);
} else if (code === 5000) {
console.log(message);
}
};
getUserInfo();
// 2
// 把請求方法再封裝一層只洒,單獨維護再一個ts文件,推薦
const getUserInfo2 = (): Promise<IResult<{ id: number; name: string }>> =>
$request.get("getUserInfo");
// 開始請求
const _getUserInfo2 = async () => {
const { code, data, message } = await getUserInfo2();
if (code === 2000) {
// ide自能提示劳坑,data的屬性
console.log(data.id);
console.log(data.name);
console.log(message);
} else if (code === 5000) {
console.log(message);
}
};
_getUserInfo2();
- 泛型類
class Count<T> {
init: T;
count: (x: T, y: T) => T;
}
const count = new Count<number>();
count.init = 0;
count.count = (x, y) => {
return x + y;
};
ts中的 typeof
const obj = {
title: "標(biāo)題",
like: 100
};
console.log(typeof obj);
// object
type TObj = typeof obj;
const obj2: TObj = { title: "標(biāo)題2", like: 1000 };
// TObj -> { title: string; like: number; }
收窄類型/流動類型
const fn = (val: string | number) => {
if (typeof val === "string") {
val.split("");
}
};
不僅僅typeof毕谴,instanceof,switch case【回到上面看收窄類型&never的例子】...
等場景也是類型收窄的手段
*.d.ts 文件
要想描述非TypeScript編寫的類庫的類型距芬,我們需要聲明類庫所暴露出的API
如果你只寫js涝开,d.ts對你來說也是有用的,vscode會給你智能提示
在node_modules的第三方庫框仔,經(jīng)常會看到 *.d.ts 相關(guān)文件舀武,如:
-
第三方UI庫
-
如node_modules的test庫,配對的index.d.ts文件
在項目中引用 test 包時离斩,也會自動引用其對應(yīng)的聲明 index.d.ts
-
在 ts 文件里银舱,引用了一個沒有聲明文件的 js 庫,如classnames跛梗,會提示:
使用npm install 命令安裝之后寻馏,會在node_modules/@types 文件夾下生成
如果庫 @types/classnames 不存在,就要在工程下 xxx.d.ts 項目全局聲明定義
declare module 'classnames';
vue工程下常見的.d.ts文件 & 自定義全局聲明
shims-tsx.d.ts
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
// 自定義全局聲明
interface IUser {
id: string;
name: String;
}
}
shims-vue.d.ts【墊片修復(fù)在引入.vue文件的時候不會報錯】
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
如何去編寫.d.ts 聲明文件核偿?
一般來說在做第三方庫發(fā)布至npm平臺的時候诚欠,我們會用TypeScript提供的工具,直接用命令tsc生成如:
index.ts
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return 'Hello, ' + person.firstName + ' ' + person.lastName;
}
const arr: number[] = [1, 2];
let user = { firstName: 'Jane', lastName: 'User' };
export { greeter, arr, user };
在終端根目錄執(zhí)行 tsc , 即編譯出 commonjs 規(guī)范的包和配對的 .d.ts 聲明文件
index.js
"use strict";
exports.__esModule = true;
exports.user = exports.arr = exports.greeter = void 0;
function greeter(person) {
return 'Hello, ' + person.firstName + ' ' + person.lastName;
}
exports.greeter = greeter;
var arr = [1, 2];
exports.arr = arr;
var user = { firstName: 'Jane', lastName: 'User' };
exports.user = user;
index.d.ts
let user 在 index.ts 沒有明確聲明對象類型漾岳,但是ts會將自動推斷類型轰绵,并寫入.d.ts文件里
interface Person {
firstName: string;
lastName: string;
}
declare function greeter(person: Person): string;
declare const arr: number[];
declare let user: {
firstName: string;
lastName: string;
};
export { greeter, arr, user };
當(dāng)在使用index.js這個庫時候,ide就會根據(jù)聲明智能提示尼荆。
對于如何編寫 .d.ts左腔,我們可以去模仿一些第三方庫 .d.ts 文件的編寫,在項目的全局 .d.ts 上去進行實踐
如:
一些簡單的
declare const arr: number[];
declare module '*.module.less';
declare namespace NodeJS {
interface ProcessEnv {
readonly REACT_APP_MY_ENV: 'test' | 'prod';
}
}
// ...
對于 .d.ts 耀找,我們擁抱的態(tài)度是僅需要了解 .d.ts 的文件相關(guān)的寫法翔悠,把重心放在如何去更好的編寫 ts/tsx 文件。
Typescript 其他
- keyof
interface IPerson {
name: string;
age: number;
location: string;
}
type TFind = keyof IPerson;
// "name" | "age" | "location"
const find: TFind = "name";
- 約束對象key的值
in
type TName = "zhangsan" | "lisi" | "wangwu";
type TUser = {
[key in TName]: number;
};
const obj: TUser = { zhangsan: 1, lisi: 2, wangwu: 3 };
keyof T
function getProperty<T extends { [k: string]: number }>(obj: T, key: keyof T) {
return obj[key].toString();
}
let obj = { a: 1, b: 2, c: 3, d: 4 };
getProperty(obj, "a");
// 報錯
getProperty(obj, "m");
- 約束聲明
// 約束聲明野芒,ITest key對應(yīng)的 value 只能是string
interface IGrade<T extends { [K in keyof T]?: string } = {}> {
name: string;
params: T;
}
interface ITest {
hobby: string;
gradeName: string;
}
let test: IGrade<ITest> = {
name: "zhangsan",
params: {
hobby: "play",
gradeName: "一年級一班"
}
};
Required<T>
轉(zhuǎn)成非空屬性
interface IPerson {
name?: string;
age?: number;
}
type IPersonRequired = Required<IPerson>;
/*
interface IPersonRequired {
name: string;
age: number;
}
*/
const person: IPersonRequired = { name: "zhangsan", age: 18 };
// 內(nèi)部實現(xiàn)
type Required<T> = {
[P in keyof T]-?: T[P];
};
Partial<T>
轉(zhuǎn)成可空屬性
interface IPerson {
name: string;
age: number;
}
type IPersonPartial = Partial<IPerson>;
/*
interface IPersonPartial {
name?: string;
age?: number;
}
*/
const person: IPersonPartial = {};
// 內(nèi)部實現(xiàn)
type Partial<T> = {
[P in keyof T]?: T[P];
};
Pick<T>
取出某些屬性蓄愁,提高interface復(fù)用率
/*
Pick<T>,反之Omit<T>
*/
interface TState {
name: string;
age: number;
like: string[];
}
interface ISingleState {
name: string;
age: number;
}
interface ISingleState extends Pick<TState, "name" | "age"> {}
總之狞悲,TypeScript優(yōu)點是可見的撮抓,擁抱TypeScript會讓你變得更好。