Подтвердить что ты не робот

Typescript значения по умолчанию для интерфейса

У меня есть следующий интерфейс в TypeScript:

interface IX {
    a: string,
    b: any,
    c: AnotherType
}

Объявляю переменную этого типа, и я инициализирую все свойства

let x: IX = {
    a: 'abc',
    b: null,
    c: null
}

Затем я присваиваю им реальные значения в функции init позже

x.a = 'xyz'
x.b = 123
x.c = new AnotherType()

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

let x: IX = {
    a: 'abc'
}

без получения ошибки компилятора. Сейчас он говорит мне

TS2322: Тип '{}' не присваивается типу 'IX'. Свойство 'b' отсутствует в типе '{}'.

4b9b3361

Ответ 1

Можно ли указать интерфейсу по умолчанию свойства, которые я не поставляю в null? Что позволило бы мне сделать это

Нет. Но по умолчанию они undefined, что в основном просто отлично. Вы можете использовать следующий шаблон, то есть иметь тип утверждения в точке создания:

let x: IX = {} as any;

x.a = 'xyz'
x.b = 123
x.c = new AnotherType()

У меня есть это и другие шаблоны, описанные здесь: https://basarat.gitbooks.io/typescript/content/docs/tips/lazyObjectLiteralInitialization.html

Ответ 2

Вы не можете установить значения по умолчанию в интерфейсе, но вы можете выполнить то, что вы хотите сделать, используя дополнительные свойства (сравните абзац № 3):

https://www.typescriptlang.org/docs/handbook/interfaces.html

Просто измените интерфейс на:

interface IX {
    a: string,
    b?: any,
    c?: AnotherType
}

Затем вы можете:

let x: IX = {
    a: 'abc'
}

И используйте функцию init для назначения значений по умолчанию x.b и x.c, если эти свойства не установлены.

Ответ 3

Вы можете реализовать интерфейс с классом, тогда вы можете справиться с инициализацией членов в конструкторе:

class IXClass implements IX {
    a: string;
    b: any;
    c: AnotherType;

    constructor(obj: IX);
    constructor(a: string, b: any, c: AnotherType);
    constructor() {
        if (arguments.length == 1) {
            this.a = arguments[0].a;
            this.b = arguments[0].b;
            this.c = arguments[0].c;
        } else {
            this.a = arguments[0];
            this.b = arguments[1];
            this.c = arguments[2];
        }
    }
}

Другой подход заключается в использовании функции factory:

function ixFactory(a: string, b: any, c: AnotherType): IX {
    return {
        a: a,
        b: b,
        c: c
    }
}

Тогда вы можете просто:

var ix: IX = null;
...

ix = new IXClass(...);
// or
ix = ixFactory(...);

Ответ 4

Хотя @Timar answer отлично работает для null значений по умолчанию (что было запрошено), здесь есть другое простое решение, которое допускает другие значения по умолчанию: определение интерфейса параметров, а также соответствующей константы, содержащей значения по умолчанию; в конструкторе используйте оператор распространения, чтобы установить переменную-член options

interface IXOptions {
    a?: string,
    b?: any,
    c?: number
}

const XDefaults: IXOptions = {
    a: "default",
    b: null,
    c: 1
}

export class ClassX {
    private options: IXOptions;

    constructor(XOptions: IXOptions) {
        this.options = { ...XDefaults, ...XOptions };
    }

    public printOptions(): void {
        console.log(this.options.a);
        console.log(this.options.b);
        console.log(this.options.c);
    }
}

Теперь вы можете использовать класс следующим образом:

const x = new ClassX({ a: "set" });
x.printOptions();

Выход:

set
null
1

Ответ 5

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

Использование пользовательской универсальной функции deepCopy:

deepCopy = <T extends {}>(input: any): T => {
  return JSON.parse(JSON.stringify(input));
};

Определите ваш интерфейс

interface IX {
    a: string;
    b: any;
    c: AnotherType;
}

... и определить значения по умолчанию в отдельном const.

const XDef : IX = {
    a: '',
    b: null,
    c: null,
};

Тогда init так:

let x : IX = deepCopy(XDef);

Это все, что нужно..

.. тем не мение..

Если вы хотите выполнить пользовательскую инициализацию любого корневого элемента, вы можете изменить функцию deepCopy для принятия пользовательских значений по умолчанию. Функция становится:

deepCopyAssign = <T extends {}>(input: any, rootOverwrites?: any): T => {
  return JSON.parse(JSON.stringify({ ...input, ...rootOverwrites }));
};

Который затем можно назвать так:

let x : IX = deepCopyAssign(XDef, { a:'customInitValue' } );

Любой другой предпочтительный способ глубокого копирования будет работать. Если требуется только поверхностная копия, тогда будет достаточно Object.assign, исключая необходимость использования утилиты deepCopy или deepCopyAssign.

let x : IX = object.assign({}, XDef, { a:'customInitValue' });

Известные вопросы

  • Он не будет глубоко присваивать в этом виде, но не так уж сложно изменить deepCopyAssign для итерации и проверки типов перед присваиванием.
  • Функции и ссылки будут потеряны в процессе анализа /stringify. Я не нуждаюсь в них для своей задачи, как и ОП.
  • Пользовательские значения инициализации не намекаются IDE или типом, проверенным при выполнении.