Vue3 + TypeScript 實(shí)踐總結(jié)

為什么 TypeScript

Vue2.x 對(duì) TypeScript 的支持是硬傷旬陡,而 TypeScript 對(duì)于大型項(xiàng)目更加友好驶睦,團(tuán)隊(duì)協(xié)作時(shí)強(qiáng)類型總比約定更嚴(yán)謹(jǐn)廉羔。所以孩饼,一些大型工程在技術(shù)選型時(shí)此迅,拋棄 Vue 而選擇 React。尤其是在 React16.8+ 函數(shù)式組件有狀態(tài)以后。

為什么會(huì)出現(xiàn) setup

首先嘁酿,Vue 只是 MVC 中的 View 層,需要將 Model、Controller 分離開才能最大化地降低耦合、增加復(fù)用铐刘。即Vue 單文件組件應(yīng)該只是處理視圖層的業(yè)務(wù)邏輯及渲染即可俯艰,而 Controller 的業(yè)務(wù)邏輯不應(yīng)該在單文件組件中画株,因此誕生了 Vuex芹关,而 Vuex 更多是一個(gè)全局狀態(tài)管理诗祸,在處理一些業(yè)務(wù)邏輯怀樟、數(shù)據(jù)交互共耍、組件通信上,使用 Vuex 來管理顯得太重了。因而在 View 與 Controller 的業(yè)務(wù)邏輯處理上,有了一個(gè)真空地帶洲脂,導(dǎo)致我們?cè)?methods 中耦合了大量的業(yè)務(wù)邏輯疆液,隨著版本不斷迭代潘飘,一旦體量變大,公共方法的抽離掉缺、代碼維護(hù)的成本指數(shù)倍增長(zhǎng)卜录,組件已變得不可復(fù)用眶明,難以閱讀艰毒,能做的只能繼續(xù)往上面堆;

其次搜囱,一些需要響應(yīng)式的公共屬性丑瞧、方法,我們通常引入 mixin 來隔離犬辰,但是 mixin 不太好管理嗦篱,容易污染。

不難發(fā)現(xiàn)幌缝,業(yè)務(wù)邏輯耦合度變高后,最終導(dǎo)致 Vue 單文件組件的作為 View 層诫欠,頁(yè)面 UI 哪怕相似度極高涵卵,也可能無法復(fù)用浴栽。

高內(nèi)聚、低耦合轿偎,對(duì)于 Vue 來說典鸡,即需要能夠分離業(yè)務(wù)邏輯關(guān)注點(diǎn)、易管理坏晦、響應(yīng)式的API萝玷,setup 為了這而產(chǎn)生。

Vue 實(shí)踐

Props 自身的 type 屬性無法描述更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)昆婿,TS 會(huì)報(bào)錯(cuò)

Vue 提供了類型別名 PropType球碉,用于定義接收的更復(fù)雜的 props 屬性結(jié)構(gòu)。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n16" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">import { PropType, defineComponent, RendererElement } from 'vue';

interface ITabItem {
text: string,
id: string | number,
active?: boolean
}

const XComponent = defineComponent({
props: {
tabList: {
type: Array as PropType<Array<ITabItem>>,
default: () => []
}
},
setup() {
return (): RendererElement => (
<div>{
props.tabList.map(tab => {
return <p key={tab.id}>{tab.text}</p>
})
}</div>
)
}
})</pre>

單文件 vue 組件與 tsx 函數(shù)組件間的 slot 使用

單文件 vue 組件互相之間的 slot 使用仓蛆,可閱讀官方文檔 單文件插槽

jsx/tsx 組件互相之間的 slot 使用睁冬,可以閱讀文檔 jsx/tsx 插槽

單文件同 jsx、tsx 組件之間的 slot 使用看疙,示例如下:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="tsx" cid="n22" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// vue 單文件中引入 tsx 函數(shù)式組件 XInput
<section>
<x-input v-model="inputInfo.value">
<template v-slot:search>
<p>This is search slot.</p>
</templatet>
</x-input>
</section>

// XInput
const SearchBox = (props: IChildProp) => {
const value = props.value;
const clickEv = () => {
// todo
props.clickHandler?.();
}
return (
<div class="search-box" onClick={clickEv}>
<img src={searchIcon} />
</div>
)
}
export default defineComponent({
setup(props, { emit, slots }) {
const value = ref(props.value);
const inputEv = (ev: Event) => {
value.value = (ev.target as HTMLInputElement).value;
emit('update:input', value);
}
const searchEv = ():void => {
// todo
}
return (): RendererElement => (
<div class="x-input">
<input class="input-el" value={value.value} onInput={inputEv} />
{
slots.search &&
(<>
<SearchBox clickHandler={searchEv} value={value.value} />
{slot.search?.()} // 渲染成 <p>This is a search slot.</p>
</>)
}
</div>
)
}
})</pre>

自定義hooks——將“動(dòng)作”轉(zhuǎn)成 “狀態(tài)”

// todo

巧用類型收縮解決 TypeScript 報(bào)錯(cuò)

