基本類型&&變量聲明
類型定義
typescript中可以如下定義變量:
let a: string = '你好啊'
上述的: string是對a變量的類型定義
在我們并不對變量進行類型定義時,typescript也可以根據(jù)變量的類型進行推斷
let a = '你好啊'
a = 2 // ? 不能將類型“2”分配給類型“string”
由此看出,在初始化時,若沒有主動聲明變量類型,typescript也會自行推斷,若在被賦值時與初始化類型不同,則編譯時會報錯
數(shù)組定義
兩種方式:
let arr: number[] = [1,2,3] // 類型[]
let arr: Array<number> = [1,2,3] // Array<類型>
元組定義
元組類型允許表示一個已知元素數(shù)量和類型的數(shù)組,各元素的類型不必相同,
// 下面定義一個類型數(shù)組長度為3,類型分別為string,number,object的數(shù)組
interface Type {
a: number;
b: string;
}
let x: [string, number, Type]; // x為長度為3的數(shù)組
x = ['hello', 735, { a: 23, b: '你好'}]
x[3] = '23';// 不能將類型“"23"”分配給類型“undefined”。Tuple type '[string, number, Type]' of length '3' has no element at index '3'
any
在編程階段有可能存在還不清楚變量類型的情況,這種情況下,我們不希望類型檢查器對這些值進行檢查,而是直接讓他們通過編譯檢查,就用到了any,在變量賦值時,any與Object相似,下文會講到
// 下面代碼不會出現(xiàn)類型錯誤的提示
let a: any = 4;
a = "你好啊";
a = true;
Object與object
在javascript中萬物皆對象Object,因此,在賦值方向,Object與any有這類似的作用,Object類型的變量允許給它賦任意值,但與any相比,它不能在上面調(diào)用任意方法如下:
let A: any = 4;
A = '123';
A.ifItExists(); // 實際上在A上沒有ifItExists函數(shù),但是因為跳過編譯檢查,所以不會檢查這一步,因此不會報錯
A.toFixed();
let B: Object = 4;
B = '123'; // javascript中,萬物皆對象
B.toFixed(); // ?類型“Object”上不存在屬性“toFixed”
let C: object = 3; // ?不能將類型“3”分配給類型“object”
C = '123'; // object單純的指類型為對象
let D: {} = 2; // {} 與Object作用相同
D = '13';
D.toFixed(); // ?類型“{}”上不存在屬性“toFixed”
這里面any和Object的區(qū)別就在于,any是跳過編譯檢查,既然已經(jīng)跳過了,那么在對這個變量做任何處理都不會報錯,Object類型的變量由number被賦值成了字符串成功,因為javascript中,萬物皆對象,都從對象繼承過來的,所以賦值會成功,但是toFixed()函數(shù)在Object中不存在,因此不能直接調(diào)用,{}作用與Object相同。Object的所有效果在{}都能表現(xiàn)出來。
object則單純指類型為對象,表示非原始類型,也就是除number,string,boolean,symbol,null或undefined之外的類型。
Void
某種程度上來說,void類型像是與any類型相反,它表示沒有任何類型。 當(dāng)一個函數(shù)沒有返回值時,你通常會見到其返回值類型是 void,用void聲明一個變量一般沒有什么意義,因為他只能賦值undefined,一般void用來聲明函數(shù)的返回值,當(dāng)沒有返回值時,使用void
let A: void = undefined;
let B: void = null; // ?不能將類型“null”分配給類型“void”
function fun1(): void {
console.log('你好啊');
}
Never
never類型表示的是那些永不存在的值的類型。一般不會有實際應(yīng)用,再次不做過多介紹。
any、 Object、 {}三者對比
當(dāng)定義一個值為對象類型的時候請設(shè)置他為object?。。⒁娚鲜鯫bject與object
你可能會試圖使用Object或{}來表示一個值可以具有任意屬性,因為Object是最通用的類型。 然而在這種情況下any是真正想要使用的類型,因為它是最靈活的類型。
比如,有一個Object類型的東西,你將不能夠在其上調(diào)用toLowerCase()。
越普通意味著更少的利用類型,但是any比較特殊,它是最普通的類型但是允許你在上面做任何事情。 也就是說你可以在上面調(diào)用,構(gòu)造它,訪問它的屬性等等。 記住,當(dāng)你使用any時,你會失去大多數(shù)TypeScript提供的錯誤檢查和編譯器支持。
如果你還是決定使用Object和{},你應(yīng)該選擇{}。 雖說它們基本一樣,但是從技術(shù)角度上來講{}在一些深奧的情況里比Object更普通。
// 這兩種都是對的
const a:Object = 1
const a: {} = 1
// 所以單純設(shè)置一個值為對象,使用object
const a: object = {} //對的
const a: object = 1 // ?
Function、function、() => void
() => void只是簡單列舉一種,最普通的函數(shù)形式,其余的帶參數(shù)、有返回值的形式,可自行編寫,原理與此相同
- typescript中不存在function該種類型的變量類型
- Function只能定義函數(shù)類型,不能想Object可以給任何值定義類型(number、string等等),他只是函數(shù)的類型定義,因為他不是原型鏈的最底層,故而不向Object那么通用
- 在Function和() => void之間,最好選用() => void,因為表示的形式更具體,F(xiàn)unction表示所有可能的函數(shù)類型,() => void只表示符合該項規(guī)定的類型,() => void是Function子集
類型斷言
一個變量可能有多種變量類型,有時可能需要當(dāng)變量為一種類型時執(zhí)行一種操作,為另一種類型時,執(zhí)行另一個操作,這時就需要用到類型斷言,通過類型斷言,我們可以準(zhǔn)確的告訴編譯器我們想要做什么,這個動作僅在編譯時起作用。
類型斷言兩種方式:尖括號和as關(guān)鍵字
type Str = string | number;
let str: Str = '你好啊';
let strLen = (<string>str).length // <類型>變量
type Str = string | number;
let str: Str = '你好啊';
let strLen = (str as string).length // 變量 as 類型
若當(dāng)前的類型并不等于斷言的類型,則該條語句不被執(zhí)行
type Str = string | number;
let str: Str = 2;
let strLen = (str as string).length
console.log('strLen', strLen) // undefined
在JSX文件中TypeScript的類型斷言可以使用as,不允許使用尖括號方式
泛型
目的:用于提升代碼的重用性
泛型函數(shù)
泛型函數(shù)的定義與使用
// 普通函數(shù)形式
function hello <T>(arg: T): T { // hello <T>:定義的泛型變量,arg: T傳入?yún)?shù)類型,(arg: T): T返回值類型
return arg;
};
// ES6箭頭函數(shù)形式(泛型變量只有一個時,eg:T) 正確
const hello = <T extends Object>(arg: T): T => {
return arg;
};
// ES6箭頭函數(shù)形式(泛型變量只有一個時,eg:T) 錯誤
const hello = <T>(arg: T): T => {
return arg; // JSX element 'T' has no corresponding closing tag.ts
};
// ES6箭頭函數(shù)形式(泛型變量兩個及已上時) 錯誤
const hello = <T, U>(arg: T): T => {
return arg; // JSX element 'T' has no corresponding closing tag.ts
};
// 使用
let str = hello<string>('hello');
ES6箭頭函數(shù)使用泛型,且泛型變量只有一個時,必須使用<T extends Object>形式,原因:TypeScript不確定它是否可能是JSX開始標(biāo)記。 它必須選擇一個,所以它與JSX一起使用。如果你想要一個具有完全相同語義的函數(shù),可以明確列出T的約束,這打破了TypeScript的歧義,以便您可以使用泛型類型參數(shù)。 它也具有相同的語義,因為類型參數(shù)始終具有{}的隱式約束。
泛型變量
創(chuàng)建上述的泛型函數(shù),編譯器要求在函數(shù)體中正確使用類型,換句話說,你必須吧這些參數(shù)當(dāng)成時任意類型。比如上述函數(shù)中想要console.log(arg.length)就會立即報錯,因為存在一些類型并沒有l(wèi)ength屬性,這時可以優(yōu)化上面的函數(shù)
function hello <T>(args: T[]): T[] { // hello <T>:定義的泛型變量,arg: T傳入?yún)?shù)類型,(arg: T): T返回值類型
console.log(args.length);
return arg;
};
這樣泛型變量T就作為了數(shù)組的一部分屬性,而不是作為整體類型,增加了靈活性,此時傳入的參數(shù)會發(fā)生變化,由eg:string -->string[];
枚舉
TypeScript支持數(shù)字枚舉和字符串枚舉
數(shù)字枚舉
數(shù)字枚舉,后面的枚舉變量時遞增的,第一個枚舉變量默認值為0,后續(xù)依次遞增,若重新定義了第一個枚舉變量,則后續(xù)的枚舉變量在已定義的變量之上遞增,
存在反向映射,可以通過值value拿到命名的key
enum Type {
a, // 0
b, // 1
c = 8, // 8
d // 9
}
反向映射:
const value = Type.a; // 0
const key = Type[0]; // a
字符串枚舉
字符串枚舉沒有遞增的含義,每個枚舉成員必須手動初始化,不存在反向映射
enum Type {
a = 'a', // a
b = 'b', // b
c = 'c', // c
}
可以存在數(shù)字枚舉與字符串枚舉共存的情況,但是前提是字符串枚舉必須放在下面
enum Type {
a,
b,
c=8,
d='ddd',
e='eee'
}
enum 類型的變量不能在.d.ts文件中導(dǎo)出,會報Cannot find module './data'錯誤
高級類型
interface
描述對象的結(jié)構(gòu),對字典(數(shù)據(jù)結(jié)構(gòu))進行類型約束
interface Type {
a: number,
b: string,
c: number[],
[propName: string]: any;
}
let type : Type;
type.a = 1;
type.b = 'hello';
type.c = ['a','b'] // ? 報錯 不能將類型“string”分配給類型“number”
interface BCross {
a: number,
c: string
}
interface BCross {
c: number, // 后續(xù)屬性聲明必須屬于同一類型。屬性“c”的類型必須為“string”,但此處卻為類型“number”。
d: number,
}
const a: BCross = {
a: 1,
c: '3',
d: 3
}
兩次聲明同一接口,接口中若同一個變量為不同的類型,那么最終,這個變量的類型為初次定義這個變量的類型
交叉類型與聯(lián)合類型
交叉類型 &(A& B為一個值)
指將多個字典類型合并為一個新的字典類型,既為...又為...(必須全部包含&左右兩測的所有信息,不能多也不能少)

