本文為翻譯文章粪摘,原文鏈接見(jiàn)文末
在第一部分中我們了解了許多基礎(chǔ)知識(shí)蓖宦,結(jié)束了語(yǔ)法的學(xué)習(xí),我們可以進(jìn)入下一個(gè)更有趣的部分:使用靜態(tài)類型的優(yōu)勢(shì)和劣勢(shì)
使用靜態(tài)類型的優(yōu)勢(shì)
靜態(tài)類型會(huì)給我們寫(xiě)代碼提供很多好處抹腿。下面我們就來(lái)探討一下彤路。
優(yōu)勢(shì)一:你可以盡早發(fā)現(xiàn)bug和錯(cuò)誤
靜態(tài)類型檢查允許我們?cè)诔绦驔](méi)有運(yùn)行之前就可以確定我們所設(shè)定的確定性是否是對(duì)的。一旦有違反這些既定規(guī)則的行為沫浆,它能在運(yùn)行之前就發(fā)現(xiàn)捷枯,而不是在運(yùn)行時(shí)。
一個(gè)例子:假設(shè)我們有一個(gè)簡(jiǎn)單的方法专执,輸入半徑淮捆,計(jì)算面積:
const calculateArea = (radius) => 3.14 * radius * radius;
var area = calculateArea(3);
// 28.26
現(xiàn)在,如果你要給radius
傳一個(gè)非數(shù)值的值(例如‘im evil’)
var area = calculateArea('im evil');
// NaN
會(huì)返回一個(gè)NaN
本股。如果其他某些功能要依賴這個(gè)方法攀痊,并且需要始終返回一個(gè)數(shù)值類型,那在這種返回結(jié)果下拄显,就會(huì)導(dǎo)致一個(gè)bug甚至崩潰苟径。不理想。躬审。棘街。
當(dāng)我們使用了靜態(tài)類型蟆盐,我們就可以準(zhǔn)確確認(rèn)一個(gè)方法的輸入與輸出:
const calculateArea = (radius: number): number => 3.14 * radius * radius;
現(xiàn)在在試著給calculateArea
方法傳入一個(gè)非數(shù)值類型,這時(shí)候Flow就會(huì)顯示如下信息:
calculateArea('Im evil');
^^^^^^^^^^^^^^^^^^^^^^^^^ function call
calculateArea('Im evil');
^^^^^^^^^ string. This type is incompatible with
const calculateArea = (radius: number): number => 3.14 * radius * radius;
^^^^^^ number
現(xiàn)在我們能確保這個(gè)方法只會(huì)接受有效的數(shù)值作為參數(shù)遭殉,并且返回一個(gè)有效的數(shù)值石挂。
因?yàn)轭愋蜋z查器會(huì)在你編碼的時(shí)候就告訴你錯(cuò)誤,所以這也就比你把代碼交付到客戶手中才發(fā)現(xiàn)一些錯(cuò)誤要更方面(或者說(shuō)付出更少的開(kāi)發(fā)與維護(hù)成本)险污。
優(yōu)勢(shì)二:起到在線文檔的功能
對(duì)我們和其他接觸我們代碼的人而言痹愚,類型就好像是一個(gè)文檔。
通過(guò)我之前工作項(xiàng)目里一大段代碼中的一個(gè)方法罗心,我們可以看看具體是怎么用的:
function calculatePayoutDate(quote, amount, paymentMethod) {
let payoutDate;
/* business logic */
return payoutDate;
}
第一眼看到這個(gè)方法(即使是第二眼里伯、第三眼……),我沒(méi)法搞清楚要如何使用它渤闷。
quote參數(shù)是一個(gè)數(shù)值型么疾瓮?還是一個(gè)boolean型?paymentMethod是一個(gè)對(duì)象么飒箭?還是僅僅就是一個(gè)代表著支付方式的字符串狼电?這個(gè)方法會(huì)返回一個(gè)表示日期的字符串么?還是一個(gè)Date
對(duì)象弦蹂?
沒(méi)有任何的提示……
這個(gè)時(shí)候我就只能各種看業(yè)務(wù)邏輯肩碟,在代碼庫(kù)里到處檢索,直到我最終搞清楚了凸椿。但是削祈,就為了要理解這簡(jiǎn)單的一個(gè)方法,費(fèi)了九牛二虎之力脑漫。
如果是另一種情況髓抑,我們向下面這樣來(lái)寫(xiě):
function calculatePayoutDate(
quote: boolean,
amount: number,
paymentMethod: string): Date {
let payoutDate;
/* business logic */
return payoutDate;
}
這一下就讓方法的輸入?yún)?shù)和返回值的類型變得清晰了。這一場(chǎng)景展示了优幸,我們?nèi)绾斡渺o態(tài)類型來(lái)表達(dá)方法的含義吨拍。通過(guò)這樣做,我們可以很好得與其他開(kāi)發(fā)者交流网杆,什么是我們的方法所期望的羹饰,而什么值又是他們希望從方法中獲取的。下一次他們使用到這個(gè)方法的時(shí)候碳却,就不會(huì)產(chǎn)生疑問(wèn)了队秩。
當(dāng)然,也有人會(huì)說(shuō)追城,在方法上加上一段注釋文檔不也能解決同樣的問(wèn)題么:
/*
@function Determines the payout date for a purchase
@param {boolean} quote - Is this for a price quote?
@param {boolean} amount - Purchase amount
@param {string} paymentMethod - Type of payment method used for this purchase
*/
function calculatePayoutDate(quote, amount, paymentMethod) {
let payoutDate;
/* .... Business logic .... */
return payoutDate;
};
這確實(shí)是個(gè)辦法刹碾。但是這種方式會(huì)更冗長(zhǎng)。除了冗長(zhǎng)之外座柱,由于可依賴性較低迷帜,同時(shí)缺乏結(jié)構(gòu)化物舒,因此像這樣的代碼注釋會(huì)難以維護(hù)。一些開(kāi)發(fā)者會(huì)寫(xiě)出不錯(cuò)的注釋戏锹,而另一些人的注釋可能含糊不清冠胯,甚至有些根本忘了寫(xiě)注釋。
尤其是當(dāng)你重構(gòu)代碼的時(shí)候锦针,你很容易就會(huì)忘記去更新相應(yīng)的注釋荠察。然而,類型聲明有著定義好的語(yǔ)法和結(jié)構(gòu)奈搜,不會(huì)隨著時(shí)間推移而消失——它們是被寫(xiě)在代碼里的悉盆。
優(yōu)勢(shì)三:減少了復(fù)雜的錯(cuò)誤處理
類型可以幫助我們減少?gòu)?fù)雜的錯(cuò)誤處理。讓我們重新回去看一看calculateArea
方法馋吗,看看它是怎么做到的:
const calculateAreas = (radii) => {
var areas = [];
for (let i = 0; i < radii.length; i++) {
areas[i] = PI * (radii[i] * radii[i]);
}
return areas;
};
這個(gè)方法是有效的焕盟,但是它不能正確處理無(wú)效的輸入?yún)?shù)。如果你想要確保這個(gè)方法能夠正確處理輸入?yún)?shù)非有效數(shù)組的情況宏粤,你可能需要把它改成下面這個(gè)樣子:
const calculateAreas = (radii) => {
// Handle undefined or null input
if (!radii) {
throw new Error("Argument is missing");
}
// Handle non-array inputs
if (!Array.isArray(radii)) {
throw new Error("Argument must be an array");
}
var areas = [];
for (var i = 0; i < radii.length; i++) {
if (typeof radii[i] !== "number") {
throw new Error("Array must contain valid numbers only");
} else {
areas[i] = 3.14 * (radii[i] * radii[i]);
}
}
return areas;
};
哈脚翘?就這點(diǎn)功能就要碼這么多行。
但是有了靜態(tài)類型绍哎,就變得簡(jiǎn)單了:
const calculateAreas = (radii: Array<number>): Array<number> => {
var areas = [];
for (var i = 0; i < radii.length; i++) {
areas[i] = 3.14 * (radii[i] * radii[i]);
}
return areas;
};
現(xiàn)在来农,這個(gè)方法實(shí)際上看起來(lái)就和最初的很類似,沒(méi)有了令人視覺(jué)上混亂的錯(cuò)誤處理崇堰。這還是很容易能看出它的優(yōu)勢(shì)的沃于,對(duì)吧?
優(yōu)勢(shì)四:使你在重構(gòu)時(shí)更有信心
我會(huì)通過(guò)一個(gè)趣事來(lái)解釋這一點(diǎn):我曾經(jīng)的工作涉及一個(gè)非常大型的代碼庫(kù)海诲,我們需要更新里面的User
類的一個(gè)方法揽涮。特別的,我們需要將方法中的一個(gè)參數(shù)從string
變?yōu)?code>object饿肺。
我改了這個(gè)方法,但是當(dāng)我提交更新的時(shí)候卻感到手腳發(fā)涼——這個(gè)方法在代碼庫(kù)里有太多的地方調(diào)用了盾似,以至于我不確定我是否已經(jīng)修改了所有的實(shí)例敬辣。如果有些不在測(cè)試幫助文件里的深層調(diào)用被我遺留了怎么辦呢?
唯一可以搞清楚這一點(diǎn)的方法就是零院,提交這份代碼并且祈禱不會(huì)出現(xiàn)一些錯(cuò)誤溉跃。
使用靜態(tài)類型將會(huì)避免這種情況。這會(huì)保證告抄,如果更新了方法撰茎,并且相應(yīng)地更新了類型定義,那么類型檢查器將會(huì)幫我去捕獲我遺漏的錯(cuò)誤打洼。我所需要做的就是龄糊,瀏覽這些錯(cuò)誤并修復(fù)它們逆粹。
優(yōu)勢(shì)五:將數(shù)據(jù)和行為分離
一個(gè)較少談及的關(guān)于靜態(tài)類型的優(yōu)點(diǎn)是,它可以幫助我們進(jìn)行數(shù)據(jù)和行為的分離炫惩。
讓我們?cè)賮?lái)回顧一下包含靜態(tài)類型的calculateAreas
方法:
const calculateAreas = (radii: Array<number>): Array<number> => {
var areas = [];
for (var i = 0; i < radii.length; i++) {
areas[i] = 3.14 * (radii[i] * radii[i]);
}
return areas;
};
思考一下我們是如何來(lái)編寫(xiě)僻弹、設(shè)計(jì)這個(gè)方法的。由于我們聲明了類型他嚷,我們必須先考慮我們打算要使用的數(shù)據(jù)類型蹋绽,以便于我們可以大體定義好輸入與輸出的類型。

