TypeScript考試題來了,60%的人不會(附答案)


前言

1.相信這段時間來,對 TypeScript 感興趣的小伙伴們已經把這個神器給系統的學習了一遍了吧。如果計划開始學習但是還沒有開始,或者沒有找到資料的同學,可以看下我在之前文章中 前端進階指南 找一下 TypeScript 部分的教程,自行學習。

本文從最近在 Github 上比較火的倉庫 typescript-exercises 入手,它的中文介紹是 「富有挑戰性的 TypeScript 練習集」。里面包含了 15 個 TypeScript 的練習題,我會從其中挑選出幾個比較有價值的題目,一起來解答一下。
2.光理論是不夠的。在此贈送2020最新企業級 Vue3.0/Js/ES6/TS/React/node等實戰視頻教程,想學的可進裙 519293536 免費獲取,小白勿進哦!

目標

來看下這個倉庫的發起者所定下的目標,讓每個人都學會以下知識點的實戰運用:

  1. Basic typing.
  2. Refining types.
  3. Union types.
  4. Merged types.
  5. Generics.
  6. Type declarations.
  7. Module augmentation.
  8. Advanced type mapping.

真的都是一些非常有難度且實用的知識點,掌握了它們一定會讓我們在編寫 TypeScript 類型的時候如虎添翼。

挑戰

exercise-00

題目

import chalk from "chalk" // 這里需要補全 const users: unknown[] = [ { name: "Max Mustermann", age: 25, occupation: "Chimney sweep", }, { name: "Kate Müller", age: 23, occupation: "Astronaut", }, ] // 這里需要補全 function logPerson(user: unknown) { console.log(` - ${chalk.green(user.name)}, ${user.age}`) } console.log(chalk.yellow("Users:")) users.forEach(logPerson) 復制代碼

解答

第一題只是個熱身題,考察對接口類型定義的掌握,直接定義 User 接口即可實現。

interface User { name: string age: number occupation: string } const users: User[] = [ { name: "Max Mustermann", age: 25, occupation: "Chimney sweep", }, { name: "Kate Müller", age: 23, occupation: "Astronaut", }, ] function logPerson(user: User) { console.log(` - ${chalk.green(user.name)}, ${user.age}`) } console.log(chalk.yellow("Users:")) users.forEach(logPerson) 復制代碼

或者利用類型推導,users 數組會自動推斷出類型:

const users = [ { name: "Max Mustermann", age: 25, occupation: "Chimney sweep", }, { name: "Kate Müller", age: 23, occupation: "Astronaut", }, ] 復制代碼

在 VSCode 中,鼠標放到 users 變量上即可看到類型被自動推斷出來了:

{
  name: string age: number occupation: string } ;[] 復制代碼

那么利用 typeof 關鍵字,配合索引查詢,我們也可以輕松取得這個類型。這里 number 的意思就是查找出 users 的所有數字下標對應的值的類型集合。

type User = typeof users[number] 復制代碼

這個倉庫提供了每道題的答題機制,執行 npm run 0 對應題號,看到結果即可證明編譯通過,答案正確。

 

 

execsise-01

題目

最初,我們在數據庫中只有 User 類型,后來引入了 Admin 類型。把這兩個類型組合成 Person 類型以修復錯誤。

interface User { name: string age: number occupation: string } interface Admin { name: string age: number role: string } const persons: User[] /* <- Person[] */ = [ { name: "Max Mustermann", age: 25, occupation: "Chimney sweep", }, { name: "Jane Doe", age: 32, role: "Administrator", }, { name: "Kate Müller", age: 23, occupation: "Astronaut", }, { name: "Bruce Willis", age: 64, role: "World saver", }, ] function logPerson(user: User) { console.log(` - ${chalk.green(user.name)}, ${user.age}`) } persons.forEach(logPerson) 復制代碼

解答

本題考查聯合類型的使用:

// 定義聯合類型 type Person = User | Admin const persons: Person[] /* <- Person[] */ = [ { name: "Max Mustermann", age: 25, occupation: "Chimney sweep", }, { name: "Jane Doe", age: 32, role: "Administrator", }, { name: "Kate Müller", age: 23, occupation: "Astronaut", }, { name: "Bruce Willis", age: 64, role: "World saver", }, ] function logPerson(user: Person) { console.log(` - ${chalk.green(user.name)}, ${user.age}`) } 復制代碼

exercise-02

根據上題中定義出的 Person 類型,現在需要一個方法打印出它的實例:

題目