interface A {
a: string,
b: number
}
interface B {
b: number,
c: number
}
type Type = A & B;
let t: Type = {a: '12', b: 1, c: 1}; // t變量必須包含a、b、c三個key值,必須全部包括
let tt: Type = {a: '12', b: 1, c: 1, d:2}; // ?對象文字可以只指定已知屬性,并且“d”不在類型“Type”中
interface A {
a: string,
b: number
}
interface B {
b: number,
c: number
}
type Type = A & B;
let t: Type = {a: '12', b: 1, c: 1}; // t變量必須包含a、b、c三個key值
一般交叉類型只適用于對象,舉個例子,
type Type = number & string;
let t : Type = 1 // ? 不能將類型“1”分配給類型“never”
因為number和string類型沒有交集的情況。所以number & string后不會有能包含兩種的類型的值存在。所以上文說一般交叉類型只適用于對象
- 還存在另一種情況,上面代碼中的A、B中都有相同的b,他們的類型必須相同,否則,以此交叉類型為類型的變量會報錯
- A、B中若存在兩個相同的變量名,則兩個相同變量執(zhí)行&操作:A.a&B.a
interface A {
a: string,
b: number
}
interface B {
b: string,
c: number
}
type Type = A & B;
let t: Type = {a: '12', b: 1, c: 1}; // ? 不能將類型“number”分配給類型“never”
代碼中的key值b,會進行類似下面的操作:b:number & string;因此這樣的類型還是不存在,因此代碼中會報錯
總結(jié)一下:交叉類型就是將不同類型疊加成為新的類型,并且包含了所有類型(除非里面的類型為可選的eg:b?:string,這樣可以不用寫b)
聯(lián)合類型 |(A | B為一個集合)
表示一個變量可以是幾種類型之一(或的關(guān)系),可以是...也可以是...