1豆拨、類型斷言

類型斷言可以明確告訴 TypeScript 值的詳細(xì)類型,在某些場(chǎng)景能庆,我們非常確認(rèn)它的類型施禾,即使與 typescript 推斷出來的類型不一致。

2搁胆、類型守衛(wèi)

  • typeof: 用于判斷 number, string, boolean 或 symbol 四種類型

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n36" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function padLeft(value: string | number) {
if (typeof value === string) {
console.log(value.length);
}
}</pre>

  • instanceof: 用于判斷一個(gè)實(shí)例是否屬于某個(gè)類

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n40" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">class Man {
    handsome = 'handsome'
    }
    class Woman {
    beautiful = 'beautiful'
    }
    function Human(arg: Man | Woman) {
    if (arg instanceof Man) {
    console.log(arg.handsome);
    } else {
    console.log(arg.beautiful);
    }
    }</pre>

  • in: 用于判斷一個(gè)屬性/方法是否屬于某個(gè)對(duì)象

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n43" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">interface B {
    b: string
    }
    interface A {
    a: string
    }
    function foo(x: A | B) {
    if ('a' in x) {
    return x.a;
    }
    return x.b;
    }</pre>

  • 字面量類型保護(hù)

    有些場(chǎng)景拾积,使用 in, instanceof, typeof 太過麻煩。這時(shí)候可以自己構(gòu)造一個(gè)字面量類型

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n48" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">type Man = {
    handsome: 'handsome';
    type: 'man';
    }
    type Woman = {
    beautiful: 'beautiful';
    type: 'woman';
    }
    function Human(arg: Man | Woman) {
    if (arg.type === 'man') {
    console.log(arg.handsome);
    } else {
    console.log(arg.beautiful);
    }
    }</pre>

3丰涉、雙重?cái)嘌?/h3>

有些時(shí)候使用 as 也會(huì)報(bào)錯(cuò)拓巧,因?yàn)?as 斷言的時(shí)候也不是毫無條件的。它只有當(dāng) S 類型是 T 類型的子集時(shí)一死,S能被斷言成T肛度。

所以面對(duì)這樣的情況,只想暴力解決問題投慈,可以使用雙重?cái)嘌猿泄ⅲ紫葦嘌猿杉嫒菟蓄愋偷?unknown。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n54" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function handler(event: Event) {
const element = event as HTMLElement; // Error
// 'Event' 和 'HTMLElement' 中的任何一個(gè)都不能復(fù)制給另一個(gè)
}

// 首先斷言成 unknown
function handler(event: Event) {
const element = (event as unknown) as HTMLElement;
}</pre>

4伪煤、用戶自定義的類型保護(hù)

假若我們一旦檢查過類型加袋,就能在之后的每個(gè)分支里清楚地知道它的類型就好了。 TypeScript 里的類型保護(hù)機(jī)制使其成為現(xiàn)實(shí)抱既。

類型保護(hù)就是一些表達(dá)式职烧,它們會(huì)在運(yùn)行時(shí)檢查以確保在某個(gè)作用域里的類型。要定義一個(gè)類型保護(hù),我們只要簡(jiǎn)單地定義一個(gè)函數(shù)蚀之,它的返回值是一個(gè) 類型謂詞

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n60" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}</pre>

在這個(gè)例子里蝗敢,pet is Fish 就是類型謂詞。謂詞為 parameterName is Type 這種形式足删,parameterName 必須是來自當(dāng)前函數(shù)簽名里的一個(gè)參數(shù)名寿谴。

當(dāng)使用一些變量調(diào)用 isFish 時(shí),TypeScript 會(huì)將變量縮減為那個(gè)具體的類型失受,只要這個(gè)類型與變量的原始類型是兼容的讶泰。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n63" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// TypeScript 不僅知道在 if 分支里 pet 是 Fish 類型;還清楚在 else 分支里拂到,一定不是 Fish 類型痪署,一定是 Bird 類型。
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}</pre>

5谆焊、使用 never 收窄類型

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n66" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">interface Foo {
type: 'foo'
}

interface Bar {
type: 'bar'
}
// 一個(gè)聯(lián)合類型 All
type All = Foo | Bar;

function handleValue(val: All) {
switch (val.type) {
case 'foo':
break;
case 'bar':
break;
default:
// val 在這里是 never
const exhaustiveCheck: never = val;
break;
}
}</pre>

注意在 default 里面我們把收窄為 never 的 val 賦值給了一個(gè)現(xiàn)實(shí)聲明為 never 的變量惠桃。如果一切邏輯正常,那么這里應(yīng)該能夠編譯通過辖试。但是假如后來有一天你的同事改了 All 的類型:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n69" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">type All = Foo | Bar | Baz</pre>

然而他忘記了在 handleValue 里面加上針對(duì) Baz 的處理邏輯辜王,這個(gè)時(shí)候 default 里面 val 會(huì)被收窄為 Baz,導(dǎo)致無法復(fù)制給 never罐孝,產(chǎn)生一個(gè)編譯錯(cuò)誤呐馆。所以通過這個(gè)辦法,可以確保 handleValue 總是窮盡了所有的 All 的可能類型莲兢。

