TypeScript是微軟開(kāi)發(fā)的一個(gè)開(kāi)源的編程語(yǔ)言,通過(guò)在JavaScript的基礎(chǔ)上添加靜態(tài)類型定義構(gòu)建而成。這意味著TypeScript是JavaScript的超集,它包含了JavaScript的所有元素,并為其添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊獭?/p>
一、主要特性
- 類型批注:TypeScript允許開(kāi)發(fā)者為變量、函數(shù)參數(shù)和返回值等添加類型批注,這有助于在編譯時(shí)捕獲類型錯(cuò)誤,提高代碼的健壯性。
- 聲明文件:TypeScript支持為已存在的JavaScript庫(kù)添加類型信息的頭文件(.d.ts文件),這擴(kuò)展了它對(duì)流行庫(kù)的支持,如jQuery、MongoDB、Node.js等。
- 泛型:泛型允許開(kāi)發(fā)者在定義函數(shù)、接口和類時(shí)不指定具體類型,而是在使用時(shí)指定,這提高了代碼的可重用性和靈活性。
- 兼容性:TypeScript編譯后的代碼是純JavaScript代碼,可以在任何支持JavaScript的環(huán)境中運(yùn)行,如瀏覽器、Node.js等。
二、核心知識(shí)點(diǎn)
- 基本類型:包括字符串(string)、數(shù)字(number)、布爾值(boolean)、數(shù)組(Array)、元組(tuple)、枚舉(enum)等。
- 任意類型:使用any類型可以表示任意類型的值,但在使用時(shí)應(yīng)盡量避免,以保持代碼的類型安全性。
- 空值類型:void類型表示沒(méi)有任何類型,通常用于表示函數(shù)沒(méi)有返回值;null和undefined類型分別表示空值和未定義值。
- 函數(shù):TypeScript中的函數(shù)支持默認(rèn)參數(shù)、可選參數(shù)、剩余參數(shù)和箭頭函數(shù)等特性。
- 類與接口:TypeScript支持類的定義和繼承,以及接口的實(shí)現(xiàn)。類可以包含屬性、方法、構(gòu)造函數(shù)和修飾符等;接口用于定義對(duì)象的形狀,可以包含屬性、方法以及泛型約束等。
- 裝飾器:裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明、方法、訪問(wèn)器、屬性或參數(shù)上。裝飾器使用@expression這種形式,expression求值后必須為一個(gè)函數(shù),它會(huì)在運(yùn)行時(shí)被調(diào)用,被裝飾的聲明信息作為參數(shù)傳入。
三、基本語(yǔ)法
1、變量聲明和類型注解
對(duì)變量的類型限制,是先聲明還是后注解,兩種寫法。
// js寫法
let a = 1;
// 變量聲明
let b:number = 1;
const c:boolean = true;
// 類型注解
let d = <string>'hello';
2、函數(shù)和箭頭函數(shù)
// 原方法
function add(a,b){
return a + b;
}
// ts聲明限制
function add2(a:number,b:number){
return a + b;
}
// add2的返回值自動(dòng)推斷為number,但是我們也可以手動(dòng)設(shè)置返回類型
function add2(a:number,b:number):number{
return a + b;
}
// 箭頭函數(shù)同理
const arrowSum = (a:number,b:number):number => a + b;
3、類型和接口
// 用type聲明類型
type Point = {
x:number,
y:number
}
// 方法1:聲明
let p:Point = {x:1,y:1}
// 方法2:類型注解
let p2 = <Point>{x:1,y:1}
接口定義對(duì)于java同學(xué)應(yīng)該不陌生,它的使用和類型是一樣的
// 接口定義
interface Person{
name: string,
age: number
}
// 方法1:聲明
let p3:Person = { name:'hello', age:12 }
// 方法2:注解
let p4 = <Person>{ name:'hello', age:12 }
通過(guò)類來(lái)實(shí)現(xiàn)接口
class Student implements Person{
name: string
age: number
constructor(name:string, age:number){
this.name = name;
this.age = age;
}
}
// Person,可以傳入Student
function info(person:Person){
console.log('我的名字:'+person.name)
}
const p5 = new Student('名字','18');
// info調(diào)用傳入p5,因?yàn)镾tudent是實(shí)現(xiàn)了Person接口的
info(p5);
這里有個(gè)簡(jiǎn)化操作:我們可以把接口類型作為屬性放到類里,所以上面的接口類可以改為
class Student implements Person{
constructor(public name:string, public age:number){}
}
接口也是可以繼承的
// 定義Person2繼承Person
interface Person2 extends Person{
say(): void
}
// 用Person2 實(shí)現(xiàn)接口
class Student2 implements Person2{
constructor(public name:string, public age:number){}
say(): void {
console.log('這里我說(shuō)了')
}
}
// 同前面一樣,調(diào)用
const p6 = new Student2('名字','18');
info(p6);
interface可以定義屬性,還可以定義函數(shù)
// 定義一個(gè)函數(shù),參數(shù)是person
interface Info{
(person:Person): void
}
// 定義一個(gè)函數(shù)來(lái)實(shí)現(xiàn)接口,此時(shí)入?yún)erson不需要顯示指定類型,因?yàn)閕nfo2已經(jīng)做了限制里
const info2: Info = (person) => {
console.log('我的名字:'+person.name)
}
// 同前面一樣,調(diào)用
const p7 = new Student2('名字','18');
info(p7);
另外,在ts中是可以定義可選值的,只需要多加一個(gè)問(wèn)號(hào)
interface Person3{
name: string,
age?: number // 這里多了一個(gè)問(wèn)號(hào),表示有age的時(shí)候限制類型
}
// 下面調(diào)用就可以不傳age了(開(kāi)發(fā)中不會(huì)報(bào)錯(cuò))
const p8: Person3 = {name:'我的名字'}
4、命名空間與模塊
命名空間是ts特有的,js中沒(méi)有,關(guān)鍵字是namespace
namespace App{
const version = '1.0.0'
export function getVersion(){
return version
}
}
// 調(diào)用
App.getVersion(); // log一下能看到是1.0.0
在這里直接去拿version就不行,為什么呢,其實(shí)可以編譯一下看它生成的結(jié)果
var App;
(function (App){
const version = '1.0.0';
function getVersion(){
return version
}
App.getVersion = getVersion;
})(App || (App={})) // 我們發(fā)現(xiàn)它生成了一個(gè)閉包
模塊和命名空間類似,關(guān)鍵字是module,把上面的例子中的namespace改成module即可,并且編譯結(jié)果也基本一致。那么這兩者有什么區(qū)別呢?主要是跟場(chǎng)景有關(guān),命名空間是為了集合某一類的操作,而模塊一般用于npm包。
5、數(shù)組與元組
數(shù)組聲明和常量類似,數(shù)組多了一個(gè)方括號(hào)
// 常量:let a:string = 'ccc';
const arr:string[] = ['aaa','bbb'];
// arr的push、pop等方法,同js
元組和數(shù)組是一樣的,用法略有不同
const [name, age] = <[string, number]>['我的名字', 18];
// 你可以理解為對(duì)數(shù)組的每個(gè)元素都定義類型的方式
寫過(guò)react的同學(xué)肯定用過(guò)這個(gè) const [state, setState] = useState();其實(shí)useState()就是一個(gè)元組,返回了兩個(gè)東西,第一個(gè)是值,第二個(gè)是設(shè)置狀態(tài)的函數(shù)。
6、枚舉
enum Colors {
Red, Green, Blue
}
function printColors(color:Colors){
console.log(color)
}
// 調(diào)用
printColors(Colors.Red);
// 通過(guò)編譯,我們發(fā)現(xiàn)枚舉還是閉包
var Colors;
(function (Colors){
Colors[Colors['Red'] = 0] = 'Red';
Colors[Colors['Green'] = 1] = 'Green';
Colors[Colors['Blue'] = 2] = 'Blue';
})(Colors || (Colors={}))
printColors(Colors);
// 輸出 {'0': 'Red', '1':'Green', '2':'Blue', Red: 0, Green: 1, Blue: 2}
// 枚舉的本質(zhì)是元素和索引互為鍵值對(duì)的,開(kāi)發(fā)中可以用枚舉代替魔法字符串。
7、特殊類型any、never、unknown
// 賦值可以是任何數(shù)據(jù)類型
let bb:any = 1;
bb = true; // 這里更換類型也是可以
// 任意類型也可以賦值到其他類型的常量
let aa: number = 1;
aa = bb;
// never與any相反,如下,表示永遠(yuǎn)不會(huì)發(fā)生的值
function test(): never{
throw new Error();
// 第二種情況:while(true){},死循環(huán)
}
// nuknown表示不知道的類型
let cc: nuknown = 1;
cc = 'hello'; // 因?yàn)椴恢李愋停@里可以賦值為其他類型
// 和any的區(qū)別:nukown類型的數(shù)據(jù)不能賦值給其他類型
// 上面我們可以aa = bb;
aa = cc; // 這個(gè)是不對(duì)的
四、高級(jí)特性
1、關(guān)鍵字
typeof在js中用來(lái)判斷變量的類型,ts中也可以,但是ts還可以以定義的形式拿到類型;
let aa: number = 1;
type BTtype = typeof aa; // 把拿到的類型放到一個(gè)type里,這時(shí)候的BTtype就是 number
keyof是ts里的,用來(lái)獲取對(duì)象的key
type Person = {
name: string,
age: number
}
type PersonKeys = keyof Person; // PersonKeys里就有了對(duì)象的所有key
// 下面開(kāi)始使用
const p: Person = {name: '姓名', age: 18}
function getValueFromKey(key: PersonKeys){
return p[key]
}
console.log(getValueFromKey('name')); // 調(diào)用的時(shí)候就會(huì)有 name、age 的提示了(見(jiàn)末尾圖)
in在ts中和js都有
for(const key in Person){
console.log(key); // 這里js通過(guò)in遍歷Person里的所有key
}
// ts中,通過(guò)in復(fù)制出新的類型Person2,同Person的鍵是一樣的
type Person2 = {
[key in PersonKeys]: string
}
extends在js中做來(lái)做繼承
class a {};
class b extends a {}
// 只不過(guò)在ts中,還可以用來(lái)做接口的繼承
interface aa {
a: number
}
interface bb extends aa{
b: string
}
const b1: bb = {a:1, b:'hello'} //這樣在用到bb做類型聲明的時(shí)候就需要同時(shí)滿足a、b類型條件
is是ts特有的,用來(lái)定義類型保護(hù)函數(shù)
function isString(value: nuknown){
return typeof value === 'string';
}
// 判斷正確會(huì)返回true,但是ts引擎是不知道的,可以這么改
function isString(value: nuknown): value is string{
return typeof value === 'string';
}
// 舉個(gè)例子
let nuknownType: unknown = Math.randow()>0因?yàn)?5 ? '1' : 1; //條件不同類型也不同
nuknownType.trim(); // 如果后面我們調(diào)用trim,類型未知,會(huì)有運(yùn)行時(shí)錯(cuò)誤
// 改成
if(isString(nuknownType)){ nuknownType.trim(); }
as是ts特有的,用來(lái)做類型斷言
// 上面的例子我們用isString做判斷,但是也可用as斷言
nuknownType = '中文名字';
nuknownType.trim(); // 這樣還是報(bào)錯(cuò),因?yàn)樗念愋捅旧聿淮_定,這里的string是我們自己確定
// 所以用as,告訴ts這就是一個(gè)string
(nuknownType as string).trim(); // 編碼和編譯時(shí)候檢查,而不會(huì)在運(yùn)行時(shí)檢查
const在js中很常見(jiàn),在ts里還有另一個(gè)作用,定義靜態(tài)枚舉
const enum Colors { Red, Green, Blue }
printColors(Colors); // 報(bào)錯(cuò),因?yàn)閏onst枚舉在編譯后不存在
printColors(Colors.Red); // 輸出 0
readonly是ts特有的,可以把某個(gè)屬性變成只讀的
interface Row {
id: number,
name: string
}
const row: Row = {
id: 1,
name : '名字'
}
// 后面我們可以通過(guò) row['id']=2來(lái)修改id,如果不想id被修改
interface Row {
readonly id: number,
name: string
}
publick和private用來(lái)定義公共屬性和私有屬性
class Person{
constructor(public name:string, private idcard:string){}
getName(){
console.log(this.name);
}
getIdcard(){
console.log(this.idcard);
}
}
const he = new Person('姓名','11233qqq');
// he.name、he.getName()、he.getIdcard()都可以訪問(wèn),但是
he.idcard //報(bào)錯(cuò)
// js中也可以設(shè)置私有屬性,但是方法不同,先改一下上面方法,加入#money
class Person{
#money:number;
constructor(public name:string, private idcard:string){
this.#money = 100
}
}
he.#money // he后不會(huì)提示這個(gè),強(qiáng)行寫上運(yùn)行會(huì)報(bào)錯(cuò)
// 總結(jié):用#更安全,因?yàn)樗硎静豢赏獠吭L問(wèn),而private隨便在開(kāi)發(fā)或者校驗(yàn)時(shí)報(bào)錯(cuò),但是本身可以訪問(wèn)到
2、泛型
為參數(shù)類型提供占位符,從而增加代碼的靈活性和復(fù)用性。
函數(shù)泛型
// 普通函數(shù)
function getVlueFromKey(obj, key){
return obj[key]
}
// 改裝后,O和K就有了關(guān)聯(lián),并且限制了類型
function getVlueFromKey<O, K extends keyof O>(obj:O, key:K):O[K]{
return obj[key]
}
// 使用
const obj = {
a: 1,
b: 'hello',
c: true,
d: [1, 2]
}
getVlueFromKey(obj, 'a'); //我們?cè)谑褂梅椒ǖ臅r(shí)候會(huì)有提示它的value和key,并且后面.的時(shí)候會(huì)提示對(duì)應(yīng)的類型的方法,這就是使用泛型后的作用
接口泛型
// 通常接口返回值是固定了,data不確定,用泛型
interface Apiresult<O> {
code: number,
msg: string,
data: O
}
// 使用
async function request<D>(api: string): Promise<Apiresult<D>> {
return await fetch(api).then(res => res.json)) as Apiresult<D>
}
request('').then(res=>{})
//request<string>('').then(res=>{ 這里就可以直接使用res.data.字符串方法 })
類泛型
class ResponseResult<O> {
constructor(
publick readonly code:number,
msg: string,
public readoly data:O
){}
}
// 使用
async function request<D>(api: string): Promise<ResponseResult<D>> {
return await fetch(api).then(res => res.json)) as ResponseResult<D>
}
request<string>('').then(res=>{ 這里就可以直接使用res.data.字符串方法 })
// 可以看出調(diào)用和上面的接口泛型是一樣的
類型別名的泛型
type ResultType<O> {
code: number,
msg: string,
data: O
}
// 它的使用同上面,把Pormise里的泛型 ResponseResult 改成 ResultType 即可
// 泛型的默認(rèn)類型是unknown,但是我們可以給它添加默認(rèn)類型
interface Apiresult<O = object> {
code: number,
msg: string,
data: O
}
3、常用內(nèi)置泛型
Partical:手動(dòng)把對(duì)象里字段改成可選;
interface Person = {
name: string,
age: number
}
let xiaoming: Partical<Person> = {}
Required:把對(duì)象里的字段改成必選項(xiàng);
ReadOnly:把對(duì)象里的字段都改成只讀的;
Recode:聲明一個(gè)純對(duì)象,屬性名的類型由泛型參數(shù)決定,值由另一個(gè)泛型參數(shù)決定;
Pick:從一個(gè)對(duì)象中挑出指定的屬性;
Omit:從一個(gè)對(duì)象中刪除指定的屬性;
...還有更多其他內(nèi)置泛型,可以自行去看,可以幫助我們開(kāi)發(fā)中減少很多代碼量
4、高級(jí)類型
交叉類型
interface A1 {
a: string
}
type B1 {
b: number
}
class C1 {
constructor(public c: boolean)
}
type ABC = A1 & B1 & C1;
// 使用
const abc: ABC{
a: 'hello',
b: 1,
c: false
}
聯(lián)合類型
// 還是上面的
type ABC2 = A1 | B1 | C1;
// 使用
const abc2: ABC2 {
b:12
} // 聯(lián)合類型賦值的時(shí)候,必須是被聯(lián)合類型里的
類型保護(hù)
// 如果函數(shù)入?yún)⒔邮芤粋€(gè)聯(lián)合類型,實(shí)際使用的時(shí)候怎么判斷其對(duì)應(yīng)哪個(gè)類型呢?
function formateData(input: string | Data | number){
// 這里要返回格式化后的字符串,參數(shù)不確定
// typeof判斷類型,對(duì)應(yīng)去做處理,比如是number就toString后返回
// 我們可以使用前面寫的isString
}
五、注意事項(xiàng)
- 代碼啟用嚴(yán)格模式,讓ts編譯器嚴(yán)格檢查代碼,避免出錯(cuò),代碼更健壯;
- 避免使用any類型,雖然方便使用,但是失去了代碼提示的能力,多參數(shù)可以用聯(lián)合類型;
- 避免類型斷言的濫用,不判斷就直接斷言,可能會(huì)有程序崩潰;
- 使用readonly屬性和參數(shù),保護(hù)屬性,避免誤操作;
- 遵循命名約定;
- 使用類型檢測(cè)工具,對(duì)命名和規(guī)范進(jìn)行強(qiáng)約定;
- 編寫清晰的文檔和注釋,好的注釋能讓代碼更易讀易懂;
- 測(cè)試用例,出入?yún)?shù)覆蓋比例越大,函數(shù)或方法越健壯,程序也越穩(wěn)定。
使用場(chǎng)景
TypeScript非常適合用于大型項(xiàng)目的開(kāi)發(fā),因?yàn)樗峁┝遂o態(tài)類型檢查、代碼自動(dòng)補(bǔ)全、重構(gòu)等特性,有助于提高開(kāi)發(fā)效率和代碼質(zhì)量。此外,TypeScript還支持為現(xiàn)有的JavaScript庫(kù)添加類型信息,使得在開(kāi)發(fā)過(guò)程中能夠更好地利用這些庫(kù)。
下面有幾張圖,我們來(lái)看下TS在開(kāi)發(fā)過(guò)程中對(duì)類型檢查、代碼提示、自動(dòng)補(bǔ)全等方面的一個(gè)效果: