TypeScript 踩坑筆記:物件字面量的多餘屬性檢查機制
3.6k 字,約 4 分鐘
文章目錄
最近在學 Nest.js,練習定義請求和回應的 DTO 型別。
我在 StackBlitz 上用模擬資料實作了一個 Swagger API,整個流程是:
- Controller 接收請求
- Service 處理 MockData
- 回傳結果
結果碰到多餘參數型別不一致的問題
問題場景
Controller 定義
// 略
@ApiTags('users')
@Controller('users')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@ApiOperation({
summary: '取得全部用戶資訊',
description: '返回系統中所有的用戶資訊,包括用戶名稱、角色和狀態。',
})
@ApiResponse({
status: 200,
description: 'User search successfully.',
type: FilterUsersResponseDto,
})
filterByRole(
@Query() queryParams: FilterUsersRequestDto): FilterUsersResponseDto[] {
return this.appService.filterMockData(queryParams);
}
}
Service 定義
filterMockData(filterDto: FilterUsersRequestDto): FilterUsersResponseDto[] {
const filterUserResult = this.mockData.filter((item) => {
return (
(!filterDto.role || filterDto.role.includes(item.role)) &&
(!filterDto.status || item.status === filterDto.status) &&
(!filterDto.name || item.name.includes(filterDto.name))
);
});
return filterUserResult;
}
MockData 定義
private mockData = [
{
id: 1,
name: 'John Doe',
role: UserRole.Admin,
status: UserStatus.Active,
email: 'john.doe@example.com',
createdAt: new Date('2023-01-01'),
updatedAt: new Date('2023-01-10'),
test: 123, // 亂新增的值
},
// 略
];
乍看之下沒有什麼問題,但當我調整 MockData 新增一個值時,我發現 TS 型別檢查上並沒有報錯。
誒.....怎麼會這樣,這跟原本的型別已經不同了,多了 test 應該要報錯才對呀 ?
Excess Property Checks
當物件以 物件字面值(Object Literal) 的形式直接傳遞時,TypeScript 會執行更嚴格的型別檢查。若物件包含目標型別未定義的屬性,編譯器會立即報錯。
interface IUser {
name: string,
age: number
}
function TestUse(user: IUser){
console.log('user', user)
}
TestUse({ name: 'Antonio', age: 27, phone: 100 })
// Error: Object literal may only specify known properties, and 'phone' does not exist in type 'IUser'.
但如果透過變數指派的方式,TypeScript 就只會檢查有沒有符合 IUser 的屬性,多餘的屬性就不會被檢查了。
interface IUser {
name: string,
age: number
}
function TestUse(user: IUser){
console.log('user', user)
}
const userObj = { name: 'Antonio', age: 27, phone: 100 }
TestUse(userObj)
這種繞過 Excess Property Checks 的寫法,應該只在有明確需求時才使用。在大多數情況下,出現這種情形往往代表著型別定義不夠嚴謹。
避免方式
1. 明確標註型別時會觸發檢查
interface IUser {
name: string,
age: number
}
function TestUse(user: IUser){
console.log('user', user)
}
const userObj: IUser = { name: 'Antonio', age: 27, phone: 100 }
TestUse(userObj)
// Error: Object literal may only specify known properties, and 'phone' does not exist in type 'IUser'.
2. 使用 satisfies 保留物件字面值行為
interface IUser {
name: string,
age: number
}
function TestUse(user: IUser){
console.log('user', user)
}
const userObj = { name: 'Antonio', age: 27, phone: 100 } satisfies IUser;
TestUse(userObj)
// Error: Object literal may only specify known properties, and 'phone' does not exist in type 'IUser'.
允許額外屬性的型別定義
如果業務需求確實需要允許動態新增屬性,可以考慮以下兩種方式:
1. 索引簽名(Index Signature)
interface IUser {
name: string,
age: number,
[key: string]: unknown
}
function TestUse(user: IUser){
console.log('user', user)
}
const userObj: IUser = { name: 'Antonio', age: 27, phone: 100 }
TestUse(userObj)
2. 使用介面繼承擴展原型別定義
interface IUser {
name: string
age: number
}
interface IUserWithPhone extends IUser {
phone: number
}
function TestUse(user: IUser) {
console.log('user', user)
}
const userObj: IUserWithPhone = { name: 'Antonio', age: 27, phone: 100 }
TestUse(userObj)
3. 使用型別交集(Type Intersection)
interface IUser {
name: string
age: number
}
type IUserWithPhone = IUser & {
phone: number
}
function TestUse(user: IUser) {
console.log('user', user)
}
const userObj: IUserWithPhone = { name: 'Antonio', age: 27, phone: 100 }
TestUse(userObj)
回到問題場景的解決方案
回到文章開頭的 Nest.js 範例,只需要在定義 mockData 時明確標註型別,TypeScript 就會對多餘的屬性進行檢查並報錯:
private mockData: FilterUsersResponseDto[] = [
{
id: 1,
name: 'John Doe',
role: UserRole.Admin,
status: UserStatus.Active,
email: 'john.doe@example.com',
createdAt: new Date('2023-01-01'),
updatedAt: new Date('2023-01-10'),
test: 123, // Error: 'test' does not exist in type 'FilterUsersResponseDto'
},
// 略
];
這樣一來,當我們不小心新增了 test 這個多餘屬性時,TypeScript 會立即提示錯誤,避免潛在的型別不一致問題。
補充資料來源
留言
留言使用 GitHub 帳號登入(Giscus),留言會存在 GitHub Discussions。