TypeScript: необязательные свойства в интерфейсах

TypeScript logoИнтересной возможностью языка TypeScript является декларация интерфейсов с необязательными свойствами.

Инициализация переменной в TypeScript

Предположим в коде на TypeScript определен интерфейс ICirce. Тогда можно объявить переменную такого типа и инициализировать ее, создав простой объект с такими же свойствами. Например:

interface ICircle {
    Radius: number;
    Color: string;
}

var e1: ICircle = {
    Radius: 1,
    Color: "#ff0000"
};

При присвоении TypeScript сопоставляет типы объектов по наличию одинаковых свойств. Отсутствие даже одного их них приведет к ошибке компиляции:

var e2: ICircle = { Radius: 1 };

Необязательные свойства

В TypeScript существует возможность объявлять необязательные свойства, которые могут отсутствовать при инициализации. Они отмечаются символом "?", например:

interface ICircle {
    Radius: number;
    Color?: string;
}

Теперь тоже самый код

var e2: ICircle = { Radius: 1 };

будет компилироваться, а неопределенные свойства (в данном случае это только Color) получат значения undefined.

Необходимо учесть, что если необязательное свойство имеет тип класса или интерфейса, то необязательность инициализации не распространятся автоматически на его свойства. Т.е. свойствео может быть или не инициализировано совсем или инициализировано объектом, который создан по всем правилам TypeScrtipt. Рассмотрим на примере:

interface ICircle {
    Radius: number;
    Color?: string;
    Coords?: { X: number; Y: number; Z?: number;}
}

var e3: ICircle = {
    Radius: 1,
    Coords: { X: 5 } // Ошибка: нет свойства Y
}

var e3: ICircle = {
    Radius: 1,
    Coords: { X: 5, Y: 42 } // OK: свойство Z опционально
}

Примеры использования

Объекты DTO (наборы параметров, описание конфигурации и т.д.)

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

interface ICircleConfig {
    Coords: { X: number; Y: number; }
    RadiusX?: number;
    RadiusY?: number;
    Color?: string;
}

interface IDrawElement { }
class Point implements IDrawElement { }
class Elipse implements IDrawElement { }
class Circle implements IDrawElement { }

function CreateCircle(config: ICircleConfig): IDrawElement {
    var defaultRadiusX = 1;
    var defaultColor = "#000";
    
    var color = config.Color ? config.Color : defaultColor;
    var radiusX = config.RadiusX ? config.RadiusX : defaultRadiusX;
    var radiusY = config.RadiusY ? config.RadiusY : config.RadiusX;
        
    return (radiusX != radiusY) ? new Elipse(config.Coords, radiusX, radiusY, color)
        (radiusX === 1) ? new Point(config.Coords, color) ?
            new Circle(config.Coords, radiusX, color);        
}

CreateCircle({ Coords: { X: 10, Y: 20 } });
CreateCircle({ XRadius: 10, Coords: { X: 10, Y: 20 } });
CreateCircle({ XRadius: 10, YRadius: 20, Coords: { X: 10, Y: 20 } });

В результате, для создания точки (Point) достаточно указать только координаты, а для круга – только координаты и радиус по оси X.

Взаимодействие с JavaScript: схожие объекты

Предположим некая функция из сторонней библиотеки возвращает объект, который почти аналогичен существующему интерфейсу, но без части свойств. Менять ее код локально не желательно, т.к. это приведет к усложнению поддержки и проблемам при обновлении версии. Но можно объявить свойства необязательными.

Например, функция рассчитывает окружность и возвращает аналог ICircleConfig из примера выше, но всегда без к��да цвета (Color). Но это свойство необязательно и результат вызова можно переменной типа ICircleConfig. Если необходимо, то после этого можно задать цвет, отличный от цвета по умолчанию.

Взаимодействие с JavaScript: непостоянный тип результата

Иногда встречаются функции, которые возвращают объект с разными полями в зависимости от результата вызова. В таком случае можно объявить обобщающий интерфейс с необязательными полями.

Например, вызов getContent() возвращает или { сode: x,  content: "…" } для удачного вызова или { сode: x,  errMgs: "…" } при возникновении ошибки. Как легко догадаться, обобщающий интерфейс будет следующим:

interface IGetContentResult {
    code: number;
    content?: string;
    errMgs?: string;
}

Добавить комментарий