TypeScript 練習(xí)題(第二彈)
點(diǎn)擊藍(lán)色“腦洞前端”關(guān)注我喲
加個(gè)“星標(biāo)”,帶你揭開大前端的神秘面紗!
?這是腦洞前端第「106」篇原創(chuàng)文章
TypeScript 的學(xué)習(xí)資料非常多,其中也不乏很多優(yōu)秀的文章和教程。但是目前為止沒有一個(gè)我特別滿意的。原因有:
它們大多數(shù)沒有一個(gè)清晰的主線,而是按照 API 組織章節(jié)的,內(nèi)容在「邏輯上」比較零散。 大多是“講是什么,怎么用“,而不是”講為什么,講原理“。 大多數(shù)內(nèi)容比較枯燥,趣味性比較低。都是干巴巴的文字,沒有圖片,缺乏能夠引起強(qiáng)烈共鳴的例子。
因此我的想法是做一套不同市面上大多數(shù)的 TypeScript 學(xué)習(xí)教程。以人類認(rèn)知的角度思考問題,學(xué)習(xí) TypeScript,通過通俗易懂的例子和圖片來(lái)幫助大家建立 TypeScript 世界觀。
系列安排:
上帝視角看 TypeScript TypeScript 類型系統(tǒng) types 和 @types 是什么? 你不知道的 TypeScript 泛型(萬(wàn)字長(zhǎng)文,建議收藏) typeScript 配置文件該怎么寫? TypeScript 是如何與 React,Vue,Webpack 集成的? TypeScript 練習(xí)題
?目錄將來(lái)可能會(huì)有所調(diào)整。
?
注意,我的系列文章基本不會(huì)講 API,因此需要你有一定的 TypeScript 使用基礎(chǔ),推薦兩個(gè)學(xué)習(xí)資料。
深入理解 TypeScript[1] TypeScript 官方文檔[2]
結(jié)合這兩個(gè)資料和我的系列教程,掌握 TypeScript 指日可待。
接下來(lái),我們通過幾個(gè)方面來(lái)從宏觀的角度來(lái)看一下 TypeScript。
前言
本文涉及的題目一共十六道,全部都可以在 typescript-exercises[3] 上在線提交。

可以和標(biāo)準(zhǔn)答案進(jìn)行對(duì)比。
并且由于使用了瀏覽器緩存, 因此無(wú)需登錄的情況下也可以保證關(guān)掉頁(yè)面,你的答題進(jìn)度也會(huì)保留。
?想重置進(jìn)度,清空緩存,無(wú)痕模式或者換瀏覽器都可以。
?
題目中涉及到的知識(shí)點(diǎn)我基本也都在之前的文章中提到了,如果你沒有看過,強(qiáng)烈建議先完成前面的教程,然后將上面的題目自己做一遍之后再看本文。另外一定要按照順序讀, 因此前面的題目都是后面的鋪墊。
為了不讓文章太過于冗長(zhǎng), 本篇文章分兩次發(fā)布, 一次 8 道題,一共十五道。每道題都有思路,前置知識(shí)以及代碼。「這次給大家?guī)?lái)的是后 6 道」
?其中有一道題需要大家有函數(shù)式編程的知識(shí), 如果大家不知道會(huì)比較難以解釋。為了避免內(nèi)容太過分散,將這道題從我的題解中移除,故只有 6 道。
?
題目九
題目描述
Intro:
????PowerUsers?idea?was?bad.?Once?those?users?got
????extended?permissions,?they?started?bullying?others
????and?we?lost?a?lot?of?great?users.
????As?a?response?we?spent?all?the?remaining?money
????on?the?marketing?and?got?even?more?users.
????We?need?to?start?preparing?to?move?everything?to?a
????real?database.?For?now?we?just?do?some?mocks.
????The?server?API?format?was?decided?to?be?the?following:
????In?case?of?success:?{?status:?'success',?data:?RESPONSE_DATA?}
????In?case?of?error:?{?status:?'error',?error:?ERROR_MESSAGE?}
????The?API?engineer?started?creating?types?for?this?API?and
????quickly?figured?out?that?the?amount?of?types?needed?to?be
????created?is?too?big.
Exercise:
????Remove?UsersApiResponse?and?AdminsApiResponse?types
????and?use?generic?type?ApiResponse?in?order?to?specify?API
????response?formats?for?each?of?the?functions.
題目的大概意思是:之前都是寫死的數(shù)據(jù), 現(xiàn)在數(shù)據(jù)需要從接口拿,請(qǐng)你定義這個(gè)接口的類型。
題目?jī)?nèi)置代碼
interface?User?{
??type:?"user";
??name:?string;
??age:?number;
??occupation:?string;
}
interface?Admin?{
??type:?"admin";
??name:?string;
??age:?number;
??role:?string;
}
type?Person?=?User?|?Admin;
const?admins:?Admin[]?=?[
??{?type:?"admin",?name:?"Jane?Doe",?age:?32,?role:?"Administrator"?},
??{?type:?"admin",?name:?"Bruce?Willis",?age:?64,?role:?"World?saver"?},
];
const?users:?User[]?=?[
??{
????type:?"user",
????name:?"Max?Mustermann",
????age:?25,
????occupation:?"Chimney?sweep",
??},
??{?type:?"user",?name:?"Kate?Müller",?age:?23,?occupation:?"Astronaut"?},
];
export?type?ApiResponse?=?unknown;
type?AdminsApiResponse?=
??|?{
??????status:?"success";
??????data:?Admin[];
????}
??|?{
??????status:?"error";
??????error:?string;
????};
export?function?requestAdmins(callback:?(response:?AdminsApiResponse)?=>?void)?{
??callback({
????status:?"success",
????data:?admins,
??});
}
type?UsersApiResponse?=
??|?{
??????status:?"success";
??????data:?User[];
????}
??|?{
??????status:?"error";
??????error:?string;
????};
export?function?requestUsers(callback:?(response:?UsersApiResponse)?=>?void)?{
??callback({
????status:?"success",
????data:?users,
??});
}
export?function?requestCurrentServerTime(
??callback:?(response:?unknown)?=>?void
)?{
??callback({
????status:?"success",
????data:?Date.now(),
??});
}
export?function?requestCoffeeMachineQueueLength(
??callback:?(response:?unknown)?=>?void
)?{
??callback({
????status:?"error",
????error:?"Numeric?value?has?exceeded?Number.MAX_SAFE_INTEGER.",
??});
}
function?logPerson(person:?Person)?{
??console.log(
????`?-?${person.name},?${person.age},?${
??????person.type?===?"admin"???person.role?:?person.occupation
????}`
??);
}
function?startTheApp(callback:?(error:?Error?|?null)?=>?void)?{
??requestAdmins((adminsResponse)?=>?{
????console.log("Admins:");
????if?(adminsResponse.status?===?"success")?{
??????adminsResponse.data.forEach(logPerson);
????}?else?{
??????return?callback(new?Error(adminsResponse.error));
????}
????console.log();
????requestUsers((usersResponse)?=>?{
??????console.log("Users:");
??????if?(usersResponse.status?===?"success")?{
????????usersResponse.data.forEach(logPerson);
??????}?else?{
????????return?callback(new?Error(usersResponse.error));
??????}
??????console.log();
??????requestCurrentServerTime((serverTimeResponse)?=>?{
????????console.log("Server?time:");
????????if?(serverTimeResponse.status?===?"success")?{
??????????console.log(
????????????`???${new?Date(serverTimeResponse.data).toLocaleString()}`
??????????);
????????}?else?{
??????????return?callback(new?Error(serverTimeResponse.error));
????????}
????????console.log();
????????requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse)?=>?{
??????????console.log("Coffee?machine?queue?length:");
??????????if?(coffeeMachineQueueLengthResponse.status?===?"success")?{
????????????console.log(`???${coffeeMachineQueueLengthResponse.data}`);
??????????}?else?{
????????????return?callback(new?Error(coffeeMachineQueueLengthResponse.error));
??????????}
??????????callback(null);
????????});
??????});
????});
??});
}
startTheApp((e:?Error?|?null)?=>?{
??console.log();
??if?(e)?{
????console.log(
??????`Error:?"${e.message}",?but?it's?fine,?sometimes?errors?are?inevitable.`
????);
??}?else?{
????console.log("Success!");
??}
});
前置知識(shí)
泛型 回調(diào)函數(shù)
思路
我們還是直接看報(bào)錯(cuò)。