6汹来、非空斷言運(yùn)算符!

這個(gè)運(yùn)算符可以用在變量名或者函數(shù)名之后改艇,用來強(qiáng)調(diào)對(duì)應(yīng)的元素是 非 null|undefined 的收班,應(yīng)用場(chǎng)景,特別適合我們已經(jīng)明確知道不會(huì)返回空值的場(chǎng)景谒兄,從而減少冗余的代碼判斷摔桦。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n75" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">this.$refs.container!.scrollWidth</pre>

7、鍵值獲取 keyof

keyof 可以獲取一個(gè)類型所有鍵值承疲,返回一個(gè)聯(lián)合類型

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n79" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">type Person = {
name: string;
age: number;
}
type PersonKey = keyof Person; // 'name' | 'age'</pre>

keyof 的一個(gè)典型應(yīng)用場(chǎng)景是限制訪問對(duì)象的 key 合法化邻耕,因?yàn)?any 做索引是不被接收的

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n81" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function getValue (p: Person, k: keyof Person) {
return p[k];
}</pre>

巧用高級(jí)類型靈活處理數(shù)據(jù)

1、類型索引

使用索引類型燕鸽,編譯器就能夠檢查使用了動(dòng)態(tài)屬性名的代碼兄世。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n89" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}

interface Person {
name: string;
age: number;
}
let person: Person {
name: 'Jack',
age: 18
}

let strings: string[] = pluck(person, ['name']);
</pre>

編譯器會(huì)檢查 name 是否真的是 Person 的一個(gè)屬性。

keyof T 索引類型查詢操作符啊研。對(duì)于任何類型 T御滩,keyof T 的結(jié)果為 T 上已知的公共屬性名的聯(lián)合鸥拧。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n93" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">let personProps: keyof Person; // 'name' | 'age';</pre>

T[K] 索引訪問操作符。在這里艾恼,類型語(yǔ)法反映了表達(dá)式語(yǔ)法住涉。就像索引類型查詢符一樣麸锉,可以在普通的上下文中使用 T[K]钠绍,只要確保類型變量 K extends keyof T 就可以了。

  • keyof: 獲取類型上的 key 值

  • extends: 泛型里面的約束

  • T[P]: 獲取對(duì)象 T 相應(yīng) K 的元素類型

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n103" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">type Partial<T> = {
    [P in keyof T]?: T[P]
    }
    // [自定義變量名 in 枚舉類型]: 類型</pre>

在使用 props 的時(shí)候花沉,有時(shí)候全部屬性都是可選的柳爽,如果一個(gè)一個(gè)屬性寫 ?碱屁,大量的重復(fù)動(dòng)作磷脯,這種時(shí)候可以直接使用 Partial<State>

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n108" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 如何用 ts 聲明 AnimalMap?
const AnimalMap = {
cat: { name: '貓', title: 'cat' },
dog: { name: '狗', title: 'dog' },
frog: { name: '蛙', title: 'wa' }
}

// Record 第一個(gè)泛型傳入對(duì)象的 key 值娩脾,第二個(gè)傳入對(duì)象的屬性
type Record<K extends string, T> = {
[P in K]: T;
}

type AnimalType = 'cat' | 'dog' | 'frog';
interface AnimalDescription {
name: string;
title: string
}

const AnimalMap: Record<AnimalType, AnimalDescription> {
cat: { name: '貓', title: 'cat' },
dog: { name: '狗', title: 'dog' },
frog: { name: '蛙', title: 'wa' }
}</pre>

2赵誓、可辨識(shí)聯(lián)合

合并單例類型,聯(lián)合類型柿赊,類型保護(hù)和類型別名來創(chuàng)建一個(gè)叫做 可辨識(shí)聯(lián)合 的高級(jí)模式俩功,它也稱作 標(biāo)簽聯(lián)合代數(shù)數(shù)據(jù) 數(shù)據(jù)類型∨錾可辨識(shí)聯(lián)合在函數(shù)式編程中很有用處诡蜓。

三要素:

  • 具有普通的單利類型屬性 —— 可辨識(shí)的特征

  • 一個(gè)類型別名包含了那些類型的聯(lián)合 —— 聯(lián)合

  • 此屬性上的類型保護(hù)

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n121" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">interface Square {
kind: 'square';
size: number
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface Circle {
kind: 'circle';
radius: number;
}

type Shape = Square | Rectangle | Circle;

function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}

// 可辨識(shí)聯(lián)合
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size;
case 'rectangle':
return s.height * s.width;
case 'circle':
return Math.PI * s.radius ** 2;
default:
return assertNever(s); // 處理遺漏的 case
}
}</pre>

assertNever 檢查 s 是否為 never 類型 —— 即為出去所有可能情況后剩下的類型。

裝飾器(Decorator)的理解