這之后我們才會(huì)去填充具體的邏輯實(shí)現(xiàn)部分:

這個(gè)能力恰恰表明了筋蓖,數(shù)據(jù)和行為的分離是我們更明確我們的腦海中對(duì)方法的設(shè)想卸耘,同時(shí)也更準(zhǔn)確地傳達(dá)我們的意圖,這種方式減輕了無(wú)關(guān)的思維負(fù)擔(dān)粘咖,使我們對(duì)程序的思考更加純粹與清晰蚣抗。(譯者:這應(yīng)該是程序設(shè)計(jì)時(shí)對(duì)我們思維方式的一種提示、引導(dǎo)與規(guī)范)
優(yōu)勢(shì)六:幫助我們消除了一整類bug
作為JavaScript開(kāi)發(fā)者涂炎,我們最常遇見(jiàn)的錯(cuò)誤就是運(yùn)行時(shí)的類型錯(cuò)誤忠聚。
舉個(gè)例子,應(yīng)用程序最初是的狀態(tài)被定義為:
var appState = {
isFetching: false,
messages: [],
};
假設(shè)接下來(lái)我們創(chuàng)建了一個(gè)用來(lái)獲取messages數(shù)據(jù)的API唱捣。然后两蟀,我們的app有一個(gè)簡(jiǎn)單的組件會(huì)將messages(在state中定義的)作為屬性來(lái)裝載,并且顯示未讀數(shù)量和messages的列表:
import Message from './Message';
const MyComponent = ({ messages }) => {
return (
<div>
<h1> You have { messages.length } unread messages </h1>
{ messages.map(message => <Message message={ message } /> )}
</div>
);
};
如果這個(gè)獲取messages數(shù)據(jù)的API失敗了或是返回了undefined
震缭,在生產(chǎn)環(huán)境中這段程序就會(huì)出現(xiàn)一個(gè)類型錯(cuò)誤:
TypeError: Cannot read property ‘length’ of undefined
……然后我們的程序就崩潰了赂毯。你就這么失去了你的客戶。僵……
讓我們看看類型檢查在這有什么作用拣宰。首先是給應(yīng)用的state添加類型党涕。我會(huì)使用類型別名的功能,創(chuàng)建一個(gè)AppState
類型巡社,然后用它來(lái)定義state:
type AppState = {
isFetching: boolean,
messages: ?Array<string>
};
var appState: AppState = {
isFetching: false,
messages: null,
};
由于知道了獲取messages的API并不可靠膛堤,因此在這里我給messages
定義了一個(gè)為字符串?dāng)?shù)組的maybe
類型。
最后同樣的晌该,我們?cè)趘iew層組件中獲取messages肥荔,并將它渲染在組件中。
import Message from './Message';
const MyComponent = ({ messages }) => {
return (
<div>
<h1> You have { messages.length } unread messages </h1>
{ messages.map(message => <Message message={ message } /> )}
</div>
);
};
Flow會(huì)幫我們捕獲并將錯(cuò)誤告知我們:
<h1> You have {messages.length} unread messages </h1>
^^^^^^ property `length`. Property cannot be accessed on possibly null value
<h1> You have {messages.length} unread messages </h1>
^^^^^^^^ null
<h1> You have {messages.length} unread messages </h1>
^^^^^^ property `length`. Property cannot be accessed on possibly undefined value
<h1> You have {messages.length} unread messages </h1>
^^^^^^^^ undefined
{ messages.map(message => <Message message={ message } /> )}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `map`. Method cannot be called on possibly null value
{ messages.map(message => <Message message={ message } /> )}
^^^^^^^^ null
{ messages.map(message => <Message message={ message } /> )}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `map`. Method cannot be called on possibly undefined value
{ messages.map(message => <Message message={ message } /> )}
^^^^^^^^ undefined
Wow朝群!
由于我們將messages
定義為maybe
類型燕耿,因此messages
可以是null
或者undefined
。但在沒(méi)有進(jìn)行空檢查的情況下姜胖,我們?nèi)匀徊荒茉谒厦孢M(jìn)行一些操作(像是.length或.map)誉帅。因?yàn)槿绻?code>messages的值是null
或者undefined
的話,而我們又在它上面進(jìn)行了相關(guān)操作,程序同樣會(huì)報(bào)一個(gè)類型錯(cuò)誤蚜锨。
因此我們回頭去更新一下view層的代碼:
const MyComponent = ({ messages, isFetching }: AppState) => {
if (isFetching) {
return <div> Loading... </div>
} else if (messages === null || messages === undefined) {
return <div> Failed to load messages. Try again. </div>
} else {
return (
<div>
<h1> You have { messages.length } unread messages </h1>
{ messages.map(message => <Message message={ message } /> )}
</div>
);
}
};
現(xiàn)在Flow知道我們已經(jīng)對(duì)null
或者undefined
的情況進(jìn)行了處理档插,因此類型檢查器不會(huì)報(bào)任何錯(cuò)誤。當(dāng)然踏志,運(yùn)行時(shí)的類型錯(cuò)誤也就不會(huì)再來(lái)煩你啦阀捅。
優(yōu)勢(shì)七:減少單元測(cè)試的數(shù)量
在之前的部分我們已經(jīng)知道了,靜態(tài)類型是如何通過(guò)確保輸入與輸出的類型來(lái)幫助消除復(fù)雜的錯(cuò)誤處理针余。因此饲鄙,它也可以幫助我們減少單元測(cè)試的數(shù)量。
例如圆雁,在包含了錯(cuò)誤處理的動(dòng)態(tài)類型方法calculateAreas
中:
const calculateAreas = (radii) => {
// Handle undefined or null input
if (!radii) {
throw new Error("Argument is missing");
}
// Handle non-array inputs
if (!Array.isArray(radii)) {
throw new Error("Argument must be an array");
}
var areas = [];
for (var i = 0; i < radii.length; i++) {
if (typeof radii[i] !== "number") {
throw new Error("Array must contain valid numbers only");
} else {
areas[i] = 3.14 * (radii[i] * radii[i]);
}
}
return areas;
};
如果你是一個(gè)勤勞的程序員忍级,為了確保輸入?yún)?shù)在程序中處理正確,也許你會(huì)想對(duì)輸入?yún)?shù)的有效性添加測(cè)試:
it('should not work - case 1', () => {
expect(() => calculateAreas([null, 1.2])).to.throw(Error);
});
it('should not work - case 2', () => {
expect(() => calculateAreas(undefined).to.throw(Error);
});
it('should not work - case 2', () => {
expect(() => calculateAreas('hello')).to.throw(Error);
});
等等一系列測(cè)試伪朽。此外轴咱,我們很可能會(huì)忘了添加一下邊緣用例的測(cè)試,而恰好我們的用戶發(fā)現(xiàn)了它烈涮。
由于這些測(cè)試是基于我們所有想到的用例朴肺,因此就存在一些很容易被忽略的。
在另一方面坚洽,當(dāng)我們需要去定義一些類型:
const calculateAreas = (radii: Array<number>): Array<number> => {
var areas = [];
for (var i = 0; i < radii.length; i++) {
areas[i] = 3.14 * (radii[i] * radii[i]);
}
return areas;
};
不僅僅是確保了代碼實(shí)際運(yùn)行的結(jié)果和我們的意圖一致戈稿,也使得它們不容易被我們漏掉。不同于以經(jīng)驗(yàn)為主的測(cè)試讶舰,類型檢查是通用的鞍盗。
這個(gè)場(chǎng)景的意思是:在邏輯測(cè)試中,測(cè)試是很不錯(cuò)的跳昼;而在數(shù)據(jù)類型的測(cè)試中般甲,類型檢查則較好。將兩者結(jié)合可以起到一加一大于二的效果鹅颊。
優(yōu)勢(shì)八:提供了領(lǐng)域建模(domain modeling)工具
關(guān)于類型檢查敷存,我最喜歡的一個(gè)應(yīng)用場(chǎng)景是領(lǐng)域建模。領(lǐng)域模型是堪伍,一個(gè)同時(shí)包括了數(shù)據(jù)和基于該數(shù)據(jù)的行為的該領(lǐng)域的概念模型历帚。
A domain model is a conceptual model of a domain that includes both the data and behavior on that data.
理解如何使用類型來(lái)進(jìn)行領(lǐng)域建模的最好的方法就是看一個(gè)例子。
假設(shè)現(xiàn)在有一個(gè)應(yīng)用杠娱,用戶在這個(gè)平臺(tái)上有一種或多種購(gòu)買(mǎi)商品的支付方式。現(xiàn)在Paypal谱煤、信用卡和銀行賬戶這三種方式是可供選擇的摊求。
我們首先給這三種支付方式創(chuàng)建相應(yīng)的類型別名:
type Paypal = { id: number, type: 'Paypal' };
type CreditCard = { id: number, type: 'CreditCard' };
type Bank = { id: number, type: 'Bank' };
現(xiàn)在我們可以用這三種情況和或運(yùn)算符來(lái)定義我們的PaymentMethod
類型:
type PaymentMethod = Paypal | CreditCard | Bank;
這樣足夠了么?好吧刘离,我們知道室叉,當(dāng)我們使用支付方法時(shí)睹栖,我們需要請(qǐng)求一個(gè)API,并且茧痕,根據(jù)我們所處的不同的請(qǐng)求階段野来,我們的app的狀態(tài)也是不同的。因此踪旷,會(huì)有四種可能的狀態(tài):
- 我們已經(jīng)獲取到了支付方法
- 我們正在獲取支付方法
- 我們成功獲取了支付方法
- 我們嘗試去獲取了支付方法但是出現(xiàn)了錯(cuò)誤
但是我們上面這個(gè)簡(jiǎn)單的paymentMethods類型的模型并沒(méi)有覆蓋所有的場(chǎng)景曼氛。而是假設(shè)paymentMethods是始終存在的。
有什么辦法能給我們的應(yīng)用狀態(tài)建一個(gè)模型令野,使它居且僅居這四個(gè)狀態(tài)之一么舀患?讓我們來(lái)看一下:
type AppState<E, D>
= { type: 'NotFetched' }
| { type: 'Fetching' }
| { type: 'Failure', error: E }
| { type: 'Success', paymentMethods: Array<D> };
我們使用了“|”或運(yùn)算符來(lái)定義我們的應(yīng)用狀態(tài)是上面描述的這四種中的一種。注意我是怎么使用type
屬性來(lái)確定app的狀態(tài)是四個(gè)中的哪一個(gè)的气破。使用這種方式聊浅,我們就可以分析確定什么時(shí)候我們獲取到了支付方法,而是什么時(shí)候沒(méi)有现使。
你還會(huì)注意到低匙,我給應(yīng)用狀態(tài)傳入的泛型E
和D
。類型D
代表了用戶的支付方法(之前定義的PaymentMethod
)碳锈。我們沒(méi)有定義類型E
顽冶,這會(huì)是我們的錯(cuò)誤類型(error type)。下面我們可以這么做:
type HttpError = { id: string, message: string };
總得來(lái)說(shuō)殴胧,現(xiàn)在我們的應(yīng)用狀態(tài)的標(biāo)識(shí)是AppState<E, D>
——E
是HttpError
類型渗稍,D
是PaymentMethod
支付方式。并且AppState
有四個(gè)可能(且只有這四種)的狀態(tài):NotFetched
团滥、Fetching
竿屹、Failure
和Success
。

我發(fā)現(xiàn)這種領(lǐng)域建模的類型對(duì)我們思考user interfaces的構(gòu)建而不是特定的業(yè)務(wù)邏輯很有幫助灸姊。業(yè)務(wù)規(guī)則告訴我們拱燃,我們的應(yīng)用只會(huì)是這些狀態(tài)中的一個(gè)。這使得我們可以明確地構(gòu)建應(yīng)用的狀態(tài)力惯,并且保證其只會(huì)是預(yù)先定義的狀態(tài)中的一種碗誉。當(dāng)我們構(gòu)建了這樣一個(gè)模型(例如一個(gè)view層組件),很明顯父晶,我們需要去處理所有這四種可能的狀態(tài)哮缺。
更進(jìn)一步地,這段代碼變得很清晰(self-documenting)甲喝,你可以根據(jù)這些可能的場(chǎng)景立即搞清楚應(yīng)用狀態(tài)的結(jié)構(gòu)尝苇。
使用靜態(tài)類型的劣勢(shì)
正如生活與編程中的其他東西,靜態(tài)類型檢查也有著它的利弊取舍。
重要的一點(diǎn)是糠溜,我們學(xué)習(xí)并理解靜態(tài)類型淳玩,這樣就可以對(duì)何時(shí)靜態(tài)類型是有意義的與何時(shí)它們并沒(méi)那么有價(jià)值的問(wèn)題有一個(gè)明智的判斷。
下面就是一些相關(guān)的思考:
劣勢(shì)1:需要預(yù)先學(xué)習(xí)靜態(tài)類型相關(guān)知識(shí)
對(duì)初學(xué)者來(lái)說(shuō)非竿,JavaScript真的是一門(mén)不錯(cuò)的語(yǔ)言蜕着,原因之一就是在寫(xiě)代碼之前你不需要去了解完整的類型系統(tǒng)。
當(dāng)我最開(kāi)始學(xué)習(xí)Elm(一門(mén)靜態(tài)類型語(yǔ)言)红柱,類型相關(guān)的問(wèn)題經(jīng)常會(huì)出現(xiàn)承匣,我總是會(huì)寫(xiě)出一些類型定義相關(guān)的編譯錯(cuò)誤。
有效的學(xué)習(xí)一門(mén)語(yǔ)言的類型系統(tǒng)可以說(shuō)和學(xué)習(xí)語(yǔ)言本身一樣都是一場(chǎng)硬仗豹芯。因此悄雅,靜態(tài)類型使得Elm的學(xué)習(xí)曲線比JavaScript要陡峭。
尤其是對(duì)于一些初學(xué)者,學(xué)習(xí)語(yǔ)法本身就是一件高負(fù)荷的事情了。在去混雜著學(xué)習(xí)類型會(huì)搞垮他們秆乳。
劣勢(shì)2:代碼的冗長(zhǎng)會(huì)讓你頭疼
靜態(tài)類型經(jīng)常會(huì)讓代碼看起來(lái)更冗長(zhǎng)和雜亂。
例如容诬,我們要替換下面這段代碼:
async function amountExceedsPurchaseLimit(amount, getPurchaseLimit){
var limit = await getPurchaseLimit();
return limit > amount;
}
我們需要這么寫(xiě):
async function amountExceedsPurchaseLimit(
amount: number,
getPurchaseLimit: () => Promise<number>
): Promise<boolean> {
var limit = await getPurchaseLimit();
return limit > amount;
}
在譬如,替換下面這段:
var user = {
id: 123456,
name: 'Preethi',
city: 'San Francisco',
};
需要寫(xiě)成:
type User = {
id: number,
name: string,
city: string,
};
var user: User = {
id: 123456,
name: 'Preethi',
city: 'San Francisco',
};
顯然沿腰,我們添加了一些額外的代碼览徒。但是,有幾個(gè)觀點(diǎn)并不認(rèn)為這是不好的颂龙。
首先习蓬,正如我們之前所提到的,靜態(tài)類型幫助我們消除了一整類測(cè)試措嵌。一些開(kāi)發(fā)者會(huì)認(rèn)為這是一個(gè)很合理的這兩方面的權(quán)衡躲叼。
其次,正如之前看到的企巢,靜態(tài)類型有的時(shí)候可以消除復(fù)雜的錯(cuò)誤處理枫慷,反而可以較大的減少代碼的雜亂性。
很難說(shuō)靜態(tài)類型是否真的讓代碼變得冗長(zhǎng)了浪规,但我們可以在實(shí)際工作中保留對(duì)這個(gè)問(wèn)題的思考或听。
劣勢(shì)3:需要花時(shí)間去掌握類型
學(xué)會(huì)怎么最好地在程序里定義類型需要花很多時(shí)間來(lái)進(jìn)行實(shí)踐。而且笋婿,建立一個(gè)良好的意識(shí)——何時(shí)使用靜態(tài)類型誉裆、何時(shí)使用動(dòng)態(tài)類型,也需要認(rèn)真的思考和豐富的實(shí)踐經(jīng)驗(yàn)缸濒。
例如找御,我們可能采取的一種方式是元镀,對(duì)關(guān)鍵的業(yè)務(wù)邏輯使用類型,而對(duì)臨時(shí)性的霎桅、不重要的邏輯部分使用動(dòng)態(tài)類型來(lái)降低不必要的復(fù)雜度。
這還是很難區(qū)分的讨永,尤其是當(dāng)開(kāi)發(fā)者對(duì)類型使用的經(jīng)驗(yàn)較少時(shí)滔驶。
劣勢(shì)4:靜態(tài)類型可能會(huì)延緩一些開(kāi)發(fā)速度
正如前文提到的,當(dāng)我學(xué)習(xí)Elm時(shí)卿闹,學(xué)習(xí)使用類型給我?guī)?lái)了些小阻礙揭糕,尤其是當(dāng)我需要加一些或改一些代碼時(shí)。經(jīng)常出現(xiàn)編譯錯(cuò)誤讓我心煩意亂锻霎,很難感覺(jué)到自己的進(jìn)步著角。
有人認(rèn)為,靜態(tài)類型檢查在絕大多數(shù)情況下旋恼,可能會(huì)導(dǎo)致程序員精力不集中吏口,而我們知道,集中精神是寫(xiě)出好代碼的關(guān)鍵冰更。
不僅僅如此产徊,靜態(tài)類型檢查器也不總是完美的。有時(shí)候你知道自己需要怎么做而類型檢查器反而成為了絆腳石蜀细。
我相信還有一些其他需要取舍的地方舟铜,但上面這幾條是對(duì)我來(lái)說(shuō)很重要的。
下一部分奠衔,最終的結(jié)論
在最后一部分谆刨,通過(guò)討論使用靜態(tài)類型是否有意義,進(jìn)行總結(jié)归斤。
原文:Why use static types in JavaScript? The Advantages and Disadvantages)