Domyślne wartości interfejsu maszynopisu


137

Mam następujący interfejs w TypeScript:

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

Deklaruję zmienną tego typu i inicjalizuję wszystkie właściwości

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

Następnie przypisuję im wartości rzeczywiste w funkcji init później

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

Ale nie podoba mi się konieczność określania zestawu domyślnych wartości null dla każdej właściwości podczas deklarowania obiektu, gdy zostaną one później ustawione na wartości rzeczywiste. Czy mogę powiedzieć interfejsowi, aby domyślnie właściwości, których nie dostarczam, miały wartość null? Co pozwoliłoby mi to zrobić:

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

bez uzyskania błędu kompilatora. Teraz mi to mówi

TS2322: Typu „{}” nie można przypisać do typu „IX”. Brak właściwości „b” w typie „{}”.



3
IMO, odpowiedź stackoverflow.com/a/35074490/129196 nie powinna być podejściem, które należy przyjąć. Jeśli możesz mieć obiekt w stanie bez zainicjowania wszystkich jego właściwości i nadal być poprawny, powinieneś zadeklarować te właściwości jako opcjonalne, jak wskazuje ta odpowiedź: stackoverflow.com/a/43226857/129196 . W przeciwnym razie stracimy jedyny cel używania maszynopisu (dla bezpieczeństwa typów).
— Charles Prakash Dasari

Odpowiedzi:


99

Czy mogę powiedzieć interfejsowi, aby domyślnie właściwości, których nie dostarczam, miały wartość null? Co by mi na to pozwoliło

Nie. Ale domyślnie są, undefinedco jest w większości w porządku. Możesz użyć następującego wzorca, czyli mieć potwierdzenie typu w miejscu tworzenia:

let x: IX = {} as any;

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

Mam ten i inne wzorce udokumentowane tutaj: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Dziękuję, bez zdefiniowania zestawu wartości domyślnych nie mogłem ustawić wartości dla niezdefiniowanej właściwości
— Omar Isaid

43
Używanie anypodważa cel skryptu typu . Istnieją inne odpowiedzi bez tej wady.
— Jack Miller

czy ktoś mógłby mi pomóc w podobnej kwestii, ale przy użyciu leków generycznych. Tutaj, na to pytanie
— TAB,

2
Dziwne, że basarat pasowałby do przykładu „dowolny”, gdy w podanym odsyłaczu oferuje znacznie lepszą opcję z „let foo = {} jako Foo;” („Foo” jest interfejsem)
— Neurothustra

1
OP zadał sobie trud stworzenia interfejsu TypeScript i prosi o rozwiązania dotyczące poszycia kotła. Twoim rozwiązaniem jest całkowita rezygnacja z interfejsów? Równie dobrze może zasugerować, że pomija też TypeScript ...
— Moss Palmer

99

Nie możesz ustawić wartości domyślnych w interfejsie, ale możesz osiągnąć to, co chcesz zrobić, używając właściwości opcjonalnych (porównaj paragraf nr 3):

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

Po prostu zmień interfejs na:

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

Możesz wtedy:

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

I użyj swojej funkcji init, aby przypisać domyślne wartości x.bi x.cjeśli te właściwości nie są ustawione.


3
W pytaniu poproszono o zainicjowanie x.bi za x.cpomocą null. Pisząc let x = {a: 'abc'}wtedy x.bi x.csą undefined, więc ta odpowiedź nie w pełni spełniają wymagania, mimo że to inteligentny szybko naprawić.
— Benny Neugebauer

1
@BennyNeugebauer Zaakceptowana odpowiedź ma tę samą wadę. To najlepsza odpowiedź
— tel.

1
jeśli x jest obiektem z wartościami domyślnymi, to utworzenie obiektu ostatniej instancji let a: IX = Object.assign({b:true}, x);spowoduje, że b, c będzie opcjonalne również w obiekcie instancji, co może nie być pożądane
— Luckylooke

Właściwości nie powinny być oznaczane jako „opcjonalne” dla wygody osób korzystających z interfejsu. Odpowiedź brzmi „nie”, nie możesz „nakazać interfejsowi podania wartości domyślnych”, ale możesz podać fabryczną metodę inicjowania instancji interfejsu.
— Rick O'Shea

36