function logPerson(person: Person) { let additionalInformation: string if (person.role) { // ❌ 報錯 Person 類型中不一定有 role 屬性 additionalInformation = person.role } else { // ❌ 報錯 Person 類型中不一定有 occupation 屬性 additionalInformation = person.occupation } console.log( ` - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}`, ) } 復制代碼

解答

本題考查 TypeScript 中的「類型保護」,TypeScript 的程序流分析使得某些判斷代碼包裹之下的代碼中,類型可以被進一步收縮。

in 操作符:

function logPerson(person: Person) { let additionalInformation: string if ("role" in person) { additionalInformation = person.role } else { additionalInformation = person.occupation } console.log( ` - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}`, ) } 復制代碼

函數返回值中的 is 推斷:

function isAdmin(user: Person): user is Admin { return user.hasOwnProperty("role") } function logPerson(person: Person) { let additionalInformation: string if (isAdmin(person)) { additionalInformation = person.role } else { additionalInformation = person.occupation } console.log( ` - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}`, ) } 復制代碼

exercise-04

題目

本題定義了一個 filterUsers 方法,用來通過 person 中的某些字段來篩選出用戶的子集。

function filterUsers(persons: Person[], criteria: User): User[] { return persons.filter(isUser).filter((user) => { let criteriaKeys = Object.keys(criteria) as (keyof User)[] return criteriaKeys.every((fieldName) => { return user[fieldName] === criteria[fieldName] }) }) } console.log(chalk.yellow("Users of age 23:")) filterUsers( persons, // ❌ 報錯,criteria 定義的是精確的 User 類型,少字段了。 { age: 23, }, ).forEach(logPerson) 復制代碼

可以看出,由於 filterUsers 的第二個篩選參數的類型被精確的定義為 User,所以只傳入它的一部分字段 age 就會報錯。

解答

本題考查 [mapped-types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types) 映射類型,

type Criteria = { [K in keyof User]?: User[K] } function filterUsers(persons: Person[], criteria: Criteria): User[] { return persons.filter(isUser).filter((user) => { let criteriaKeys = Object.keys(criteria) as (keyof User)[] return criteriaKeys.every((fieldName) => { return user[fieldName] === criteria[fieldName] }) }) } 復制代碼

Criteria 利用了映射類型,把 User 的 key 值遍歷了一遍,並且加上了 ? 標志代表字段都是可選的,即可完成任務。

也可以利用內置類型 Partial,這個類型用於把另一個類型的字段全部轉為可選。

function filterUsers(persons: Person[], criteria: Partial<User>): User[] {} 復制代碼

exercise-05

題目

function filterPersons( persons: Person[], personType: string, criteria: unknown, ): unknown[] {} let usersOfAge23: User[] = filterPersons(persons, "user", { age: 23 }) let adminsOfAge23: Admin[] = filterPersons(persons, "admin", { age: 23 }) 復制代碼

解答

本題返回值類型即可以是 User,也可以是Admin,並且很明顯這個結果是由第二個參數是 'user' 還是 'admin' 所決定。

利用函數重載的功能,可以輕松實現,針對每種不同的 personType 參數類型,我們給出不同的返回值類型:

function filterPersons(
  persons: Person[],
  personType: "admin",
  criteria: Partial<Person>,
): Admin[]
function filterPersons(
  persons: Person[],
  personType: "user",
  criteria: Partial<Person>,
): User[]
function filterPersons(
  persons: Person[],
  personType: string,
  criteria: Partial<Person>,
) {}

let usersOfAge23: User[] = filterPersons(persons, "user", { age: 23 })
let adminsOfAge23: Admin[] = filterPersons(persons, "admin", { age: 23 })
復制代碼

exercise-06

題目

function swap(v1, v2) { return [v2, v1] } function test1() { console.log(chalk.yellow("test1:")) const [secondUser, firstAdmin] = swap(admins[0], users[1]) logUser(secondUser) logAdmin(firstAdmin) } function test2() { console.log(chalk.yellow("test2:")) const [secondAdmin, firstUser] = swap(users[0], admins[1]) logAdmin(secondAdmin) logUser(firstUser) } 復制代碼

解答

本題的關鍵點是 swap 這個方法,它即可以接受Admin類型為參數,也可以接受 User 類型為參數,並且還需要根據傳入參數的順序把它們倒過來放在數組中放回。

也就是說傳入的是 v1: User, v2: Admin,需要返回 [Admin, User] 類型。

這題就比較有難度了,首先需要用到泛型 來推斷出參數類型,並且和結果關聯起來:

function swap<T, K>(v1: T, v2: K) { return [v2, v1] } 復制代碼

此時結果沒有按照我們預期的被推斷成 [K, T],而是被推斷成了 (K | T)[],這是不符合要求的。

這是因為 TypeScript 默認我們數組中的元素是可變的,所以它會「悲觀的」推斷我們可能會改變元素的順序。鼠標放到運行函數時的swap上,我們可以看出類型被推斷為了:

function swap<Admin, User>(v1: Admin, v2: User): (Admin | User)[] 復制代碼

要改變這一行為,我們加上 as const 來聲明它為常量,嚴格保證順序。

function swap<T, K>(v1: T, v2: K) { return [v2, v1] as const } 復制代碼

此時再看看 swap的推斷:

function swap<Admin, User>(v1: Admin, v2: User): readonly [User, Admin] 復制代碼

類型被自動加上了 readonly,並且也嚴格的維持了順序,太棒啦。

總結

1.先用其中的幾道題練練手,到第六題的時候,已經涉及到了一些進階的用法,非常有挑戰性。不知道小伙伴們對於這樣的做題形式是否感興趣,還剩下不少有挑戰性的題目,如果反饋不錯的話,我會繼續更新這個系列。
2.光理論是不夠的。在此贈送2020最新企業級 Vue3.0/Js/ES6/TS/React/node等實戰視頻教程,想學的可進裙 519293536 免費獲取,小白勿進哦!

本文的文字及圖片來源於網絡加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM