TS特有de概念
元組
指定數(shù)組每個(gè)元素的類型
特點(diǎn)
- 在初始化時(shí),必須按限定的類型和個(gè)數(shù)賦值;
- 可以通過(guò)數(shù)組的方法突破限制;
示例
let arr: [string, number, any]
arr = ['1', 1, 1] // OK
arr = [1, 1, 1] // Error
arr = ['1', 1, 1, true] // Error
arr.push(true) // OK
接口
只是在
ts
編譯階段做類型檢查,并不會(huì)轉(zhuǎn)譯成JS
代碼
用接口聲明可調(diào)用的類型
示例
// 定義函數(shù)
interface ISum {
(x: number, y: number, z?: number): number;
}
let sum: ISum
sum = (a) => {
// ^ = OK
return 1
}
sum('1', 2)
// ^ Argument of type '"1"' is not assignable to parameter of type 'number'.
// 定義類的new調(diào)用
interface IConstructor {
new(arg: string): IConstructor;
}
function Constr (this: any, name: string) {
this.name = name
}
const instance = new (Constr as any as IConstructor)('111')
以上示例注意:
-
interface
定義函數(shù)輸入、輸出的類型作為預(yù)置值內(nèi)置在函數(shù)聲明中- 函數(shù)聲明時(shí)無(wú)需二次定義參數(shù)類型,函數(shù)輸出值的類型推斷要與
interface
定義的輸出類型一致,否則會(huì)報(bào)錯(cuò)。 -
interface
定義函數(shù)沒(méi)有限制函數(shù)聲明時(shí)傳入的參數(shù)個(gè)數(shù),只有在調(diào)用時(shí)才會(huì)報(bào)參數(shù)個(gè)數(shù)錯(cuò)誤;
- 函數(shù)聲明時(shí)無(wú)需二次定義參數(shù)類型,函數(shù)輸出值的類型推斷要與
- 函數(shù)聲明無(wú)法直接其它類型,需要使用雙重?cái)嘌?code>Constr as any as IConstructor;
-
==盡可能不要使用雙重?cái)嘌?=,它會(huì)影響
ts
的判斷
// 示例: let num: number = 0 let str: string = 's' num = str // Error num = str as any as number // OK //^ = num === 's' //這里str認(rèn)為是number類型,賦值成功
-
==盡可能不要使用雙重?cái)嘌?=,它會(huì)影響
聯(lián)合類型
一個(gè)數(shù)據(jù)聲明為聯(lián)合類型,使用時(shí),若不確定是聯(lián)合類型中具體的類型時(shí)(通過(guò)if條件、as斷言、in操作符、typeof縮小未知范圍),只能訪問(wèn)聯(lián)合類型共有的方法。
斷言
斷言是聯(lián)合類型縮小未知范圍時(shí)使用,但,斷言強(qiáng)制瀏覽器相信數(shù)據(jù)是我們指定的類型,實(shí)際上是不安全的。【推薦】使用類型收縮
typeof
、instanceof
...來(lái)縮小未知范圍。
當(dāng)兩個(gè)類型聲明有交集時(shí),才可以使用斷言,否則會(huì)報(bào)錯(cuò)(because neither type sufficiently overlaps with the other.)
如果兩個(gè)類型聲明沒(méi)有交集,可以使用雙重?cái)嘌詮?qiáng)制斷言成另一種類型,示例如上:
Constr as any as IConstructor
readonly & Readonly泛型
readonly標(biāo)識(shí)符,用于對(duì)象屬性
Readonly映射類型,接收一個(gè)泛型T,用來(lái)把泛型T的所有屬性標(biāo)記為只讀類型;
示例
type Foo = {
bar: number;
bas: number;
}
type ReadFoo = Readonly<Foo>
/** ^ = type ReadFoo = {
* readonly bar: number;
* readonly bas: number;
* }
*/
示例
function fn(x: number | string): number {
return x.length;
}
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
對(duì)象結(jié)構(gòu)
示例
// type定義對(duì)象結(jié)構(gòu),不可重載
type TObjectProps = {
x: number;
y: number;
}
const obj: TObjectProps = {
x: 1,
y: 1
}
// interface定義對(duì)象結(jié)構(gòu)
interface IObjectProps {
x: number;
y: number;
}
const obj: IObjectProps = {
x: 1,
y: 1,
add() {}
//^ = 'add' does not exist in type 'IObjectProps'
}
// interface定義重載
interface IObjectProps {
x: number;
y: number;
}
const obj: IObjectProps = {
x: 1,
y: 1,
add() {} // OK
}
interface IObjectProps {
add: () => void;
}
// let & typeof定義對(duì)象結(jié)構(gòu),不可重載
let objectProps: {
x: number;
y: number;
}
const obj: typeof objectProps = {
x: 1,
y: 1
}
Function
函數(shù)類型聲明方式有多種,應(yīng)用場(chǎng)景兩種: 固定參數(shù),不固定參數(shù);
示例
固定參數(shù):
// type定義
type Tfn = (a: number, b: number) => number;
let fn1: Tfn
fn1 = function(a, b) {
return a + b
}
fn1(1, 2)
// type定義重載
type Tfn = {
(a: string): string;
(a: number, b: number): number;
}
let fn1: Tfn
fn1 = function(a: any, b?: any): any {
if (b) {
return a + b
} else {
return a
}
}
fn1('1')
// ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
// ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1) //Error
// interface定義
interface Ifn {
(x: string): string;
}
let fn1: Ifn
fn1 = function(a) {
return a
}
fn1('1')
// interface定義重載
interface Ifn {
(x: string): string;
(x: number, y: number): number;
}
let fn1: Ifn
fn1 = function(a: any, b?: any): any {
if (b) {
return a + b
} else {
return a
}
}
fn1('1')
// ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
// ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1) //Error
// let & typeof 聲明
let fn: {
(x: string): string;
}
let fn1: typeof fn
fn1 = function(a) {
return a
}
fn1('1')
// let & typeof聲明重載
let fn: {
(x: string): string;
(x: number, y: number): number;
}
let fn1: typeof fn
fn1 = function(a: any, b?: any) {
if (b) {
return a + b
} else {
return a
}
}
fn1('1')
// ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
// ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1) //Error
// function聲明
function fn(x: string): string {
return x
}
fn('1')
// function聲明重載:fn實(shí)現(xiàn)必須緊跟著fn頭聲明
function fn(x: string): string;
function fn(x: number, y: number): number;
function fn(x: any, y?: any) {
if (y) {
return x + y
} else {
return x
}
}
fn('1')
// ^ = let fn: (a: string) => string (+1 overload)
fn(1, 2)
// ^ = let fn: (a: number, b: number) => number (+1 overload)
fn(1) //Error
通過(guò)重復(fù)定義函數(shù)頭實(shí)現(xiàn)重載,而函數(shù)聲明輸入、輸出最好使用any
類型(不使用any
的話,函數(shù)體的操作邏輯使用的必須是聯(lián)合聲明類型共有的成員),調(diào)用時(shí)會(huì)根據(jù)參數(shù)個(gè)數(shù)匹配預(yù)定義的函數(shù)頭進(jìn)行校驗(yàn)。
不固定參數(shù):
// 應(yīng)用場(chǎng)景:作為回調(diào)函數(shù),通過(guò)`apply`調(diào)用;
interface IFn {
(...args: any[]): any;
}
function invoke(fn: IFn) {
fn.apply(null, [...arguments])
}
枚舉Enum
定義索引和值的雙向映射;
enum Direction {
UP,
DOWN,
LEFT,
RIGHT
}
console.log(Direction[0]) // 'UP'
console.log(typeof Direction[0]) // 'string'
console.log(Direction['UP']) // 0
console.log(typeof Direction['UP']) // 'number'
console.log(Direction[0] === 'UP') // true
分類
數(shù)字枚舉
數(shù)字枚舉默認(rèn)從0開始;
若有指定的索引,則后續(xù)數(shù)據(jù)索引++
// 場(chǎng)景:Code編碼語(yǔ)義化
enum Direction {
Up,
Down = 10,
Left,
Right
}
console.log(Direction[0]) // 'UP'
console.log(Direction['Up']) // 0
console.log(Direction['Left']) // 11
console.log(Direction[10]) // 'Down'
字符串枚舉
// 場(chǎng)景:游戲按鍵?
enum Direction {
Up = 'u',
Down = 'd',
Left = 'l',
Right = 'r'
}
console.log(Direction['Up']) // '上'
console.log(Direction['Down']) // '下'
常量枚舉
和上述枚舉類型編譯結(jié)果不同;
enum Dir {
Up,
Down = 10,
Left,
Right
}
const enum Direction {
Up,
Down = 10,
Left,
Right
}
let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];
/////編譯輸出如下:
"use strict";
var Dir;
(function (Dir) {
Dir[Dir["Up"] = 0] = "Up";
Dir[Dir["Down"] = 10] = "Down";
Dir[Dir["Left"] = 11] = "Left";
Dir[Dir["Right"] = 12] = "Right";
})(Dir || (Dir = {}));
let directions = [
0 /* Up */,
10 /* Down */,
11 /* Left */,
12 /* Right */,
];
字面量類型
const str: 'name' = 'name' // str只能是'name'字符串,賦值其他字符串或其他類型會(huì)報(bào)錯(cuò);
const number: 1 = 1 // number只能是1,賦值其他數(shù)值或其他類型會(huì)報(bào)錯(cuò);
type Directions = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
let toWhere: Directions = 'LEFT'
對(duì)應(yīng)場(chǎng)景:
interface IO {
'y+': number;
'M+': number;
'd+': number;
'h+': number;
'm+': number;
's+': number;
'q+': number;
'S+': number;
}
type TKeyProps = keyof IO
// ^ = type TKeyProps = "y+" | "M+" | "d+" | "h+" | "m+" | "s+" | "q+" | "S+"
var o: IO = {
'y+': this.getFullYear(),
'M+': this.getMonth() + 1,
'd+': this.getDate(),
'h+': this.getHours(),
'm+': this.getMinutes(),
's+': this.getSeconds(),
'q+': Math.floor((this.getMonth() + 3) / 3),
'S+': this.getMilliseconds()
}
o['y++'] // OK
let kkk = 's+'
o[kkk] // Error
o[kkk as TKeyProps] // OK
泛型
泛型de目的是在成員之間(至少有兩個(gè)地方用到了泛型占位)提供有意義的約束
成員:
- 類的屬性
- 類的方法
- 函數(shù)參數(shù)
- 函數(shù)返回值
泛型在定義時(shí),不能明確數(shù)據(jù)類型,聲明一變量占位,調(diào)用時(shí)通過(guò)傳入類型或
ts
類型推斷來(lái)確定具體的數(shù)據(jù)類型。
- 相對(duì)于
any
:泛型未丟失數(shù)據(jù)結(jié)構(gòu)信息; - 相對(duì)于聯(lián)合聲明:泛型明確具體的類型結(jié)構(gòu),聯(lián)合聲明并未明確具體類型;
邏輯中只能調(diào)用泛型數(shù)據(jù)的通用成員屬性/方法,否則會(huì)報(bào)錯(cuò);
示例
function identity<T>(arg: T): T {
return arg;
}
// 明確指定T是string類型
let output = identity<string>("myString"); // type of output will be 'string'
// 利用類型推論 -- 即編譯器會(huì)根據(jù)傳入的參數(shù)自動(dòng)地幫助確定T的類型
let output = identity("myString"); // type of output will be 'string'
any & never & unknown
-
any
- 稱為
top type
,任何類型的值都能賦給any
類型的變量 - 又稱為
bottom type
,任何類型(除never
外)的子類型 - 可以理解為沒(méi)有類型
- 稱為
-
never
稱為
bottom type
,任何類型的子類型,也可以賦值給任何類型-
推斷場(chǎng)景1:
無(wú)法執(zhí)行到函數(shù)終止點(diǎn)的函數(shù)表達(dá)式
- 場(chǎng)景:總拋出異常的函數(shù)表達(dá)式的返回值類型;
// 需要是函數(shù)表達(dá)式,若是函數(shù)聲明為assertNever: () => void const assertNever = function (x: any) { // ^ = type assertNever = never throw new Error("Unexpected object: " + x); }
- 場(chǎng)景:永不結(jié)束函數(shù)表達(dá)式的返回值類型;
let loop = function () { // ^ = type loop = never while (true) {} }
推斷場(chǎng)景2:被永不為真的類型保護(hù)約束的變量類型;
// 示例: type result = 1 & 2 // 結(jié)果為never // ^ = type result = never // 尤大示例: interface Foo { type: 'foo' } interface Bar { type: 'bar' } type All = Foo | Bar function handleValue(val: All) { switch (val.type) { case 'foo': // 這里 val 被收窄為 Foo break case 'bar': // val 在這里是 Bar break default: const exhaustiveCheck = val // ^ = type exhaustiveCheck = never break } }
-
unknown
-
top type
:任何類型都是它的subtype
; - 不能將
unknown
賦值其它類型,unknown
類型的數(shù)據(jù)只能賦值給unknown
、any
類型; - 相對(duì)于
any
,ts
會(huì)為unknown
提供有效的類型檢測(cè); - 若
unknown
類型數(shù)據(jù)的屬性或方法,需要通過(guò)類型斷言/類型收縮來(lái)縮小未知范圍;
-
any
的危害
使用
any
做類型聲明或者做斷言,會(huì)喪失原始數(shù)據(jù)的結(jié)構(gòu)類型信息,即:再也無(wú)法知道原始數(shù)據(jù)是什么結(jié)構(gòu)了,更不會(huì)有報(bào)錯(cuò)信息,推薦使用unknown
& 類型收縮;
索引簽名
索引簽名用于定義對(duì)象/數(shù)組de通配結(jié)構(gòu)
索引簽名的名稱(如:
{ [key: string]: number }
的key
)除了可讀性外,沒(méi)有任何意義,可以任意定義。
其它成員都必須符合索引簽名值de結(jié)構(gòu),所以,索引簽名值de結(jié)構(gòu)必須是其它成員屬性類型的
top type
。
盡量不要使用字符串索引簽名與有效變量混合使用——如果屬性名稱中有拼寫錯(cuò)誤,這個(gè)錯(cuò)誤不會(huì)被捕獲。【同級(jí)的其它屬性應(yīng)該是對(duì)索引簽名的限制增強(qiáng)】
示例
// 1. 對(duì)象示例
interface Foo {
[key: string]: number; // 該通配結(jié)構(gòu)[key: string]即是簽名索引。
}
// 1. 數(shù)組示例
interface Foo {
[idx: number]: string;
length: number;
}
const arr: Foo = ['1', '2', '3', '4']
// 2. 所有明確的成員都必須符合索引簽名
interface Bar {
[key: string]: number;
x: number; // OK
y: string;
//^ = Property 'y' of type 'string' is not assignable to string index type 'number'.
}
// 2. 可以使用交叉類型突破限制
interface Bar {
[key: string]: number;
x: number;
}
type Composition = Bar && {
y: string;
}
// 3. 有效變量和索引簽名不要同級(jí)定義
interface CSS {
[selector: string]: string;
color?: string;
}
const failsSilently: CSS = {
colour: 'red' // 'colour' 不會(huì)被捕捉到錯(cuò)誤
};
TS
類型聲明
ts
報(bào)錯(cuò)對(duì)應(yīng)查找:做詞典用;
declare
聲明通過(guò)各種途徑(<script>
導(dǎo)入、new webpack.ProvidePlugin({//...})
)引入全局命名空間的變量/模塊
函數(shù)聲明中顯式使用this
// 場(chǎng)景示例:
function Person() {
var vm = this;
// ^ = this具有隱式類型any;
}
// 解決方案:
function Person(this: void) {
var vm = this;
}
this
作為函數(shù)的第一參數(shù),由于ts
只是類型檢查,編譯成JS
時(shí)不會(huì)將this
參數(shù)輸出。
全局庫(kù)聲明
// 場(chǎng)景示例:
$('div')[]
//^ = not find name '$'
// 解決方案:追加zepto.d.ts聲明
interface ZeptoStatic {
//...
}
interface ZeptoCollection {
// ...
}
declare var Zepto: (fn: ($: ZeptoStatic) => void) => void;
declare var $: ZeptoStatic;
declare function $(selector?: string, context?: any): ZeptoCollection;
JS
內(nèi)置對(duì)象追加屬性聲明
// 場(chǎng)景示例:
Date.prototype.format = function() {
//...
}
const arg = 2423423413;
new Date().format(arg)
// ^ = format 不在Date上
// 解決方案:追加聲明*.d.ts聲明
declare global {
interface Date {
Format(arg: string): string;
}
}
圖片...靜態(tài)資源聲明
// 場(chǎng)景示例:
import Logo from './assets/logo.png'
// ^ = not find Module
// 解決方案:追加聲明*.d.ts聲明
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
給vue3
配置全局成員
// 場(chǎng)景示例:
// main.ts
import { createApp } from 'vue'
import axios from 'axios'
import qs from 'qs'
import App from './App.vue'
const vueInstance = createApp(App)
vueInstance.config.globalProperties.$http = axios
vueInstance.config.globalProperties.$qs = qs
// *.vue
...
this.$http.post(...).then(() => {})
// ^ = Property '$http' does not exist on type 'ComponentPublicInstance<>'...
...
// 解決方案:追加聲明*.d.ts
import Axios from "axios";
import qs from 'qs'
import Store from "../store";
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$http: Axios;
$store: Store;
$qs: qs;
}
}
第三方ES Module:export default
聲明
// 場(chǎng)景示例:
// utils.ts
export default (function() {
const utils = {
}
return utils
})()
// *.ts
utils.default.isString(123)
// ^ = Property 'default' does not exist on type 'typeof utils'
// 解決方案:追加聲明*.d.ts
declare namespace utils {
const UtilsProps: {
isString: (arg: unknown) => boolean;
isArray: (arg: unknown)=> boolean;
isObject: (arg: unknown)=> boolean;
isDate: (arg: unknown)=> boolean;
app: any;
}
export default UtilsProps;
}
第三方ES Module:export
聲明
// *.d.ts
declare namespace utils {
export function isString (arg: unknown): boolean;
export var app: any;
}
第三方CommonJS
模塊聲明
// 模塊類庫(kù) module-lib.ts
function moduleLib(options) {
console.log(options);
}
const version = "1.0.0";
function doSomething() {
console.log('moduleLib do something');
}
moduleLib.version = version;
moduleLib.doSomething = doSomething;
module.exports = moduleLib;
// *.d.ts
declare function moduleLib(options: Options): void;
interface Options {
[key: string]: any,
}
declare namespace moduleLib{
const version: string;
function doSomething(): void;
}
export = moduleLib;
第三方'UMD'聲明
// UMD庫(kù) umd-lib.js
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(factory);
} else if(typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.umdLib = factory();
}
})(this, function () {
return {
version: "1.0.2",
doSomething() {
console.log('umdLib do something');
}
}
});
// *.d.ts文件
declare namespace umdLib {
const version: string;
function doSomething(): void;
}
export as namespace umdLib // 專門為umd庫(kù)準(zhǔn)備的語(yǔ)句,不可缺少
export = umdLib // commonjs導(dǎo)出