type Type = number | string;
let t: Type = 1;
類型保護
要實現(xiàn)類型保護,只需要簡單地定義一個函數(shù)就可以,但返回值必須是一個主謂賓語句
function isTeacher(person: Teacher | Student): person is Teacher {
return (person as Teacher).teach !== undefined;
}
person is Teacher是類型保護語句,說明參數(shù)必須來自于當(dāng)前函數(shù)簽名(定義了函數(shù)或方法的輸入輸出)里的一個參數(shù)名
類型別名 type
使用type關(guān)鍵字來描述類型變量,使用類型別名或者類型集合創(chuàng)建一個新名字,類型別名可以是泛型,也可以是用類型別名在屬性里引用自己,聽起來比較像遞歸
// 普通
type Age = number;
// 泛型
type Person<T> = {
age: T;
}
// 類型別名在屬性里引用自己
type Person<T> = {
age: T;
mother: Person<T>
father: Person<T>
}
字面量類型
字面量類型通常結(jié)合聯(lián)合類型使用
// 最簡單的字面量類型
type Profession = 'teacher';
// 結(jié)合聯(lián)合類型
type Profession = 'teacher' | 'doctor' | 'student';
let person: Profession // person的值在 teacher、 doctor、student中
類型推導(dǎo)
在沒有明確指出類型的地方,TypeScript編譯器會自己推測出當(dāng)前變量的類型,TypeScript里的類型兼容性是基于結(jié)構(gòu)子類型的,只要滿足了子類型的描述,那么就可以通過編譯時檢查,TypeScript的設(shè)計思想比不是滿足正確的類型,而是滿足能正確通過編譯的類型,這就造成了運行時和編譯時可能存在類型偏差。以下類型是可以通過編譯的:
interface Person { // Person相當(dāng)于A
age: number;
}
class Father { // Father相當(dāng)于B
age: number;
name: string;
}
let person: Person;
person = new Father();
也就是說TypeScript結(jié)構(gòu)化類型系統(tǒng)的基本規(guī)則如下:如果A想要兼容y,那么B至少具有與x相同的屬性,
eg: A = B,將B賦值給A,要看A里的每個參數(shù)是否能在B中找到相對應(yīng)的參數(shù),即A的屬性個數(shù)<=B,從屬性上來講,B包含A,A?B
對象類型賦值
- 變量 = 值:遵循值類型與變量定義的類型相同原則
-
變量 = 變量:遵循變量定義的類型與變量定義的類型比較原則,詳情參見下方
變量 = 變量.png
上述說的【變量 = 變量】比較變量類型,父集關(guān)系都是指【非可選變量】,當(dāng)有可選變量時,比較前去除可選變量在進行變量值比較
拿一個賦值的例子解釋一下賦值時的過程
// 對象賦值
interface Person { // 編譯通過
age: number;
}
let person: Person;
const alice = { name: '123', age: 11}
person = alice;
// 檢查函數(shù)參數(shù)
function Test (person: Person){} // 編譯通過
Test(alice)
alice在賦值給person時,編譯器首先查看person中的每個屬性,看是否能在alice中找到所有person應(yīng)該有的屬性,在上面中發(fā)現(xiàn)person有的屬性age,aliceu 也有,因此就判斷賦值合理
這套賦值檢查的程序,在檢查函數(shù)參數(shù)時同樣奏效,編譯給通過
解釋一下常見的問題,a賦值給b可以,再這個操作之后將b賦值給a就ts報錯
interface A {
m: number;
n: string;
}
interface B {
m: number;
}
// 狀態(tài)一
let a: A = { m: 1, n:'1' };
let b: B = { m: 2 };
b = a;
a = b; // Property 'n' is missing in type 'B' but required in type 'A'.
// 狀態(tài)二
let a = { m: 1, n:'1' };
let b: B = { m: 2 };
b = a;
a = b; // Property 'n' is missing in type 'B' but required in type 'A'.
原因是:按照上文解釋,b中的的所有屬性,a中都有,所以可以將a賦值給b,但是也只是ts判斷賦值合理,雖然此刻b的值為{ m: 1,n:'1' },但是實際上他的值的類型還是B類型即interface B { m: number;},這個是定義變量時已經(jīng)確定了的,即便是值被更改,但是這個變量的類型也不會被更改
interface B {
m: number;
}
// 狀態(tài)一
let b: B;
b = { m: 3, n: 2 }; // 不能將類型“{ m: number; n: number; }”分配給類型“B”。對象文字可以只指定已知屬性,并且“n”不在類型“B”中。
// 狀態(tài)二
let b: { m:2 };
b = { m: 3, n: 2 }; // 不能將類型“{ m: number; n: number; }”分配給類型“B”。對象文字可以只指定已知屬性,并且“n”不在類型“B”中
上述狀態(tài)一和狀態(tài)二的原理其實是一樣的,b是被直接賦給了值,而不是付給一個變量,編譯器就不會去像之前一樣對比a、b兩個變量的屬性類型,那么他就會直接去對比當(dāng)前B的類型,發(fā)現(xiàn)B類型interface B { m: number; }中并不包含n這個屬性,ts校驗就會報錯
- 上面說的父子集關(guān)系,都是指對象中沒有可選參數(shù)的情況,如果有可選參數(shù),那么去除可選參數(shù)后,在進行關(guān)系比較,確定能否賦值(ps不懂的可以轉(zhuǎn)換思考,可選類型為:類型|undefined)
/*
interface AObj {
a: number;
b: number;
c: number;
}
interface BObj {
a: number;
}
interface CObj {
a: number;
d: string
}
interface DObj {
a: number;
b: string
}
interface EObj {
a: number;
d?: string
}
*/
// variableObj1為父集,variableObj2為子集
let variableObj1: AObj = {
a: 1,
b: 1,
c: 1
};
let variableObj2: BObj = {
a: 2,
};
variableObj2 = variableObj1;
// variableObj1 = variableObj2
// “=”右側(cè)的變量的值不完全包含左側(cè)變量的值,不包含的部分 為 可選項
let variableObj3: EObj = {
a: 3,
d: 'd'
}
variableObj3 = variableObj1;
// “=”右側(cè)的變量的值不完全包含左側(cè)變量的值,不包含的部分 不為 可選項
let variableObj4: CObj = {
a: 4,
d: 'd'
}
let variableObj5: DObj = {
a: 5,
b: 'b'
}
variableObj4 = variableObj1;// ?Property 'd' is missing in type 'AObj' but required in type 'CObj'
variableObj5 = variableObj1; // ?不能將類型“AObj”分配給類型“DObj”。屬性“b”的類型不兼容。
console.log(variableObj1, variableObj2, variableObj3, variableObj4, variableObj5)
注意:上述所有說的報錯,僅指ts校驗報錯,而并非不能使用,即使ts校驗報錯,也可以賦值成功,與javascript特性有關(guān)(弱類型語言)
函數(shù)賦值
在判斷兩個函數(shù)是否能夠賦值時,TypeScript對比的是函數(shù)簽名(輸入?yún)?shù)類型、輸出的數(shù)值類型),輸入?yún)?shù)的名字、輸出的值,是否相同無所謂,只看參數(shù)類型

