Сколько раз у вас был ужасный опыт в качестве фронтенд-разработчика, когда вам приходилось создавать ресурсы, которые будут отправляться в бэкэнд (объект JSON), и это чрезвычайно сложно, поднимите руку

Что ж, один из моих подходов к ресурсам, поступающим из серверной части, - это их определение в одном месте. Достаточно простой пример - пользователь, с которым связана Компания.

interface Company {
    id: string;
    name: string;
    numberEmployees: number;
}

interface User {
    id: string;
    firstName: string;
    lastName: string;
    company: Company;
}

Что ж, поскольку это форма пользователя, я мог бы повторно использовать этот интерфейс, чтобы определить, как будет выглядеть новый пользователь. Теперь вы можете спросить: Бруно, у нового пользователя еще не определен идентификатор. Здесь на помощь приходят служебные типы Typescript.

Какие бывают типы утилит

Типы утилит - это не что иное, как способ преобразования одного типа в другой. Я могу выбрать тип T и преобразовать его в тип Y. Typescript предоставляет несколько типов справки, но сегодня мы рассмотрим только некоторые из них.

Частично ‹T›

Частично, как следует из названия, принимает тип T и превращает все его ключи в необязательные поля.

interface Todo {
    title: string;
    description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
    return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
    title: 'organize desk',
    description: 'clear clutter',
};

const todo2 = updateTodo(todo1, {
    description: 'throw out trash',
});

Только чтение ‹T›

Принимает тип T и превращает все его ключи в атрибуты, доступные только для чтения. Это чрезвычайно полезно, когда нам нужно иметь исходную копию объекта, который не будет изменен, а затем мы можем сравнить исходный объект с измененным.

interface Todo {
    title: string;
}

const todo: Readonly<Todo> = {
    title: 'Delete inactive users',
};

todo.title = 'Hello'; // Error: cannot reassign a readonly property

Pick<T,K>

Создает тип, выбирая набор свойств K из T

interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>;

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
};

Пропустить ‹T, K›

Создает тип, выбирая все свойства из T и удаляя K

interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Omit<Todo, 'description'>;

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
};

Используя эти служебные типы, мы можем создавать новые объекты на основе определения типа наших внутренних ресурсов. Если когда-либо атрибут серверной части изменится с строки на строку | null нужно просто поменять в одном месте.

Использование типов утилит в наших примерах

Теперь, когда у нас есть определения типов для наших внутренних ресурсов, мы можем создавать новые определения типов для создания ресурса.

interface Company {
    id: string;
    name: string;
    numberEmployees: number;
}

interface User {
    id: string;
    firstName: string;
    lastName: string;
    company: Company;
}

type ICompany = Omit<Company, 'id'>;
// If you are not familiar with the & operator in Typescript (intersection operator)
// you can read it about here: https://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types
type IUser = Omit<User, 'id' | 'company'> & { company: ICompany };

let newUser: IUser = {
    firstName: '',
    lastName: '',
    company: {
        name: '',
        numberEmployees: 0,
    }
}

Итак, мы идем. Теперь мы можем создать определение нового типа для пользователя без необходимости копировать / вставлять определения Компания и Пользователь.

Бонус

Это чрезвычайно полезный подход, когда дело доходит до создания фабрик для ваших ресурсов, которые необходимо заполнить во внешнем интерфейсе. Давайте создадим фабрику для пользователя вместо того, чтобы каждый раз вручную определять ее

interface Company {
    id: string;
    name: string;
    numberEmployees: number;
}

interface User {
    id: string;
    firstName: string;
    lastName: string;
    company: Company;
}

type ICompany = Omit<Company, 'id'>;
type IUser = Omit<User, 'id' | 'company'> & { company: ICompany };

class CompanyFactory implements ICompany {
    name: string = '';
    
    numberEmployees: number = 0;
}

class UserFactory implements IUser {
    company: ICompany = new CompanyFactory();

    firstName: string = '';

    lastName: string = '';
}

let newUser: IUser = new UserFactory();

Еще один чрезвычайно полезный подход для таких типов утилит, как этот, - разбить огромные формы на шаги. Таким образом, мы можем легко использовать оператор Pick для выбора свойств объекта, принадлежащего этому шагу:

interface Company {
    id: string;
    name: string;
    numberEmployees: number;
}

interface User {
    id: string;
    firstName: string;
    lastName: string;
    company: Company;
}

type ICompany = Omit<Company, 'id'>;
type IUser = Omit<User, 'id' | 'company'> & { company: ICompany };

class CompanyFactory implements ICompany {
    name: string = '';

    numberEmployees: number = 0;
}

class UserFactory implements IUser {
    company: ICompany = new CompanyFactory();

    firstName: string = '';

    lastName: string = '';
}

let newUser: IUser = new UserFactory();

let newUserStep1: Pick<IUser, 'firstName' | 'lastName'> = {
    firstName: '',
    lastName: '',
}

let newUserStep2: ICompany = {
    name: '',
    numberEmployees: 0
}

Вывод

Типы утилит - это нечто чрезвычайно мощное, чего другие языки не могут себе позволить. Мы можем легко создавать новые определения типов без необходимости копировать / вставлять из «родительского» определения.

Я надеюсь, что эта статья была полезной и дала вам некоторое представление о том, как лучше печатать ваши объекты в машинописном тексте. Пожалуйста, если у вас есть комментарии, улучшения или конструктивная критика, оставьте комментарий. Я всегда открыт для изучения новых подходов, шаблонов проектирования, которые я ломаю, и грубых ошибок, которые совершаю.

Ресурсы:

Типы утилит машинописного текста

Тип пересечения машинописного текста