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


137

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

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

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

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

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

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

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

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

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

TS2322: Тип '{}' не может быть назначен типу 'IX'. Свойство 'b' отсутствует в типе '{}'.


Я добавил для вас документы: basarat.gitbooks.io/typescript/content/docs/tips/…
— basarat

3
ИМО, ответ stackoverflow.com/a/35074490/129196 не должен быть подходом. Если вы можете иметь объект в состоянии без инициализации всех его свойств и по-прежнему оставаться действительным, вы должны объявить эти свойства как необязательные, как указано в этом ответе: stackoverflow.com/a/43226857/129196 . В противном случае мы потеряем единственную цель использования машинописного текста (для обеспечения безопасности типов).
— Чарльз Пракаш Дасари

Ответы:


100

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

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

let x: IX = {} as any;

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

У меня есть этот и другие шаблоны, задокументированные здесь: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Спасибо, без определения набора значений по умолчанию, которые раньше мне не
— удавалось

43
Использование anyподрывает цель Type Script. Есть и другие ответы без этого недостатка.
— Джек Миллер

может ли кто-нибудь помочь мне по аналогичному вопросу, но с использованием дженериков. Вот в этом вопросе
— TAB

2
Странно, что басарат пошел бы с примером 'any', когда в предоставленной ссылке он предлагает гораздо лучший вариант с 'let foo = {} as Foo;' («Фу» - интерфейс)
— Нейротустра

1
OP взялся за создание интерфейса TypeScript и просит решения для обшивки котла. Ваше решение - полностью отказаться от интерфейсов?
— Мосс Палмер

100

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

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

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

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

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

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

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


3
В вопросе предлагалось инициализировать x.bи x.cс null. При написании let x = {a: 'abc'}тогда x.bи x.cесть undefined, поэтому этот ответ не полностью соответствует требованиям, хотя это умное быстрое решение.
— Бенни Нойгебауэр

1
@BennyNeugebauer Принятый ответ имеет тот же недостаток. Это лучший ответ
— тел.

1
если x - объект со значениями по умолчанию, то создание последнего объекта-экземпляра let a: IX = Object.assign({b:true}, x);приведет к тому, что b, c будут необязательными также в объекте-экземпляре, что может быть нежелательно
— Luckylooke

Свойства не должны быть помечены как «необязательные» для удобства пользователей интерфейса. Ответ - «нет», вы не можете «указать интерфейсу предоставить значения по умолчанию», но вы можете предоставить фабричный метод для инициализации экземпляра интерфейса.
— Рик О'Ши

36

Хотя ответ @Timar отлично работает для 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

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

что, если вместо того, чтобы просто регистрировать значения, я хочу использовать их, например, const calcvalue = this.options.a * 1000; это все равно вызовет тревогу, поскольку потенциально может быть неопределенным ..
— Маниш Рават

Да, вы можете использовать класс или написать его на другом языке, ни один из которых не отвечает на его вопрос.
— Рик О'Ши

22

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

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];
        }
    }
}

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

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

Вы можете использовать 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'
}

если x - объект со значениями по умолчанию, то создание последнего экземпляра объекта let a: IX = Object.assign({b:true}, x);вызовет ошибкуPartial<IX> cannot be assigned to IX
— Luckylooke

Частично меняет тип. xбольше не орудия IX, но часть IX. Partialхорошо подходит для мест, где каждое свойство может быть необязательным, например, с ORM, где вы можете передать часть интерфейса объекта и обновить только те поля, которые определены (в отличие от undefinedтого, чем Partialможет быть каждое поле a ). Для интерфейсов, которые имеют поля со значениями по умолчанию, вы можете объявить объектные литералы, реализующие этот тип интерфейсов, без необходимости объявлять значения по умолчанию, используя let x: Partial<IX> = { /* non-default fields */ } as IXсинтаксис.
— Пол-Себастьян Маноле

1

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

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

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

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

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

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

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

Затем выполните инициализацию так:

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для итерации и проверки типов перед назначением.
  • Функции и ссылки будут потеряны в процессе синтаксического анализа / преобразования в строку. Мне они не нужны для моей задачи, и OP тоже.
  • Пользовательские значения инициализации не подсказываются IDE и не проверяются при выполнении.

0

Мое решение:

Я создал оболочку над Object.assign, чтобы исправить проблемы с набором текста.

export function assign<T>(...args: T[] | Partial<T>[]): T {
  return Object.assign.apply(Object, [{}, ...args]);
}

Использование:

env.base.ts

export interface EnvironmentValues {
export interface EnvironmentValues {
  isBrowser: boolean;
  apiURL: string;
}

export const enviromentBaseValues: Partial<EnvironmentValues> = {
  isBrowser: typeof window !== 'undefined',
};

export default enviromentBaseValues;

env.dev.ts

import { EnvironmentValues, enviromentBaseValues } from './env.base';
import { assign } from '../utilities';

export const enviromentDevValues: EnvironmentValues = assign<EnvironmentValues>(
  {
    apiURL: '/api',
  },
  enviromentBaseValues
);

export default enviromentDevValues;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.