很明顯這個(gè)報(bào)錯(cuò)的原因是類型是 unknown, 因此我們只有將 unknown 改成正確的類型即可。
換句話說(shuō), 就是把這種地方改成正確類型即可。

題目描述說(shuō)了, 這個(gè) response 其實(shí)是從后端返回的。而后端返回的數(shù)據(jù)有固定的格式。比如獲取用戶列表接口:
type?UsersApiResponse?=
??|?{
??????status:?"success";
??????data:?User[];
????}
??|?{
??????status:?"error";
??????error:?string;
????};
其他接口也是類似, 不同的是 data 的類型。因此我們考慮使用泛型封裝,將 data 的類型作為參數(shù)即可。
從本質(zhì)上來(lái)說(shuō), 就是從后端取的數(shù)據(jù)有兩種大的可能, 一種是錯(cuò)誤, 一種是成功。兩者在同一接口同一時(shí)刻只會(huì)出現(xiàn)一個(gè),且必須出現(xiàn)一個(gè)。
而成功的情況又會(huì)隨著接口不同從而可能產(chǎn)生不同的類型。

這是明顯的使用 「或邏輯關(guān)系」 和「泛型進(jìn)行類型定義」的強(qiáng)烈信號(hào)。我們可以使用泛型做如下改造:
export?type?ApiResponse?=
??|?{
??????status:?"success";
??????data:?T;
????}
??|?{
??????status:?"error";
??????error:?string;
????};
那么上面的 UsersApiResponse 就可以變成:
type?UsersApiResponse?=?ApiResponse;
不懂的同學(xué)建議看下我之前的文章:- 你不知道的 TypeScript 泛型(萬(wàn)字長(zhǎng)文,建議收藏)
用同樣的套路把其他后端返回加上類型即可。
代碼
核心代碼:
export?type?ApiResponse?=
??|?{
??????status:?"success";
??????data:?T;
????}
??|?{
??????status:?"error";
??????error:?string;
????};
export?function?requestAdmins(
??callback:?(response:?ApiResponse)?=>?void
)?{
??callback({
????status:?"success",
????data:?admins,
??});
}
export?function?requestUsers(
??callback:?(response:?ApiResponse)?=>?void
)?{
??callback({
????status:?"success",
????data:?users,
??});
}
export?function?requestCurrentServerTime(
??callback:?(response:?ApiResponse<number>)?=>?void
)?{
??callback({
????status:?"success",
????data:?Date.now(),
??});
}
export?function?requestCoffeeMachineQueueLength(
??callback:?(response:?ApiResponse<number>)?=>?void
)?{
??callback({
????status:?"error",
????error:?"Numeric?value?has?exceeded?Number.MAX_SAFE_INTEGER.",
??});
}
題目十
題目描述
Intro:
????We?have?asynchronous?functions?now,?advanced?technology.
????This?makes?us?a?tech?startup?officially?now.
????But?one?of?the?consultants?spoiled?our?dreams?about
????inevitable?future?IT?leadership.
????He?said?that?callback-based?asynchronicity?is?not
????popular?anymore?and?everyone?should?use?Promises.
????He?promised?that?if?we?switch?to?Promises,?this?would
????bring?promising?results.
Exercise:
????We?don't?want?to?reimplement?all?the?data-requesting
????functions.?Let's?decorate?the?old?callback-based
????functions?with?the?new?Promise-compatible?result.
????The?final?function?should?return?a?Promise?which
????would?resolve?with?the?final?data?directly
????(i.e.?users?or?admins)?or?would?reject?with?an?error
????(or?type?Error).
????The?function?should?be?named?promisify.
Higher?difficulty?bonus?exercise:
????Create?a?function?promisifyAll?which?accepts?an?object
????with?functions?and?returns?a?new?object?where?each?of
????the?function?is?promisified.
????Rewrite?api?creation?accordingly:
????????const?api?=?promisifyAll(oldApi);
題目大意是:前面用的是基于 callback 形式的代碼, 他們對(duì)代碼進(jìn)行了重構(gòu),改造成了 Promise,讓你對(duì)基于 Promise 的接口進(jìn)行類型定義。
題目?jī)?nèi)置代碼
interface?User?{
??type:?"user";
??name:?string;
??age:?number;
??occupation:?string;
}
interface?Admin?{
??type:?"admin";
??name:?string;
??age:?number;
??role:?string;
}
type?Person?=?User?|?Admin;
const?admins:?Admin[]?=?[
??{?type:?"admin",?name:?"Jane?Doe",?age:?32,?role:?"Administrator"?},
??{?type:?"admin",?name:?"Bruce?Willis",?age:?64,?role:?"World?saver"?},
];
const?users:?User[]?=?[
??{
????type:?"user",
????name:?"Max?Mustermann",
????age:?25,
????occupation:?"Chimney?sweep",
??},
??{?type:?"user",?name:?"Kate?Müller",?age:?23,?occupation:?"Astronaut"?},
];
export?type?ApiResponse?=
??|?{
??????status:?"success";
??????data:?T;
????}
??|?{
??????status:?"error";
??????error:?string;
????};
export?function?promisify(arg:?unknown):?unknown?{
??return?null;
}
const?oldApi?=?{
??requestAdmins(callback:?(response:?ApiResponse )?=>?void)?{
????callback({
??????status:?"success",
??????data:?admins,
????});
??},
??requestUsers(callback:?(response:?ApiResponse )?=>?void)?{
????callback({
??????status:?"success",
??????data:?users,
????});
??},
??requestCurrentServerTime(callback:?(response:?ApiResponse<number>)?=>?void)?{
????callback({
??????status:?"success",
??????data:?Date.now(),
????});
??},
??requestCoffeeMachineQueueLength(
????callback:?(response:?ApiResponse<number>)?=>?void
??)?{
????callback({
??????status:?"error",
??????error:?"Numeric?value?has?exceeded?Number.MAX_SAFE_INTEGER.",
????});
??},
};
export?const?api?=?{
??requestAdmins:?promisify(oldApi.requestAdmins),
??requestUsers:?promisify(oldApi.requestUsers),
??requestCurrentServerTime:?promisify(oldApi.requestCurrentServerTime),
??requestCoffeeMachineQueueLength:?promisify(
????oldApi.requestCoffeeMachineQueueLength
??),
};
function?logPerson(person:?Person)?{
??console.log(
????`?-?${person.name},?${person.age},?${
??????person.type?===?"admin"???person.role?:?person.occupation
????}`
??);
}
async?function?startTheApp()?{
??console.log("Admins:");
??(await?api.requestAdmins()).forEach(logPerson);
??console.log();
??console.log("Users:");
??(await?api.requestUsers()).forEach(logPerson);
??console.log();
??console.log("Server?time:");
??console.log(
????`???${new?Date(await?api.requestCurrentServerTime()).toLocaleString()}`
??);
??console.log();
??console.log("Coffee?machine?queue?length:");
??console.log(`???${await?api.requestCoffeeMachineQueueLength()}`);
}
startTheApp().then(
??()?=>?{
????console.log("Success!");
??},
??(e:?Error)?=>?{
????console.log(
??????`Error:?"${e.message}",?but?it's?fine,?sometimes?errors?are?inevitable.`
????);
??}
);
前置知識(shí)
Promise promisify 泛型 高階函數(shù)
思路
題目給了一個(gè) promisefy, 并且類型都是 unknown,不難看出, 它就是想讓我們改造 promisefy 使其不報(bào)錯(cuò), 并能正確推導(dǎo)類型。
export?function?promisify(arg:?unknown):?unknown?{
??return?null;
}
我們先不考慮這個(gè)類型怎么寫,先把 promiify 實(shí)現(xiàn)一下再說(shuō)。這需要你有一點(diǎn)高階函數(shù)和 promise 的知識(shí)。由于這不是本文的重點(diǎn),因此不贅述。
export?function?promisify(fn)?{
??return?()?=>
????new?Promise((resolve,?reject)?=>?{
??????fn((response)?=>?{
????????if?(response.status?===?"success")?resolve(response.data);
????????else?reject(response.error);
??????});
????});
}
接下來(lái),我們需要給其增加類型簽名。
這個(gè) fn 實(shí)際上是一個(gè)函數(shù),并且又接受一個(gè) callback 作為參數(shù)。因此大概是這個(gè)樣子:
((something)?=?void)?=>?void
這里的 something 實(shí)際上我們?cè)谏弦还?jié)已經(jīng)解決了,直接套用即可。代碼:
(callback:?(response:?ApiResponse )?=>?void)?=>?void
整體代碼大概是:
export?function?promisify<T>(
??fn:?(callback:?(response:?ApiResponse)?=>?void)?=>?void
):?()?=>?Promise<T>?{
??//?上面的實(shí)現(xiàn)
}
代碼
核心代碼:
export?function?promisify<T>(
??fn:?(callback:?(response:?ApiResponse)?=>?void)?=>?void
):?()?=>?Promise<T>?{
??return?()?=>
????new?Promise((resolve,?reject)?=>?{
??????fn((response)?=>?{
????????if?(response.status?===?"success")?resolve(response.data);
????????else?reject(response.error);
??????});
????});
}
第十一題
題目描述
Intro:
????In?order?to?engage?users?in?the?communication?with
????each?other?we?have?decided?to?decorate?usernames
????in?various?ways.?A?brief?search?led?us?to?a?library
????called?"str-utils".?Bad?thing?is?that?it?lacks
????TypeScript?declarations.
Exercise:
????Check?str-utils?module?implementation?at:
????node_modules/str-utils/index.js
????node_modules/str-utils/README.md
????Provide?type?declaration?for?that?module?in:
????declarations/str-utils/index.d.ts
????Try?to?avoid?duplicates?of?type?declarations,
????use?type?aliases.
題目的意思是他們用到了一個(gè)庫(kù) str-utils,這個(gè)庫(kù)的人又沒給我們寫類型定義,于是我們不得不去自己寫(好真實(shí)的例子啊)。
其實(shí)就是讓我們實(shí)現(xiàn)以下函數(shù)的類型簽名:
import?{
??strReverse,
??strToLower,
??strToUpper,
??strRandomize,
??strInvertCase,
}?from?"str-utils";
題目?jī)?nèi)置代碼
//?declarations/str-utils/index.d.js
declare?module?"str-utils"?{
??//?export?const?...
??//?export?function?...
}
//?index.ts
import?{
??strReverse,
??strToLower,
??strToUpper,
??strRandomize,
??strInvertCase,
}?from?"str-utils";
interface?User?{
??type:?"user";
??name:?string;
??age:?number;
??occupation:?string;
}
interface?Admin?{
??type:?"admin";
??name:?string;
??age:?number;
??role:?string;
}
type?Person?=?User?|?Admin;
const?admins:?Admin[]?=?[
??{?type:?"admin",?name:?"Jane?Doe",?age:?32,?role:?"Administrator"?},
??{?type:?"admin",?name:?"Bruce?Willis",?age:?64,?role:?"World?saver"?},
??{?type:?"admin",?name:?"Steve",?age:?40,?role:?"Steve"?},
??{?type:?"admin",?name:?"Will?Bruces",?age:?30,?role:?"Overseer"?},
??{?type:?"admin",?name:?"Superwoman",?age:?28,?role:?"Customer?support"?},
];
const?users:?User[]?=?[
??{
????type:?"user",
????name:?"Max?Mustermann",
????age:?25,
????occupation:?"Chimney?sweep",
??},
??{?type:?"user",?name:?"Kate?Müller",?age:?23,?occupation:?"Astronaut"?},
??{?type:?"user",?name:?"Moses",?age:?70,?occupation:?"Desert?guide"?},
??{?type:?"user",?name:?"Superman",?age:?28,?occupation:?"Ordinary?person"?},
??{?type:?"user",?name:?"Inspector?Gadget",?age:?31,?occupation:?"Undercover"?},
];
const?isAdmin?=?(person:?Person):?person?is?Admin?=>?person.type?===?"admin";
const?isUser?=?(person:?Person):?person?is?User?=>?person.type?===?"user";
export?const?nameDecorators?=?[
??strReverse,
??strToLower,
??strToUpper,
??strRandomize,
??strInvertCase,
];
function?logPerson(person:?Person)?{
??let?additionalInformation:?string?=?"";
??if?(isAdmin(person))?{
????additionalInformation?=?person.role;
??}
??if?(isUser(person))?{
????additionalInformation?=?person.occupation;
??}
??const?randomNameDecorator?=
????nameDecorators[Math.round(Math.random()?*?(nameDecorators.length?-?1))];
??const?name?=?randomNameDecorator(person.name);
??console.log(`?-?${name},?${person.age},?${additionalInformation}`);
}
([]?as?Person[]).concat(users,?admins).forEach(logPerson);
//?In?case?if?you?are?stuck:
//?https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules
前置知識(shí)
如何給缺乏類型定義的第三方庫(kù)定義類型
思路
這個(gè)題目的考點(diǎn)就是「如何給缺乏類型定義的第三方庫(kù)定義類型」。
這個(gè)時(shí)候我們只要新建一個(gè)文件然后加入以下代碼即可。
declare?module?"str-utils"?{
??//?在這里定義類型
??//?export?const?...
??//?export?function?...
}
其中 str-utils 是那個(gè)可惡的沒有類型定義的庫(kù)的名字。
有了這個(gè)知識(shí),我們的代碼就簡(jiǎn)單了。
代碼
declare?module?"str-utils"?{
??//?export?const?...
??//?export?function?...
??export?function?strReverse(s:?string):?string;
??export?function?strToLower(s:?string):?string;
??export?function?strToUpper(s:?string):?string;
??export?function?strRandomize(s:?string):?string;
??export?function?strInvertCase(s:?string):?string;
}
第十二題
題目描述
Intro:
????We?have?so?many?users?and?admins?in?the?database!
????CEO's?father?Jeff?says?that?we?are?a?BigData
????startup?now.?We?have?no?idea?what?it?means,?but
????Jeff?says?that?we?need?to?do?some?statistics?and
????analytics.
????We've?ran?a?questionnaire?within?the?team?to
????figure?out?what?do?we?know?about?statistics.
????The?only?person?who?filled?it?was?our?coffee
????machine?maintainer.?The?answers?were:
?????*?Maximums
?????*?Minumums
?????*?Medians
?????*?Averages
????We?found?a?piece?of?code?on?stackoverflow?and
????compiled?it?into?a?module?`stats`.?The?bad
????thing?is?that?it?lacks?type?declarations.
Exercise:
????Check?stats?module?implementation?at:
????node_modules/stats/index.js
????node_modules/stats/README.md
????Provide?type?declaration?for?that?module?in:
????declarations/stats/index.d.ts
Higher?difficulty?bonus?exercise:
????Avoid?duplicates?of?type?declarations.
題目大概意思是又來(lái)了一個(gè)庫(kù),這個(gè)庫(kù)又沒有寫定義,我們又要自己寫。(真實(shí)++)
題目?jī)?nèi)置代碼
//?declartions/stats/index.d.ts
declare?module?"stats"?{
??export?function?getMaxIndex(input:?unknown,?comparator:?unknown):?unknown;
}
//?index.ts
import?{
??getMaxIndex,
??getMaxElement,
??getMinIndex,
??getMinElement,
??getMedianIndex,
??getMedianElement,
??getAverageValue,
}?from?"stats";
interface?User?{
??type:?"user";
??name:?string;
??age:?number;
??occupation:?string;
}
interface?Admin?{
??type:?"admin";
??name:?string;
??age:?number;
??role:?string;
}
const?admins:?Admin[]?=?[
??{?type:?"admin",?name:?"Jane?Doe",?age:?32,?role:?"Administrator"?},
??{?type:?"admin",?name:?"Bruce?Willis",?age:?64,?role:?"World?saver"?},
??{?type:?"admin",?name:?"Steve",?age:?40,?role:?"Steve"?},
??{?type:?"admin",?name:?"Will?Bruces",?age:?30,?role:?"Overseer"?},
??{?type:?"admin",?name:?"Superwoman",?age:?28,?role:?"Customer?support"?},
];
const?users:?User[]?=?[
??{
????type:?"user",
????name:?"Max?Mustermann",
????age:?25,
????occupation:?"Chimney?sweep",
??},
??{?type:?"user",?name:?"Kate?Müller",?age:?23,?occupation:?"Astronaut"?},
??{?type:?"user",?name:?"Moses",?age:?70,?occupation:?"Desert?guide"?},
??{?type:?"user",?name:?"Superman",?age:?28,?occupation:?"Ordinary?person"?},
??{?type:?"user",?name:?"Inspector?Gadget",?age:?31,?occupation:?"Undercover"?},
];
function?logUser(user:?User?|?null)?{
??if?(!user)?{
????console.log("?-?none");
????return;
??}
??const?pos?=?users.indexOf(user)?+?1;
??console.log(`?-?#${pos}?User:?${user.name},?${user.age},?${user.occupation}`);
}
function?logAdmin(admin:?Admin?|?null)?{
??if?(!admin)?{
????console.log("?-?none");
????return;
??}
??const?pos?=?admins.indexOf(admin)?+?1;
??console.log(`?-?#${pos}?Admin:?${admin.name},?${admin.age},?${admin.role}`);
}
const?compareUsers?=?(a:?User,?b:?User)?=>?a.age?-?b.age;
const?compareAdmins?=?(a:?Admin,?b:?Admin)?=>?a.age?-?b.age;
const?colorizeIndex?=?(value:?number)?=>?String(value?+?1);
export?{
??getMaxIndex,
??getMaxElement,
??getMinIndex,
??getMinElement,
??getMedianIndex,
??getMedianElement,
??getAverageValue,
};
console.log("Youngest?user:");
logUser(getMinElement(users,?compareUsers));
console.log(
??`?-?was?${colorizeIndex(getMinIndex(users,?compareUsers))}th?to?register`
);
console.log();
console.log("Median?user:");
logUser(getMedianElement(users,?compareUsers));
console.log(
??`?-?was?${colorizeIndex(getMedianIndex(users,?compareUsers))}th?to?register`
);
console.log();
console.log("Oldest?user:");
logUser(getMaxElement(users,?compareUsers));
console.log(
??`?-?was?${colorizeIndex(getMaxIndex(users,?compareUsers))}th?to?register`
);
console.log();
console.log("Average?user?age:");
console.log(
??`?-?${String(getAverageValue(users,?({?age?}:?User)?=>?age))}?years`
);
console.log();
console.log("Youngest?admin:");
logAdmin(getMinElement(admins,?compareAdmins));
console.log(
??`?-?was?${colorizeIndex(getMinIndex(users,?compareUsers))}th?to?register`
);
console.log();
console.log("Median?admin:");
logAdmin(getMedianElement(admins,?compareAdmins));
console.log(
??`?-?was?${colorizeIndex(getMedianIndex(users,?compareUsers))}th?to?register`
);
console.log();
console.log("Oldest?admin:");
logAdmin(getMaxElement(admins,?compareAdmins));
console.log(
??`?-?was?${colorizeIndex(getMaxIndex(users,?compareUsers))}th?to?register`
);
console.log();
console.log("Average?admin?age:");
console.log(
??`?-?${String(getAverageValue(admins,?({?age?}:?Admin)?=>?age))}?years`
);
前置知識(shí)
泛型 高階函數(shù) 如何給缺乏類型定義的第三方庫(kù)定義類型
思路
和上面的思路類似。唯一的不同的是這道題的需要實(shí)現(xiàn)的幾個(gè)方法支持不同的入?yún)㈩愋汀?/p>
import?{
??getMaxIndex,
??getMaxElement,
??getMinIndex,
??getMinElement,
??getMedianIndex,
??getMedianElement,
??getAverageValue,
}?from?"stats";
因此,我們考慮使用泛型來(lái)定義。知道了這個(gè), 代碼就不難寫。這是最最基本的泛型, 比我們前面寫的還簡(jiǎn)單。
代碼
declare?module?"stats"?{
??export?function?getMaxIndex<T>(
????input:?T[],
????comparator:?(a:?T,?b:?T)?=>?number
??):?number;
??export?function?getMaxElement<T>(
????input:?T[],
????comparator:?(a:?T,?b:?T)?=>?number
??):?T;
??export?function?getMinElement<T>(
????input:?T[],
????comparator:?(a:?T,?b:?T)?=>?number
??):?T;
??export?function?getMedianIndex<T>(
????input:?T[],
????comparator:?(a:?T,?b:?T)?=>?number
??):?number;
??export?function?getMedianElement<T>(
????input:?T[],
????comparator:?(a:?T,?b:?T)?=>?number
??):?T;
??export?function?getAverageValue<T>(
????input:?T[],
????getValue:?(a:?T)?=>?number
??):?number;
??export?function?getMinIndex<T>(
????input:?T[],
????comparator:?(a:?T,?b:?T)?=>?number
??):?number;
}
第十三題
題目描述
Intro:
????The?next?logical?step?for?us?is?to?provide?more
????precise?registration?date?for?our?users?and?admins.
????We've?approximately?made?up?dates?for?each?user?and
????admin?and?used?a?library?called?"date-wizard"?in
????order?to?pretty-format?the?dates.
????Unfortunately,?type?declarations?which?came?with
????"date-wizard"?library?were?incomplete.
????1.?DateDetails?interface?is?missing
???????time?related?fields?such?as?hours,?minutes?and
???????seconds.
????2.?Function?"pad"?is?exported?but?not?declared.
Exercise:
????Check?date-wizard?module?implementation?at:
????node_modules/date-wizard/index.js
????node_modules/date-wizard/index.d.ts
????Extend?type?declaration?of?that?module?in:
????module-augmentations/date-wizard/index.ts
題目大概意思是又來(lái)了一個(gè)庫(kù),這個(gè)庫(kù)又沒有寫定義,我們又要自己寫。(真實(shí)+++++++++++++)
題目?jī)?nèi)置代碼
//?module-augmentations/data-wizard/index.d.ts
//?This?enables?module?augmentation?mode.
import?"date-wizard";
declare?module?"date-wizard"?{
??//?Add?your?module?extensions?here.
}
//?index.ts
import?*?as?dateWizard?from?"date-wizard";
import?"./module-augmentations/date-wizard";
interface?User?{
??type:?"user";
??name:?string;
??age:?number;
??occupation:?string;
??registered:?Date;
}
interface?Admin?{
??type:?"admin";
??name:?string;
??age:?number;
??role:?string;
??registered:?Date;
}
type?Person?=?User?|?Admin;
const?admins:?Admin[]?=?[
??{
????type:?"admin",
????name:?"Jane?Doe",
????age:?32,
????role:?"Administrator",
????registered:?new?Date("2016-06-01T16:23:13"),
??},
??{
????type:?"admin",
????name:?"Bruce?Willis",
????age:?64,
????role:?"World?saver",
????registered:?new?Date("2017-02-11T12:12:11"),
??},
??{
????type:?"admin",
????name:?"Steve",
????age:?40,
????role:?"Steve",
????registered:?new?Date("2018-01-05T11:02:30"),
??},
??{
????type:?"admin",
????name:?"Will?Bruces",
????age:?30,
????role:?"Overseer",
????registered:?new?Date("2018-08-12T10:01:24"),
??},
??{
????type:?"admin",
????name:?"Superwoman",
????age:?28,
????role:?"Customer?support",
????registered:?new?Date("2019-03-25T07:51:05"),
??},
];
const?users:?User[]?=?[
??{
????type:?"user",
????name:?"Max?Mustermann",
????age:?25,
????occupation:?"Chimney?sweep",
????registered:?new?Date("2016-02-15T09:25:13"),
??},
??{
????type:?"user",
????name:?"Kate?Müller",
????age:?23,
????occupation:?"Astronaut",
????registered:?new?Date("2016-03-23T12:47:03"),
??},
??{
????type:?"user",
????name:?"Moses",
????age:?70,
????occupation:?"Desert?guide",
????registered:?new?Date("2017-02-19T17:22:56"),
??},
??{
????type:?"user",
????name:?"Superman",
????age:?28,
????occupation:?"Ordinary?person",
????registered:?new?Date("2018-02-25T19:44:28"),
??},
??{
????type:?"user",
????name:?"Inspector?Gadget",
????age:?31,
????occupation:?"Undercover",
????registered:?new?Date("2019-03-25T09:29:12"),
??},
];
const?isAdmin?=?(person:?Person):?person?is?Admin?=>?person.type?===?"admin";
const?isUser?=?(person:?Person):?person?is?User?=>?person.type?===?"user";
function?logPerson(person:?Person,?index:?number)?{
??let?additionalInformation:?string?=?"";
??if?(isAdmin(person))?{
????additionalInformation?=?person.role;
??}
??if?(isUser(person))?{
????additionalInformation?=?person.occupation;
??}
??let?registeredAt?=?dateWizard(
????person.registered,
????"{date}.{month}.{year}?{hours}:{minutes}"
??);
??let?num?=?`#${dateWizard.pad(index?+?1)}`;
??console.log(
????`?-?${num}:?${person.name},?${person.age},?${additionalInformation},?${registeredAt}`
??);
}
export?{?dateWizard?};
console.log("All?users:");
([]?as?Person[]).concat(users,?admins).forEach(logPerson);
console.log();
console.log("Early?birds:");
([]?as?Person[])
??.concat(users,?admins)
??.filter((person)?=>?dateWizard.dateDetails(person.registered).hours?10)
??.forEach(logPerson);
//?In?case?if?you?are?stuck:
//?https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules
//?https://www.typescriptlang.org/docs/handbook/declaration-merging.html
前置知識(shí)
interface 或 type 聲明自定義類型 如何給缺乏類型定義的第三方庫(kù)定義類型
思路
和上面兩道題思路一樣, 不用多說(shuō)了吧?
代碼
//?This?enables?module?augmentation?mode.
import?"date-wizard";
declare?module?"date-wizard"?{
??//?Add?your?module?extensions?here.
??function?dateWizard(date:?string,?format:?string):?string;
??function?pad(s:?number):?string;
??interface?DateDetails?{
????year:?number;
????month:?number;
????date:?number;
????hours:?number;
????minutes:?number;
????seconds:?number;
??}
??function?dateDetails(date:?Date):?DateDetails;
}
第十四題
需要大家有函數(shù)式編程的知識(shí), 如果大家不知道會(huì)比較難以解釋。為了避免內(nèi)容太過分散,將這道題從我的題解中移除。
對(duì)函數(shù)式編程感興趣的,也可是看下我之前寫的文章 函數(shù)式編程系列教程。
第十五題
題目描述
Intro:
????Our?attempt?to?Open?Source?didn't?work?quite?as
????expected.?It?turned?out?there?were?already?many
????existing?functional?JS?libraries.
????All?the?remaining?developers?left?the?company?as
????well.?It?seems?that?they?are?joining?a?very
????ambitious?startup?which?re-invented?a?juicer?and
????raised?millions?of?dollars.
????Too?bad?we?cannot?compete?with?this?kind?of
????financing?even?though?we?believe?our?idea?is
????great.
????It's?time?to?shine?for?the?last?time?and?publish
????our?new?invention:?object-constructor?as?our?CTO
????named?it.?A?small?library?which?helps
????manipulating?an?object.
Exercise:
????Here?is?a?library?which?helps?manipulating?objects.
????We?tried?to?write?type?annotations?and?we?failed.
????Please?help!
題目大概意思是函數(shù)式編程他們 hold 不住,于是又準(zhǔn)備切換到面向?qū)ο缶幊獭S谑悄阈枰a(bǔ)充類型定義使得代碼不報(bào)錯(cuò)。
題目?jī)?nèi)置代碼
export?class?ObjectManipulator?{
??constructor(protected?obj)?{}
??public?set(key,?value)?{
????return?new?ObjectManipulator({?...this.obj,?[key]:?value?});
??}
??public?get(key)?{
????return?this.obj[key];
??}
??public?delete(key)?{
????const?newObj?=?{?...this.obj?};
????delete?newObj[key];
????return?new?ObjectManipulator(newObj);
??}
??public?getObject()?{
????return?this.obj;
??}
}
前置知識(shí)
泛型 Omit 泛型 ES6 class keyof 使用 extends 進(jìn)行泛型約束 聯(lián)合類型
思路
這道題難度頗高,比前面的泛型題目都要難。也是本系列的壓軸題,我們重點(diǎn)講一下。
首先題目有五個(gè)報(bào)錯(cuò)位置, 報(bào)錯(cuò)信息都是隱式使用了 any , 因此我們的思路就是將五個(gè)地方顯式聲明類型即可。