裝飾器(Decorator) 是一種特殊類型的聲明胰挑,它能夠被附加到類聲明蔓罚,方法訪問符瞻颂,屬性參數(shù) 上豺谈。裝飾器使用 @expression 這種形式,expression 求值后必須為一個(gè)函數(shù)贡这,它會(huì)在運(yùn)行時(shí)被調(diào)用茬末,被裝飾聲明信息作為參數(shù)傳入,它添加額外的方法或?qū)傩缘交惿吓号鳌槲覀冊(cè)陬惖穆暶骷俺蓡T上通過元編程語(yǔ)法添加標(biāo)注提供了一種方式团南。

我們簡(jiǎn)單的理解裝飾器,可以認(rèn)為它是一種包裝炼彪,對(duì)對(duì)象吐根、方法、屬性的包裝辐马。當(dāng)我們需要訪問一個(gè)對(duì)象的時(shí)候拷橘,如果我們通過這個(gè)對(duì)象外圍的包裝去訪問的話,被這個(gè)包裝附加的行為就會(huì)被觸發(fā)。例如一把加了消聲器的槍冗疮,消聲器就是一個(gè)裝飾器萄唇,但是它和原來的槍成為一個(gè)整體,開槍的時(shí)候消聲器就會(huì)發(fā)生作用术幔。

裝飾器組合

多個(gè)裝飾器可以同時(shí)應(yīng)用到一個(gè)聲明上:

  • 書寫在同一行上:

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n136" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">@f @g x</pre>

  • 書寫在多行上(常用):

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n139" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">@f
    @g
    x</pre>

當(dāng)多個(gè)裝飾器應(yīng)用于一個(gè)聲明上另萤,它們求值方式與 復(fù)合函數(shù) 相似。

當(dāng)多個(gè)裝飾器應(yīng)用在一個(gè)聲明上時(shí)會(huì)進(jìn)行如下步驟的操作(調(diào)用棧诅挑,后進(jìn)先出):

1四敞、由上至下依次對(duì)裝飾器表達(dá)式求值

2、求值的結(jié)果會(huì)被當(dāng)作函數(shù)拔妥,由下至上依次調(diào)用

leDecorator是welcome函數(shù)的裝飾器:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n147" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {
console.log('enter decorator');
const oldValue = descriptor.value;
descriptor.value = function() {
console.log(Calling "${propertyKey} with", arguments, target, target instanceof Object ,descriptor);
const value = oldValue.apply(null, [arguments[1], arguments[0]]);
console.log('Function is executed');
return ${value}; This is awesome;
};
return descriptor;
}

class JSMeetup {
public speaker = 'Ruban';
// @leDecorator
welcome(arg1, arg2) {
console.log(Arguments Received are ${arg1} ${arg2});
return ${arg1} ${arg2};
}
}

const meetup = new JSMeetup();
console.log(meetup.welcome('World', 'Hello'));

// 注釋掉 @leDecorator
/*
Arguments Received are World Hello
World Hello
*/

// 放開注釋
/*
enter decorator
Calling "welcome" with [Arguments] { '0': 'World', '1': 'Hello' } {}, true, {
value: [Function (anonymous)],
writable: true,
enumerable: false,
configuarble: true
}
Arguments Received are Hello World
Function is executed
Hello World; This is awesome
*/ </pre>

TypeScript 中共計(jì)有 5 類裝飾器:

  • 方法裝飾器

  • 屬性裝飾器

  • 類裝飾器

  • 參數(shù)裝飾器

  • 訪問器裝飾器

方法裝飾器

方法裝飾器聲明在一個(gè)方法的聲明之前(緊靠著方法聲明)忿危。它會(huì)被應(yīng)用到方法的屬性描述符上,可以用來監(jiān)視没龙、修改或者替換方法定義铺厨。方法裝飾器不能用在聲明文件(*.d.ts),重載或者任何外部上下文(比如 declare 的類)中硬纤。

下面是方法裝飾器的定義:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n165" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">MethodDecorator = <T>(target: object, key: string, decriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;</pre>

參數(shù):

  • target: 對(duì)于靜態(tài)成員來說是類的構(gòu)造函數(shù)(construcor)解滓,對(duì)于實(shí)例成員是類的原型對(duì)象(Object)

  • key: 成員的名字(被裝飾的函數(shù)名)

  • descriptor: 成員(被裝飾的函數(shù))的屬性描述符

下面是一個(gè)方法裝飾器(@writable)的例子,應(yīng)用于 Greeter 類的方法上:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n175" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function writable(value: boolean) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.writable = value;
return descriptor;
}
}

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
// @writable(false)
greet() {
return Hello, ${this.greeting};
}
}

const greeter = new Greeter('Tony');
greeter.greet = () => { return 'Hello, Jack' };
console.log(greeter.greet());

// 注釋修飾器writable
// Hello, Jack

// 放開注釋
// Hello, Tony</pre>

這里的 @writable(false) 是一個(gè) 裝飾器工廠 咬摇。當(dāng)裝飾器 @writable(false) 被調(diào)用時(shí)伐蒂,它會(huì)修改屬性描述符的 writable 屬性。

