I have a type that is:
export enum ApiFunctions {
"setHidden" = "HIDE",
"setReadOnly" = "SET_READ_ONLY",
"setDescription" = "DESCRIPTION"
}
export type ValueOfApiFunction = `${ApiFunctions}`
and logic that is listening for an event on the window like so
window.addEventListener("message", (event) => {
if(event.data.type === "COMPLETE") {
event.data.changes.forEach((change: { fieldId: string, action: ValueOfApiFunction }) => {
const field = api.getFieldById(change.fieldId);
//I want to make sure that within this forEach all values of the type above are covered and if not, through a compilation error
// in this case "DESCRIPTION" isn't included so I would want a compilation error being thrown
if(change.action === "HIDE") {
field?.setVisible(false);
}
else if(change.action === "SET_READ_ONLY") {
field?.setName("This field is now read only")
}
})
resolve();
}
if(event.data.type === "MAKE_REQUEST") {
invoke('makeRequest', { url: event.data.url }).then(result => {
console.log(result);
iframe.contentWindow?.postMessage({
type: "REQUEST_COMPLETE",
response: result
}, "*")
});
}
})
I have explained what I want to achieve in a comment in the code block above but basically I want to ensure all values of my type are covered within the forEach logic, if not, compilation error.
Many thanks
CodePudding user response:
TypeScript does not have a native capability to enforce the use of certain logic within a codebase. It is mostly concerned that objects are not manipulated incorrectly, rather than inspecting what the correct code is doing. Nevertheless, there is a workaround that would enable you to make sure that logic is executed for every possibility of an enum:
You can create a JSON object that holds all enum values as its keys, and each key has a value of a function, meaning that there is a piece of logic executed for every enum as follows:
const fieldToAction: { [key in ValueOfApiFunction]: (field?: Field) => void } = {
"HIDE": (field?: Field) => field?.setVisible(false),
"SET_READ_ONLY": (field?: Field) => field?.setName("This field is now read only"),
"DESCRIPTION": (field?: Field) => field?.setName("This field is now read only"),
}
(The Field
type could be something different as it was not provided in the example)
All you need to do now is to call this within your forEach
as follows:
fieldToAction[change.action](field);
Playground link that illustrates this behaviour can be found here.
CodePudding user response:
You can take advantage of the 'never' type like this :
event.data.changes.forEach((change: { fieldId: string, action: ValueOfApiFunction }) => {
const field = api.getFieldById(change.fieldId);
if(change.action === "HIDE") {
field?.setVisible(false);
}
else if(change.action === "SET_READ_ONLY") {
field?.setName("This field is now read only")
} else {
const _shouldNeverBeReached: never = change.action
}
})
Now if you forget to handle a change.action
, the type will not be never and therefore it will not compile.
UPDATE : for a more explicite message:
const exaustiveCheck = <T extends any>(t: T extends never ? never : "One of the cases was not handled") => {
throw new Error("One of the cases was not handled")
}
and then
} else {
exaustiveCheck(change.action)
}