從它的名字 ObjectManipulator 以及 api 可以看出, 它應(yīng)該可以存儲(chǔ)任何對(duì)象,因此使用泛型定義就不難想到。
你也可是把這個(gè) ObjectManipulator 想象成抽象包包。你的期望是限量款包包拍照的時(shí)候用,普通包包和閨蜜逛街的時(shí)候用,優(yōu)衣庫(kù)送的包包逛超市的時(shí)候用等等。
ObjectManipulator 是一個(gè)抽象的包包概念,不是具體的包, 比如當(dāng)你買一個(gè) LV 的包包的時(shí)候就是 ObjectManipulator。這樣當(dāng)你往 LV 里放超市買的水果的時(shí)候就可以報(bào)錯(cuò):你怎么可以用 LV 包包裝這樣?xùn)|西呢?你應(yīng)該用 ta 裝*。

?當(dāng)然這個(gè)例子很不嚴(yán)謹(jǐn), 這個(gè)只是幫助大家快速理解而已,切莫較真。
?
理解了題意,我們就可以開始寫了。
我們先改第一個(gè)錯(cuò) - 構(gòu)造函數(shù) constructor, 這個(gè)錯(cuò)比較簡(jiǎn)單。
export?class?ObjectManipulator?{
??constructor(protected?obj:?T)?{
????this.obj?=?obj;
??}
??...
}
這個(gè)時(shí)候經(jīng)過 ObjectManipulator 實(shí)例化產(chǎn)生的對(duì)象的 this.obj 都是 T 類型,其中 T 是泛型。因此 getObject 的錯(cuò)也不難改,返回值寫 T 就行。
export?class?ObjectManipulator?{
??...
??public?getObject():?T?{
????return?this.obj;
??}
}
剩下的 get,set 和 delete 思路有點(diǎn)類似。先拿 get 來(lái)說(shuō):
export?class?ObjectManipulator?{
??...
??public?get(key)?{
????return?this.obj[key];
??}
??...
}
這個(gè)怎么寫類型呢?key 理論上可是是任何值,返回值理論上也可以是任何值。但是一旦類型 T 確定了, 那么實(shí)際上 key 和返回值就不是任意值了。比如:
type?A?=?ObjectManipulator<{?name:?string;?age:?number?}>;
const?a:?A?=?new?ObjectManipulator({?name:?"",?age:?17?});
如上代碼中的 A 是 ObjectManipulator 傳入具體類型 { name: string; age: number } 產(chǎn)生的新的類型。
?我這里用的是行內(nèi)類型, 實(shí)際項(xiàng)目建議使用 interface 或者 type 定義類型。
?
之后我們模擬一些操作:
a.set("name",?"腦洞前端");
a.get("name");
a.get("name123");?//?期望報(bào)錯(cuò)
a.set("name123",?"腦洞");
a.delete("name123");?//?期望報(bào)錯(cuò)
a.delete("name");
實(shí)際上,我「可能」期望的是其中一些行為可以借助 TypeScript 的類型分析直接報(bào)錯(cuò)。
簡(jiǎn)單來(lái)說(shuō),我的期望是 「get 和 delete 不在 T 中的 key 都報(bào)錯(cuò)。」
?當(dāng)然你的真實(shí)項(xiàng)目也可以不認(rèn)同我的觀點(diǎn), 比如 get 一個(gè)不在 T 中定義的 key 也可以,但是我還是推薦你這么做。
?
知道了這個(gè), 再結(jié)合我之前有關(guān)泛型的文章就不難寫出來(lái)。
其中 get 和 delete 的代碼:
export?class?ObjectManipulator?{
??public?getextends?keyof?T>(key:?K):?T[K]?{
????return?this.obj[key];
??}
??public?deleteextends?keyof?T>(key:?K):?ObjectManipulator>?{
????const?newObj?=?{?...this.obj?};
????delete?newObj[key];
????return?new?ObjectManipulator(newObj);
??}
}
最后是 set,其實(shí)一開始我的 set 是這么寫的。
export?class?ObjectManipulator?{
??public?setextends?keyof?T,?V>(key:?K,?value:?V):?ObjectManipulator?{
????return?new?ObjectManipulator({
??????...this.obj,
??????[key]:?value,
????})?as?ObjectManipulatorin?K]:?V?}>;
??}
}
但是無(wú)奈沒有通過官方的測(cè)試用例。實(shí)際項(xiàng)目我其實(shí)更推薦我上面的這種寫法。下面是我為了通過所有的測(cè)試用例寫的方法。
經(jīng)過分析, 我發(fā)現(xiàn)它期望的是 set 中的 key 可以不是 T 中的。這一點(diǎn)從官方給的測(cè)試用例就可以看出來(lái)。