注意點(diǎn):

  • 裝飾器在 class 被聲明的時(shí)候執(zhí)行肛鹏,而不是class實(shí)例化的時(shí)候

  • 方法裝飾器返回一個(gè)值

  • 存儲(chǔ)原有的描述符并且返回一個(gè)新的描述符是推薦的做法逸邦。這在多描述符應(yīng)用場(chǎng)景下非常有用

  • 設(shè)置描述符value的時(shí)候,不要使用箭頭函數(shù)

屬性裝飾器

通過屬性裝飾器在扰,可以重新定義 getter, setter, 修改 enumerable, writable, configurable 等屬性缕减,也可以用來記錄這個(gè)屬性的元數(shù)據(jù)。

屬性裝飾器定義如下:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n193" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">PropertyDecorator = (target: object, key: string) => void;</pre>

屬性裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)調(diào)用芒珠,傳入下列2個(gè)參數(shù)

  • 對(duì)于靜態(tài)成員來說是類的構(gòu)造函數(shù)桥狡,對(duì)于實(shí)例成員是類的原型對(duì)象,即屬性擁有者

  • 成員的名字皱卓,屬性名

用 Object.defineProperty 來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的屬性裝飾器

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n203" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function realName(target, key: string): any {
let _val = target[key];

const getter = function() {
return ${_val} Ma;
}
const setter = function(newVal) {
_val = newVal;
}

Object.defineProperty(target, key, {
get: getter,
set: setter
})
}

class JSMeetup {
// @realName
public myName = 'Tony';
greet() {
return Hi, I'm ${this.myName};
}
}

const meetup = new JSMeetup();
console.log(meetup.greet());

// 注釋裝飾器realName
// Hi, I'm Tony

// 放開注釋
// Hi, I'm Tony Ma</pre>

metadata(元數(shù)據(jù))

描述數(shù)據(jù)的數(shù)據(jù)裹芝,也叫元數(shù)據(jù)。它是對(duì)一直心系的一種集合稱謂娜汁,它可以是描述某項(xiàng)具體的數(shù)值嫂易,也可以是描述影像或聲音的內(nèi)容,也可能 只是一些注釋掐禁。它們往往跟隨著對(duì)象文件怜械,讓我們得以更全面的了解對(duì)象相關(guān)的信息颅和。eg. 照片的元數(shù)據(jù)包括圖像的尺寸、拍攝時(shí)間缕允、光圈峡扩、快門、GPS等障本;視頻文件畫面尺寸教届、視頻和音頻的編碼、時(shí)長(zhǎng)等等彼绷。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n208" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">import 'reflect-metadata';

const formatMetadataKey = Symbol('format');

function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
@format('Hello, %s')
greeting: string;

constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, 'greeting');
return formatString.replace('%s', this.greeting);
}
}

const greeter = new Greeter('Tony');
console.log(greeter.greet());
// Hello, Tony</pre>

這個(gè) @format('Hello, $s') 裝飾器是一個(gè) 裝飾器工廠巍佑。被調(diào)用時(shí)茴迁,添加一條這個(gè)屬性的元數(shù)據(jù)寄悯。當(dāng) getFormat 被調(diào)用時(shí),它讀取對(duì)應(yīng)屬性的元數(shù)據(jù)堕义。

類裝飾器

類裝飾器應(yīng)用于類構(gòu)造函數(shù)猜旬,可以用來監(jiān)視,修改或替換類定義倦卖。如果類裝飾器返回一個(gè)值洒擦,它會(huì)使用提供的構(gòu)造函數(shù)來替換類的聲明。

類裝飾器定義如下:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n216" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">ClassDecorator = <T extends Function>(target: T) => T</pre>

類裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)調(diào)用怕膛,類的構(gòu)造函數(shù)作為其唯一的參數(shù)熟嫩。

無返回值:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n220" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 通過 Object.seal 密封此類的構(gòu)造函數(shù)和原型
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

// @sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
this.constructor.prototype.toString = function() {};
console.log(this.constructor.prototype);
}
greet() {
return Hello, ${this.greeting};
}
}

const greeter = new Greeter('Tony');
console.log(greeter.greet());

// 注釋裝飾器 sealed
// { toString: [Function (anonymous)] }
// Hello, Tony

// 取消注釋
// TypeError: Cannot add property toString, object is not extensible</pre>

有返回值:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n222" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">interface Extra {
extra: string
}

function AwesomeMeetup<T extends { new (...args: any[]): {} }>(constructor: T): T {
return class extends constructor implements Extra {
extra = 'Tadah!';
speaker: string = 'Ragularuban';
hello: string = 'Hello';
}
}

// @AwesomeMeetup
class JSMeetup {
public speaker = 'Ruban';
public hello: string;
constructor(message: string) {
this.hello = message;
}
greet() {
return Hi, I'm ${this.speaker};
}
}

const meetup = new JSMeetup('Hi') as JSMeetup & Extra;
console.log(meetup.greet());
console.log(meetup.extra);

class A extends JSMeetup {}

const a = new A('HeHe');
console.log(a.greet());