eg:A = B,將B賦值給A,要看B里的每個參數(shù)是否能在A中找到相對應(yīng)的參數(shù),并且位置從第一個參數(shù)開始就類型對應(yīng),即A的屬性個數(shù)>=B,從屬性上來講,A包含B,A?B ,參照下圖

可以將函數(shù)中的參數(shù)轉(zhuǎn)變?yōu)榭梢岳斫獾膶ο笮问剑旅姘凑諈?shù)的每一位順序,做對應(yīng)的對象轉(zhuǎn)換,幫助更容易理解函數(shù)賦值,已上圖為例,函數(shù)參數(shù)類型,按照順序依次映射到對象的a-z字母中去


上述說的【變量 = 變量】比較變量類型,父集關(guān)系都是指【非可選變量】,當(dāng)有可選變量時,比較前去除可選變量在進行變量值比較
// 輸出類型不同
let fun1 = () => 0;
let fun2 = () => '1';
fun1 = fun2 // ?不能將類型“() => string”分配給類型“() => number”。
// 輸出值不同
let fun3 = () => 0;
let fun4 = () => 1;
fun3 = fun4
// 輸入類型比較
// 輸入?yún)?shù)名不同,類型相同
let fun5 = (a: number) => 0;
let fun6 = (b: number) => 0;
fun5 = fun6;
// A包含B,無可選參數(shù)情況
let fun7 = (a: number, b: number) => 0
let fun8 = (a: number) => 0
fun7 = fun8;
fun8 = fun7 // ?不能將類型“(a: number, b: number) => number”分配給類型“(a: number) => number”。
// A包含B,無可選參數(shù)情況,但是B中的參數(shù)位置,與A中參數(shù)的位值,不能做到從第一位開始的位置映射
let fun9 = (b: number, c: string) => 0;
let fun10 = (d: string) => 0;
fun9 = fun10; // ? 不能將類型“(d: string) => number”分配給類型“(b: number, c: string) => number”
// A不完全包含B,有可選參數(shù)情況
let fun11 = (a: number, b: number) => 0
let fun12 = (a: number, b: number, c?: string) => 0
fun11 = fun12
// A不完全包含B,有可選參數(shù)情況,B的可選參數(shù)類型與A不能做到位置映射
let fun13 = (a: number, b: number, c: number) => 0
let fun14 = (a: number, b?: string) => 0
fun13 = fun14 // ?不能將類型“(a: number, b?: string | undefined) => number”分配給類型“(a: number, b: number, c: number) => number”。
參上,可賦值形式為fun(A) = fun(B),要保證以下條件:
- 返回值【類型】相同
- 輸入?yún)?shù)【類型】轉(zhuǎn)變?yōu)閕nterface后,去除A,B末尾的可選參數(shù)
- A?B
- B中參數(shù)位置與A中參數(shù)位置,從第一位開始,一對一映射關(guān)系(相同類型)