因此我將代碼改成 K 放寬到任意 string,返回值做了一個(gè)聯(lián)合類型。代碼:
export?class?ObjectManipulator?{
??...
??public?setextends?string,?V>(
????key:?K,
????value:?V
??):?ObjectManipulatorin?K]:?V?}>?{
????return?new?ObjectManipulator({
??????...this.obj,
??????[key]:?value,
????})?as?ObjectManipulatorin?K]:?V?}>;
??}
??...
}
終于通過了所有的測(cè)試用例。
代碼
export?class?ObjectManipulator?{
??constructor(protected?obj:?T)?{
????this.obj?=?obj;
??}
??public?setextends?string,?V>(
????key:?K,
????value:?V
??):?ObjectManipulatorin?K]:?V?}>?{
????return?new?ObjectManipulator({
??????...this.obj,
??????[key]:?value,
????})?as?ObjectManipulatorin?K]:?V?}>;
??}
??public?getextends?keyof?T>(key:?K):?T[K]?{
????return?this.obj[key];
??}
??public?deleteextends?keyof?T>(key:?K):?ObjectManipulator>?{
????const?newObj?=?{?...this.obj?};
????delete?newObj[key];
????return?new?ObjectManipulator(newObj);
??}
??public?getObject():?T?{
????return?this.obj;
??}
}
總結(jié)
以上就是給大家?guī)?lái)的題目解析。這六道題的考點(diǎn)有,按照我個(gè)人理解的重要程度劃分為:
type 和 interface 的基本操作(必須掌握) 如何給缺乏類型定義的第三方庫(kù)定義類型(必須掌握) 聯(lián)合類型 和 交叉類型(強(qiáng)烈建議掌握) 類型斷言和類型收縮(強(qiáng)烈建議掌握) 泛型和常見內(nèi)置泛型(強(qiáng)烈建議掌握) 高階函數(shù)的類型定義(強(qiáng)烈建議掌握)
最后祝愿大家告別 anyscript,成為 TypeScript 魔法師。
關(guān)注我
大家也可以關(guān)注我的公眾號(hào)《腦洞前端》獲取更多更新鮮的前端硬核文章,帶你認(rèn)識(shí)你不知道的前端。
點(diǎn)關(guān)注,不迷路!
Reference
深入理解 TypeScript: https://jkchao.github.io/typescript-book-chinese/
[2]TypeScript 官方文檔: https://www.typescriptlang.org/docs/home
[3]typescript-exercises: https://typescript-exercises.github.io/