// 注釋裝飾器 AwesomeMeetup
/*
Hi, I'm Ruban
undefined
HeHe, I'm Ruban
*/

// 取消注釋
/*
Hello, I'm Ragularuban
Tadah!
Hello, I'm Ragularuban
*/</pre>

參數(shù)裝飾器

參數(shù)裝飾器應(yīng)用于類構(gòu)造函數(shù)或方法聲明,往往用來對(duì)特殊的參數(shù)進(jìn)行標(biāo)記褐捻,然后在方法裝飾器中讀取對(duì)應(yīng)的標(biāo)記掸茅,執(zhí)行進(jìn)一步的操作。

參數(shù)裝飾器往往搭配方法裝飾器一起使用

參數(shù)裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用柠逞,傳入下列3個(gè)參數(shù):

  • 對(duì)于靜態(tài)成員來說是類的構(gòu)造函數(shù)昧狮,對(duì)于實(shí)例成員是類的原型對(duì)象

  • 成員的名字

  • 參數(shù)在函數(shù)參數(shù)列表中的索引

參數(shù)裝飾器只能用來監(jiān)視一個(gè)方法的參數(shù)是否被傳入,參數(shù)裝飾器的返回值會(huì)被忽略板壮。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n237" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function logParameter(target: any, key: string, index: number) {
const metadataKey = 'myMetaData';
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
} else {
target[metadataKey] = [index];
}
console.log('param target: ', target);
}

function logMethod(target, key: string, descriptor: any): any {
const originalMethod = descriptor.value;
// 參數(shù)裝飾器逗鸣、方法裝飾器的第一個(gè)參數(shù) target 是同一個(gè)引用
console.log('method target: ', target);
descriptor.value = function(...args: any[]) {
const metadataKey = 'myMetaData';
const indices = target[metadataKey];
for (let i = 0; i < args.length; i++) {
if (indices.indexOf(i) !== -1) {
args[i] = 'Abrakadabra';
}
}
const result = originalMethod.apply(this, args);
return result;
}
return descriptor;
}

class JSMeetup {
// @logMethod
public saySomething(something: string, @logParameter somethingElse: string): string {
return ${something} : ${somethingElse};
}
}

const meetup = new JSMeetup();
console.log(meetup.saySomething('Something', 'Something Else'));

// 注釋掉方法裝飾器 logMethod
/*
param target: { myMetaData: [1] }
Something : Something Else
*/

// 取消注釋
/*
param target: { myMetaData: [1] }
method target: { myMetaData: [1] }
Something : Abrakadabra
*/
</pre>

訪問器裝飾器

訪問器裝飾器應(yīng)用于訪問器的屬性描述符,可以用來監(jiān)視绰精、修改或替換一個(gè)訪問器的定義撒璧。

不能向多個(gè)同名的 get/set 訪問器應(yīng)用裝飾器,get/set 只能選擇其一應(yīng)用

訪問器裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用笨使,傳入下列3個(gè)參數(shù):

  • 對(duì)于靜態(tài)成員來說是類的構(gòu)造函數(shù)卿樱,對(duì)于實(shí)例成員是類的原型對(duì)象

  • 成員的名字

  • 成員的屬性描述符

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n251" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function configurable(value: boolean) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// descriptor.writable = false 會(huì)報(bào)錯(cuò)后再賦值就會(huì)報(bào)錯(cuò)
descriptor.configurable = value;
}
}

class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}

@configurable(false)
get x() {
return this._x;
}

@configurable(false)
get y() {
return this._y;
}
}

function reWrite(value: number) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.get = function() {
return value;
}
return descriptor;
}
}

class Greeter {
_x: number;
_y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}

set x(value: number) {
this._x = value;
}

// @reWrite(999)
get x() {
return this._x;
}

getPoint() {
console.log(x: ${this.x}, y: ${this._y});
}
}

const greeter = new Greeter(100, 100);
greeter.getPoint();

// 注釋掉訪問器裝飾器 reWrite
// x: 100, y: 100

// 去取消注釋
// x: 999, y: 100</pre>

TypeScript 解決了哪些痛點(diǎn)

屬性、方法等訪問/調(diào)用錯(cuò)誤阱表,空指針等基于非預(yù)期類型的錯(cuò)誤

常見錯(cuò)誤:

  • Uncaught TypeError: Cannot read property

  • TypeError: 'undefined' is not an object

  • TypeError: null is not an object

  • TypeError: 'undefined' is not a function

  • TypeError: Cannot read property 'length'

  • ReferenceError: event is not defined

enum 枚舉

常見的應(yīng)用場(chǎng)景是描述某同一特征的常量殿如、元數(shù)據(jù)贡珊,例如任務(wù)類型可能被定義為 '1', '2', '3',不同的任務(wù)類型有不同的處理邏輯涉馁,通過 switch/if 判斷時(shí)门岔,我們直接 if(taskType === '1') 是不可以的,枚舉(enum) 的出現(xiàn)解決了這個(gè)問題烤送,并且讓代碼的可閱讀性更高寒随。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n273" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">enum TaskType {
DOWNLOAD: '1',
SHARE: '2',
FEED: '3'
}
switch (type) {
case DOWNLOAD:
// todo
break;
// ...
}

enum ResStatus {
EXPECT: '1001',
ACT_OVER: '3001',
NO_LOGIN: '3010'
}</pre>

裝飾器

一些底層的 Class 不能過多的業(yè)務(wù)邏輯或者耦合,或者在某些場(chǎng)景中帮坚,需要對(duì)元數(shù)據(jù)進(jìn)行操作(元編程)妻往,或在真實(shí)應(yīng)用中,不能盡善盡美试和,裝飾器可以看做是一種潤(rùn)滑劑讯泣、補(bǔ)丁、對(duì)元數(shù)據(jù)的操作(元編程)阅悍,或者說可以稱之為面向切面編程(AOP)好渠。

泛型

TypeScript 高級(jí)用法(字節(jié)前端)

泛型在 TS 中承載了從靜態(tài)定義到動(dòng)態(tài)調(diào)用的橋梁,同時(shí)也是 TS 對(duì)自己類型定義的元編程节视。

基本使用

泛型可以用在普通類型定義拳锚,類定義、函數(shù)定義上:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n285" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 普通類型定義
type Dog<T> = { name: string, type: T }
// 普通類型使用
// 需要把泛型類型也寫上去
const dog: Dog<number> = { name: 'wang', type: 3 }

// 類定義
class Cat<T> {
private type: T;
constructor(type: T) {
this.type = type;
}
}
// 類使用
// 變量能夠推斷出來寻行,可以省略泛型書寫
const cat: Cat<number> = new Cat<number>(20); // const cat = new Cat(20);

// 函數(shù)定義
function swipe<T, U>(value: [T, U]): [U, T] {
return [value[1], value[0]];
}
// 函數(shù)使用
// 變量能夠推斷出來霍掺,可以省略泛型書寫
swipe<Cat<number>, Dog<number>>([cat, dog]); // swipe([cat, dog]);</pre>

泛型約束

有的時(shí)候,可以不用關(guān)注泛型具體的類型

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n289" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function fill<T>(length: number, value: T): T[] {
return new Array(length).fill(value);
}</pre>

有時(shí)候拌蜘,需要限定類型杆烁,可以使用關(guān)鍵字 extends

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n291" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function sum<T extends number>(value: T[]): number {
let count = 0;
value.forEach(v => count += v);
return count;
}</pre>

這樣可以 sum([1, 2, 3]) 的方式調(diào)用,而 sum(['1', '2', '3']) 則無法通過編譯拦坠。

泛型推斷 infer

一般搭配泛型條件語(yǔ)句使用连躏,所謂推斷,就是不用預(yù)先指定在泛型列表中贞滨,在運(yùn)行時(shí)會(huì)自動(dòng)判斷入热,不過先得預(yù)先定義好整體的結(jié)構(gòu)。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n296" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">type Foo<T> = T extends {t: infer Test} ? Test: string;</pre>

首先看 extends 后面的類容晓铆,{t: infer Test} 可以看成一個(gè)包含 t 屬性的類型定義勺良,這個(gè) t 屬性的 value 類型通過 infer 進(jìn)行推斷后會(huì)賦值給 Test 類型,如果泛型實(shí)際參數(shù)符合 {t: infer Test} 的定義骄噪,那么返回 Test 類型尚困,否則默認(rèn)返回 string 類型。

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n298" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 返回 string链蕊,因?yàn)?number 不是一個(gè)包含 t 屬性的對(duì)象類型
type One = Foo<number>;

// 返回 boolean,因?yàn)榉盒蛥?shù)中有 t 屬性的對(duì)象類型,使用了 infer 對(duì)應(yīng)的類型年局,即 boolean
type Two = Foo<{t: boolean}>

// 同理,返回一個(gè)函數(shù)類型掌实,() => void
type Three = Foo<{a: numbem, t: () => void}></pre>