Podczas gdy odpowiedź @ Timar działa doskonale w nullprzypadku wartości domyślnych (o co pytano), oto kolejne proste rozwiązanie, które dopuszcza inne wartości domyślne: Zdefiniuj interfejs opcji oraz odpowiednią stałą zawierającą wartości domyślne; w konstruktorze użyj operatora spread, aby ustawić optionszmienną składową

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

Teraz możesz użyć klasy w następujący sposób:

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

Wynik:

set
null
1

Nie można tego używać jako domyślnego rozwiązania w każdym przypadku, ale zdecydowanie jest to kreatywne rozwiązanie w niektórych przypadkach. 👍
— abhay tripathi

co, jeśli zamiast po prostu rejestrować wartości, chcę ich użyć jak const calcvalue = this.options.a * 1000; to wciąż budzi niepokój, ponieważ może być potencjalnie nieokreślone ...
— Manish Rawat

Tak, możesz skorzystać z zajęć lub napisać je w innym języku, z których żaden nie odpowiada na jego pytanie.
— Rick O'Shea

22

Możesz zaimplementować interfejs za pomocą klasy, następnie możesz zająć się inicjalizacją elementów członkowskich w konstruktorze:

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

Innym podejściem jest użycie funkcji fabrycznej:

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

Następnie możesz po prostu:

var ix: IX = null;
...

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

4

Możesz użyć Partialmapowanego typu, jak wyjaśniono w dokumentacji: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

W swoim przykładzie będziesz mieć:

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

let x: Partial<IX> = {
    a: 'abc'
}

jeśli x jest obiektem z wartościami domyślnymi, to utworzenie obiektu ostatniej instancji let a: IX = Object.assign({b:true}, x);spowoduje błądPartial<IX> cannot be assigned to IX
— Luckylooke

Częściowa zmiana typu. xjuż nie implementuje IX, ale jest częścią IX. Partialjest dobre w miejscach, w których każda właściwość może być opcjonalna, na przykład w przypadku ORM, gdzie można przekazać część interfejsu obiektu i zaktualizować tylko zdefiniowane pola (w przeciwieństwie do undefinedtego, czym Partialmoże być każde pole a ). W przypadku interfejsów, które mają pola z wartościami domyślnymi, można zadeklarować literały obiektowe implementujące tego typu interfejsy, bez konieczności deklarowania wartości domyślnych, używając let x: Partial<IX> = { /* non-default fields */ } as IXskładni.
— Paul-Sebastian Manole

1

Natknąłem się na to, szukając lepszego sposobu niż to, do czego dotarłem. Po przeczytaniu odpowiedzi i wypróbowaniu ich pomyślałem, że warto opublikować, co robię, ponieważ inne odpowiedzi nie wydawały mi się tak zwięzłe. Zależało mi na tym, aby za każdym razem, gdy konfigurowałem nowy interfejs, napisać niewielką ilość kodu. Zdecydowałem się na ...

Korzystanie z niestandardowej ogólnej funkcji deepCopy:

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

Zdefiniuj swój interfejs

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

... i zdefiniuj wartości domyślne w oddzielnej stałej.

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

Następnie uruchom w ten sposób:

let x : IX = deepCopy(XDef);

To wszystko, czego potrzeba ...

.. jednak ..

Jeśli chcesz niestandardowo zainicjować dowolny element główny , możesz zmodyfikować funkcję deepCopy, aby akceptowała niestandardowe wartości domyślne. Funkcja staje się:

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

Który można wtedy nazwać w ten sposób:

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

Każdy inny preferowany sposób głębokiego kopiowania zadziała. Gdyby potrzebna była tylko płytka kopia, wystarczyłby Object. assign, który zrezygnowałby z narzędzia deepCopylub deepCopyAssignfunkcji.

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

Znane problemy

  • Nie będzie to głęboko przypisywane w tym przebraniu, ale nie jest zbyt trudne do zmodyfikowania, deepCopyAssignaby iterować i sprawdzać typy przed przypisaniem.
  • Funkcje i odwołania zostaną utracone w procesie analizy / określania parametrów. Nie potrzebuję ich do mojego zadania, podobnie jak OP.
  • Niestandardowe wartości inicjalizacji nie są podpowiadane przez środowisko IDE ani typ sprawdzany podczas wykonywania.

0

Moje rozwiązanie:

Utworzyłem opakowanie na Object. assign, aby naprawić problemy z pisaniem.

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

Stosowanie:

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;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.