Union Array's type-guard utils
問題
backend 回傳值,會根據機器當前模式有不同狀態
例如
錯誤模式 → error: [’HARDWARE_ERROR’, ‘xxxxx’],真的遇到一些硬體上使用的錯誤或未知錯誤
特殊模式 → error: [’USER_OPERATION’, ‘xxxxx’],並非使用錯誤,而是需要使用者做的操作
主要麻煩的處理是這兩種狀態都是塞在 key 為 error 的值,難以拆開
前端也沒辦法預知什麼時候會出現哪一種,且又必須根據不同種類的回傳值,invoke 不同的 modal component
先不討論為什麼 後端不區分出來的情況下,前端 Typescript 要怎麼處理這個狀況
解決思路
我希望在收到值的時候,若 index[0] 為 HARDWARE_ERROR,則整個陣列都需要符合 HARDWARE_ERROR 裡有可能出現的 value,這邊也必須跟後端確認會有哪幾種錯誤類型。
同樣地,若 index[0] 為 USER_OPERATION,就是 USER_OPERATION 可能會帶的值類型。
這麼說可能有點難理解,直接看下方的例子步驟比較好懂
解決步驟
1. 將兩種 type 類型定義成 object
// 定義「錯誤模式」可能會出現的屬性與值,基本上屬性、值相同,與後端回傳值一模一樣
export const WorkErrorTypes = {
DOOR_OPENED: 'DOOR_OPENED', // 機器上蓋未關
BOTTOM_OPENED: 'BOTTOM_OPENED', // 機器下蓋未關
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
HARDWARE_ERROR: 'HARDWARE_ERROR',
} as const;
/**
* 這個 as const 的類型推斷蠻好用的,它會確保屬性的值只能完全符合定義的字面值,而非 string
* 例如 DOOR_OPENED 的值只能是:'DOOR_OPENED',而不能是任意的字串內容
* 此外,也賦予其 readonly 的特性,防止被外界改寫
**/
// 取出錯誤模式所有可能性作為判別的 type
export type workErrorType = typeof WorkErrorTypes[keyof typeof WorkErrorTypes];
// 定義「特殊模式」可能會出現的屬性與值
export const userOperationTypes = {
CHANGE_MODULE: 'CHANGE_MODULE', // 更換機器模組
CORRECT_MODULE: 'CORRECT_MODULE', // 正確的模組
USER_OPERATION: 'USER_OPERATION',
} as const;
// 取出特殊模式所有可能性作為判別的 type
export type userOperationType = (typeof userOperationTypes)[keyof typeof userOperationTypes];
2. 創建可以判定當前 error array 是否符合某個模式的 type-guard utils
// 錯誤模式
function isWorkErrorType(value: any): value is workErrorType {
return Object.values(WorkErrorTypes).includes(value);
}
export function isWorkErrorArray(array: any[]): array is workErrorType[] {
return array.every(isWorkErrorType);
}
/**
* value is workErrorType 以及 array is workErrorType[],實際上不影響 function 作用的回傳值,畢竟它也只是 TS 的功能
* 但這個類型宣告可以協助 TS 判斷該 function 的回傳值是什麼
* 像第一個函式 isWorkErrorType 回傳的布林值,理應上,結果必須等於傳進的參數是否為 workErrorType (步驟一定義過的)
* 我覺得作用比較算是提供雙重保障
**/
// 特殊模式
function isUserOperationType(value: any): value is userOperationType {
return Object.values(userOperationTypes).includes(value);
}
export function isUserOperationArray(array: any[]): array is userOperationType[] {
return array.every(isUserOperationType);
}
3. 在需要根據 value 來決定要使用哪個 modal 的邏輯裡,使用我們寫好的 type-guard 做條件判斷
if (isUserOperationArray(errorArr)) {
showOperationModal({ error: errorArr, ... });
} else if (isWorkErrorArray(errorArr)) {
showWorkErrModal(errorArr, ...);
}
這樣就避免 TS 報錯:當為 workErrorType 時,不能符合 userOperationType
實際上確實沒辦法符合,因為兩者在後端的回應來說不會並行
錯誤分享
原本想直接看能不能用 Union 解決這個問題
type CombinedErrorType = workErrorType | userOperationType;
type error = CombinedErrorType[];
const [workError, setWorkError] = useState<CombinedErrorType[] | null>(null);
if(errorArr[0] === 'USER_OPERATION'){
showOperationModal({ error: errorArr, ... }); // 這裡的參數 error 會報錯
}
/**
TS error:
Type 'CombinedErrorType[]' is not assignable to type 'userOperationType[]'.
Type 'CombinedErrorType' is not assignable to type 'userOperationType'.
Type '"DOOR_OPENED"' is not assignable to type 'userOperationType'.
因為 showOperationModal 的 error type 為 userOperationType
不想把 error type 設為 CombinedErrorType
畢竟 showOperationModal 確實不該吃到 workErrorType 的值
因而無法用 Union 簡單定義這些
**/