前言 1.相信這段時間來,對 TypeScript 感興趣的小伙伴們已經把這個神器給系統的學習了一遍了吧。如果計劃開始學習但是還沒有開始,或者沒有找到資料的同學,可以看下我在之前文章中 前端進階指南 找一下 TypeScript 部分的教程,自行學習。 本文從最近在 Github 上比較火的倉庫 t ...
前言
1.相信這段時間來,對 TypeScript 感興趣的小伙伴們已經把這個神器給系統的學習了一遍了吧。如果計劃開始學習但是還沒有開始,或者沒有找到資料的同學,可以看下我在之前文章中 前端進階指南 找一下 TypeScript 部分的教程,自行學習。
本文從最近在 Github 上比較火的倉庫 typescript-exercises 入手,它的中文介紹是 「富有挑戰性的 TypeScript 練習集」。裡面包含了 15 個 TypeScript 的練習題,我會從其中挑選出幾個比較有價值的題目,一起來解答一下。
2.光理論是不夠的。在此贈送2020最新企業級 Vue3.0/Js/ES6/TS/React/node等實戰視頻教程,想學的可進裙 519293536 免費獲取,小白勿進哦!
目標
來看下這個倉庫的發起者所定下的目標,讓每個人都學會以下知識點的實戰運用:
- Basic typing.
- Refining types.
- Union types.
- Merged types.
- Generics.
- Type declarations.
- Module augmentation.
- 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 免費獲取,小白勿進哦!
本文的文字及圖片來源於網路加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理