類型

基本類型&&變量聲明

類型定義

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為一個值)

指將多個字典類型合并為一個新的字典類型,既為...又為...(必須全部包含&左右兩測的所有信息,不能多也不能少)


image.png
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)系),可以是...也可以是...


image.png
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ù)類型

image.png

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

函數(shù)賦值.png

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

A參數(shù).png

B參數(shù).png

上述說的【變量 = 變量】比較變量類型,父集關(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)系(相同類型)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容