Вот мое решение проблемы.
Давайте определим некоторые базовые c машинописные интерфейсы.
interface TaskType {
title: string;
type: TypeEnum;
metadata: Record<string, any>; // it stores different kind of objects based on the type
}
enum TypeEnum {
CHECKBOX = 'CHECKBOX',
TEXTAREA = 'TEXTAREA',
DATE = 'DATE',
WHATEVER = 'WHATEVER',
}
Давайте определим интерфейс для функции, которая отвечает за обновление задачи на основе некоторых данных формы
// this is what you expect to get from HTML form
export interface TaskFormType {
metadata: Record<string, any>; // you can define it more strictly according to your needs
}
// interface for function arguments
export interface SaveTaskFnArgs {
task: TaskType;
formData: TaskFormType;
}
// interface for the function which should prepare a new version of the task
export interface SaveTaskFn {
(args: SaveTaskFnArgs): TaskType;
}
Теперь мы можем определить выделенную функцию SaveTaskFn для каждого типа Task
const checkboxSave: SaveTaskFn = ({ task, formData }) => {
const updatedTask: TaskType = ... // do what you need to update the task
return updatedTask;
};
const textareaSave: SaveTaskFn = ({ task, formData }) => { ... }
const dateSave: SaveTaskFn = (args) => { ... }
const whateverSave: SaveTaskFn = (args) => { ... }
Теперь это самое интересное, как на самом деле избежать операторов if-else. 1. Определим отображение между типом задачи и функцией 2. Определим функцию publi c saveTaskFn (поэтому мы можем экспортировать только эту функцию на самом деле)
// 1
const SAVE_TASK_MAPPING: Record<TypeEnum, SaveTaskFn> = {
[TypeEnum.CHECKBOX]: checkboxSave,
[TypeEnum.TEXTAREA]: textareaSave,
[TypeEnum.DATE]: dateSave,
[TypeEnum.WHATEVER]: whateverSave,
};
// 2
export const saveTask = (args: SaveTaskFnArgs): TaskType => {
// all if-else statements collapsed in two lines of code (literally);
const fn: SaveTaskFn = SAVE_TASK_MAPPING[args.task.template.type];
return (fn && fn(args)) || args.task; // here instead of returning original task ( || args.task) you can raise exception or return some default object based on your architecture
};
Вот еще одно преимущество использования таких отображение. Если вы расширите значение TypeEnum в Task новым значением, например, «FILE», вы получите ошибку при наборе текста
Error:(<line>, <column>) TS2741:
Property 'FILE' is missing in type '{
[TypeEnum.CHECKBOX]: SaveTaskFn;
[TypeEnum.TEXTAREA]: SaveTaskFn;
[TypeEnum.DATE]: SaveTaskFn;
[TypeEnum.WHATEVER]: SaveTaskFn;
[TypeEnum.FILE]: SaveTaskFn;
}' but required in type 'Record<TypeEnum, SaveTaskFn>'.
..., поэтому у вас есть напоминание о реализации новой функции, например fileSave () и добавьте его в отображение следующим образом
const SAVE_TASK_MAPPING: Record<TypeEnum, SaveTaskFn> = {
...
[TypeEnum.FILE]: fileSave,
};