Значення за замовчуванням інтерфейсу шрифту


137

У 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()

Але мені не подобається вказувати купу нульових значень за замовчуванням для кожного властивості при оголошенні об'єкта, коли вони просто будуть встановлені пізніше до реальних значень. Чи можу я сказати інтерфейсу за замовчуванням властивості, які я не надаю до нуля? Що дозволило б мені це зробити:

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 . Інакше ми втратимо єдину мету використання typecript (для безпеки типу).
— Чарльз Пракаш Дасарі

Відповіді:


98

Чи можу я сказати інтерфейсу за замовчуванням властивості, які я не надаю до нуля? Що дозволило б мені це зробити

Ні. Але за замовчуванням вони є, undefinedякі в основному просто чудові. Ви можете використовувати таку схему, тобто мати твердження типу на місці створення:

let x: IX = {} as any;

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

У мене це та інші зразки задокументовано тут: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Дякую, не визначаючи купу значень за замовчуванням, я раніше не міг встановити значення для невизначеного властивості
— Омар Ісаїд

42
Використання anyпідриває призначення Type Script. Є й інші відповіді без цього недоліку.
— Джек Міллер

хто-небудь міг мені допомогти щодо подібного питання, але використовуючи дженерики. Ось у цьому питанні
— TAB

2
Як не дивно, що базасар піде з прикладом "будь-який", коли в наданому посиланні він пропонує набагато кращий варіант з "нехай foo = {} як Foo;" ("Foo" є інтерфейсом)
— Neurothustra

1
ОП пішов на проблему зі створенням інтерфейсу TypeScript і просить вирішити питання щодо покриття котла. Ваше рішення - повністю відмовитись від інтерфейсів? Можна також припустити, що пропускає TypeScript теж ...
— Мосс Палмер

98

Ви не можете встановити значення за замовчуванням в інтерфейсі, але ви можете виконати те, що хочете зробити, скориставшись додатковими властивостями (порівняйте пункт №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
У питанні було запропоновано ініціалізувати 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

Властивості не повинні позначатися "необов'язковими" для зручності тих, хто використовує інтерфейс. Відповідь "ні", ви не можете "сказати інтерфейсу, щоб подати за замовчуванням", але ви можете надати заводський метод ініціалізації екземпляра інтерфейсу.
— Rick O'Shea

34

Хоча відповідь @ 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

Це не можна використовувати як рішення за замовчуванням для кожного випадку, але, безумовно, творче рішення для деяких випадків. 👍
— abhay tripathi

що робити, якщо замість того, щоб просто записувати значення, я хочу використовувати їх, як const calcvalue = this.options.a * 1000; це все одно підніме тривогу, оскільки це може бути невизначено ..
— Manish Rawat

Так, ви можете використовувати клас або написати його іншою мовою, жодна з яких не відповідає на його запитання.
— Rick O'Shea

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може бути кожним полем ). Для інтерфейсів, у яких є поля зі значеннями за замовчуванням, ви можете оголосити об'єктні літерали, що реалізують цей тип інтерфейсів, не оголошуючи значень за замовчуванням, використовуючи 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.

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ітерацію та перевірку типів перед призначенням.
  • Функції та посилання будуть втрачені процесом синтаксичного аналізу / строфікації. Мені це не потрібно для мого завдання, а також ОП.
  • Користувацькі значення init не натякають 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.