Tips

  • Vue 提供 interface RendererElement,可用于 渲染函數(shù)/tsx 中作為返回值校驗(yàn)

  • 聯(lián)合類型中巧用 never 類型邦马,可以處理一些遺漏的 case

  • interface HTMLInputElement 的 value 屬性獲取輸入框的值 (event.target as HTMLInputElement).value

  • 數(shù)據(jù)結(jié)構(gòu)過于復(fù)雜贱鼻、嵌套太深,類型判斷無法推斷時(shí)滋将,可以試著使用 ?. ?.() obj as IObject 等方式解決

  • usehooks 中如果涉及到元素獲取邻悬,最好使用 vue 單文件組件,ref 可以指向唯一值随闽,tsx 等渲染函數(shù)中無法通過 ref 定位元素父丰,當(dāng)組件復(fù)用高時(shí),通過 document.querySelector 拿到的不一定是預(yù)期元素(可以通過唯一 id 或 props 來解決)

  • setup 方法不能 async

  • 數(shù)組是一串相同類型的數(shù)據(jù)的集合橱脸,內(nèi)存中連續(xù)存放础米,實(shí)際上存放的是地址,指向?qū)ο髮?shí)例添诉,JS 中數(shù)組并不是真正意義上的數(shù)組,故 TypeScript 中的定義數(shù)組時(shí)医寿,需要指定數(shù)組元素中的類型

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="javascript" cid="n318" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">// js 的數(shù)組可以是任意類型
    const hunmanList = [18, 'Tony', { like: 'Reading' }]</pre>

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n320" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">interface Human {
    age: number,
    name: string
    }

    const humanList: Human[]
    const humanList: Array<Human></pre>

  • defineAsyncComponent 用于引用子組件處栏赴,接收的參數(shù)是一個(gè) Promise<Component>

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n323" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">const BannerDownload = defineAsyncComponent((): Promise<Component> => import('@/components/common/BannerDownload.vue'));</pre>

  • 通 react 一樣,類型斷言最好使用 el as HTMLElement 而非尖括號(hào) <HTMLElement>el

  • Vue3 暴露的 interface, type 在文件 @vue/runtime-core/dist/runtime-core.d.ts

  • 當(dāng)提示不能對(duì) null 進(jìn)行類型斷言時(shí)靖秩,先對(duì)變量類型簽名 unknown须眷,使用 unknown 替代 any,既靈活又可以保證類型安全

    • Vue3 setup 中獲取模板引用 ref沟突,需要 return花颗,示例如下:

    <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="typescript" cid="n333" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">onMounted(() => {
    const videoBox = ref(null);
    onMounted(() => {
    const el: unknown = videoBox.value;
    console.log(window.getComputedStyle(el as HTMLElement).width);
    })
    return { videoBox }
    })</pre>

  • TypeScript 內(nèi)置類型在 node_modules/typescript/lib 下的 *.d.ts 文件中

  • 全局安裝 ts-node 然后在 .ts 文件中第一行 *#!/usr/bin/env ts-node* 終端中輸入路徑即刻直接執(zhí)行 ts 文件

  • 構(gòu)造函數(shù)的類型可以通過 { new (...args: any[]): {} } 來描述

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惠拭,隨后出現(xiàn)的幾起案子扩劝,更是在濱河造成了極大的恐慌,老刑警劉巖职辅,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棒呛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡域携,警方通過查閱死者的電腦和手機(jī)簇秒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秀鞭,“玉大人趋观,你說我怎么就攤上這事扛禽。” “怎么了皱坛?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵旋圆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我麸恍,道長(zhǎng)灵巧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任抹沪,我火速辦了婚禮刻肄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘融欧。我一直安慰自己敏弃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布噪馏。 她就那樣靜靜地躺著麦到,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欠肾。 梳的紋絲不亂的頭發(fā)上瓶颠,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音刺桃,去河邊找鬼粹淋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瑟慈,可吹牛的內(nèi)容都是我干的桃移。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼葛碧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼借杰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起进泼,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤蔗衡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缘琅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粘都,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年刷袍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翩隧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖堆生,靈堂內(nèi)的尸體忽然破棺而出专缠,到底是詐尸還是另有隱情,我是刑警寧澤淑仆,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布涝婉,位于F島的核電站,受9級(jí)特大地震影響蔗怠,放射性物質(zhì)發(fā)生泄漏墩弯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一寞射、第九天 我趴在偏房一處隱蔽的房頂上張望渔工。 院中可真熱鬧,春花似錦桥温、人聲如沸引矩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旺韭。三九已至,卻和暖如春掏觉,著一層夾襖步出監(jiān)牢的瞬間区端,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工履腋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留珊燎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓遵湖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親晚吞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子延旧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Vue3 API 匯總手冊(cè) setup setup 函數(shù)是一個(gè)新的組件選項(xiàng)。作為在組件內(nèi)使用 Compositio...
    硅谷干貨閱讀 1,642評(píng)論 0 14
  • 本文記錄了 TypeScript 中的基礎(chǔ)變量類型和使用方式槽地,以及在 Vue2 框架中引入的調(diào)整迁沫。 類型 布爾值l...
    聚散浮生乄閱讀 241評(píng)論 0 0
  • 參數(shù)裝飾器 參數(shù)裝飾器聲明在一個(gè)參數(shù)聲明之前(緊靠著參數(shù)聲明)。 參數(shù)裝飾器應(yīng)用于類構(gòu)造函數(shù)或方法聲明捌蚊。 參數(shù)裝飾...
    2o壹9閱讀 842評(píng)論 1 49
  • TypeScript 入門與實(shí)戰(zhàn) tsconfig.json 編譯配置文件 compilerOptions啟用 -...
    我叫Aliya但是被占用了閱讀 1,016評(píng)論 0 0
  • 前言 金九銀十集畅,又是一波跑路。趁著有空把前端基礎(chǔ)和面試相關(guān)的知識(shí)點(diǎn)都系統(tǒng)的學(xué)習(xí)一遍缅糟,參考一些權(quán)威的書籍和優(yōu)秀的文章...
    WEB前端含光閱讀 597評(píng)論 0 2