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' отсутствует в типе '{}'.
Ответы
Ответ 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 или типом, проверенным при выполнении.
Ответ 6
Вы можете использовать тип Partial
сопоставления, как описано в документации: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html.
В вашем примере вы будете иметь:
interface IX {
a: string;
b: any;
c: AnotherType;
}
let x: Partial<IX> = {
a: 'abc'
}