Definicja typu w dosłowności obiektu w TypeScript


345

W klasach TypeScript można deklarować typy właściwości, na przykład:

class className {
  property: string;
};

Jak zadeklarować typ właściwości w dosłowności obiektu?

Próbowałem następującego kodu, ale nie można go skompilować:

var obj = {
  property: string;
};

Otrzymuję następujący błąd:

Nazwa „string” nie istnieje w bieżącym zakresie

Czy robię coś źle, czy to błąd?

Odpowiedzi:


423

Jesteś bardzo blisko, wystarczy wymienić =z :. Możesz użyć literału typu obiektu (patrz specyfikacja 3.5.3) lub interfejsu. Używanie literału typu obiektu jest zbliżone do tego, co masz:

var obj: { property: string; } = { property: "foo" };

Ale możesz także użyć interfejsu

interface MyObjLayout {
    property: string;
}

var obj: MyObjLayout = { property: "foo" };

14
Opcja DRY (Don't Repeat Yourself) autorstwa @Rick Love z operatorem rzutowania wydaje się być znacznie ładniejsza. Tylko komentowanie, a nie poniżanie ...
spechter

Wiem, że odpowiedziano na to jakiś czas temu, ale mylę się w założeniach, że problemy mogą powstać, gdy klasa ma zarówno metody, jak i właściwości? Ponieważ klasa nie jest inicjowana, a my przypisujemy tylko właściwości, wywołanie metody w klasie spowoduje zerowy wyjątek. Zasadniczo obiekt, który tworzymy, działa tylko jako klasa, ponieważ przypisujemy jego typ, ale tak naprawdę nie jest instancją tej klasy. Tzn. Musimy stworzyć klasę ze słowem kluczowym „new”. Wracamy do kwadratu 1, ponieważ nie możemy zrobić czegoś takiego jak nowa klasa () {prop: 1}; na przykład w TS jak w C #.
DeuxAlpha

@DeuxAlpha To jest przypisanie do interfejsu , który nie może mieć metod. Nie wierzę, że możesz przydzielić się do takiej klasy.
Mog0

link do specyfikacji byłby miły :)
ahong

@DeuxAlpha tworzy to literał obiektu, a nie obiekt klasy. Interfejs może również mieć metody. Jeśli interfejs definiuje metodę, literał obiektu musi również ją zdefiniować - nie będzie ona pusta. Literał obiektu musi spełniać wszystko, co definiuje interfejs, w przeciwnym razie system typów wyświetli błąd.
Rick Love

291

Aktualizacja 2019-05-15 (ulepszony wzorzec kodu jako alternatywa)

Po wielu latach używania consti korzystania z bardziej funkcjonalnego kodu, w większości przypadków odradzam korzystanie z poniższych. (Podczas budowania obiektów zmuszanie systemu typów do określonego typu zamiast pozwalania mu na wnioskowanie o typach często oznacza, że ​​coś jest nie tak).

Zamiast tego zaleciłbym użycie constzmiennych w jak największym stopniu, a następnie skomponowanie obiektu jako ostatni krok:

const id = GetId();
const hasStarted = true;
...
const hasFinished = false;
...
return {hasStarted, hasFinished, id};
  • Spowoduje to prawidłowe wpisanie wszystkiego bez potrzeby wyraźnego pisania.
  • Nie ma potrzeby ponownego wpisywania nazw pól.
  • To prowadzi do najczystszego kodu z mojego doświadczenia.
  • Dzięki temu kompilator może zapewnić większą weryfikację stanu (na przykład, jeśli powrócisz w wielu lokalizacjach, kompilator zapewni, że zawsze będzie zwracany ten sam typ obiektu - co zachęca cię do zadeklarowania całej wartości zwracanej w każdej pozycji - dając całkowicie jasne zamiar tej wartości).

Dodanie 2020-02-26

Jeśli rzeczywiście potrzebujesz typu, który możesz leniwie zainicjować: Zaznacz, że jest to typ unii zerowej (null lub Type). System typów uniemożliwi korzystanie z niego bez uprzedniego upewnienia się, że ma wartość.

W tsconfig.jsonupewnij się, że włączono ścisłe kontrole zerowe:

"strictNullChecks": true

Następnie użyj tego wzorca i pozwól systemowi typów zabezpieczyć cię przed przypadkowym zerowym / niezdefiniowanym dostępem:



const state = {
    instance: null as null | ApiService,
    // OR
    // instance: undefined as undefined | ApiService,

};

const useApi = () => {
    // If I try to use it here, the type system requires a safe way to access it

    // Simple lazy-initialization 
    const api = state?.instance ?? (state.instance = new ApiService());
    api.fun();

    // Also here are some ways to only access it if it has value:

    // The 'right' way: Typescript 3.7 required
    state.instance?.fun();

    // Or the old way: If you are stuck before Typescript 3.7
    state.instance && state.instance.fun();

    // Or the long winded way because the above just feels weird
    if (state.instance) { state.instance.fun(); }

    // Or the I came from C and can't check for nulls like they are booleans way
    if (state.instance != null) { state.instance.fun(); }

    // Or the I came from C and can't check for nulls like they are booleans 
    // AND I was told to always use triple === in javascript even with null checks way
    if (state.instance !== null && state.instance !== undefined) { state.instance.fun(); }
};

class ApiService {
    fun() {
        // Do something useful here
    }
}

Nie wykonuj poniższych czynności w 99% przypadków:

Aktualizacja 2016-02-10 - Aby obsłużyć TSX (Dzięki @Josh)

Użyj asoperatora dla TSX.

var obj = {
    property: null as string
};

Dłuższy przykład:

var call = {
    hasStarted: null as boolean,
    hasFinished: null as boolean,
    id: null as number,
};

Oryginalna odpowiedź

Użyj operatora rzutowania, aby uczynić to zwięzłym (przez ustawienie wartości null na żądany typ).

var obj = {
    property: <string> null
};

Dłuższy przykład:

var call = {
    hasStarted: <boolean> null,
    hasFinished: <boolean> null,
    id: <number> null,
};

Jest to o wiele lepsze niż posiadanie dwóch części (jedna do deklarowania typów, druga do deklarowania wartości domyślnych):

var callVerbose: {
    hasStarted: boolean;
    hasFinished: boolean;
    id: number;
} = {
    hasStarted: null,
    hasFinished: null,
    id: null,
};

10
Jeśli używasz TSX (TS z JSX), nie możesz używać nazewnictwa nawiasów kątowych, więc te linie stają się czymś w rodzaju, w property: null as stringktórym istotną różnicą jest asoperator.
Josh

2
@RickLove Czy to faktycznie ogranicza typ zmiennej obiektowej, czy jest to tylko sposób na określenie typów po przypisaniu? Innymi słowy, czy po przypisaniu zmiennej calldo drugiego przykładu możesz przypisać jej zupełnie inny typ?
Daniel Griscom

3
To powinna być odpowiedź
Drew R

4
nie działa. Error:(33, 15) TS2352:Type 'null' cannot be converted to type 'string'.
slideshowp2

2
Czy to tylko nadużycie funkcji języka, czy to rzeczywiście Legit? Czy możesz podać link do dalszego czytania oficjalnych dokumentów? Dzięki!
Qwerty

31

Dziwi mnie, że nikt o tym nie wspominał, ale można po prostu stworzyć interfejs o nazwie ObjectLiteral, który akceptuje key: valuepary typu string: any:

interface ObjectLiteral {
  [key: string]: any;
}

Wtedy użyjesz go w następujący sposób:

let data: ObjectLiteral = {
  hello: "world",
  goodbye: 1,
  // ...
};

Dodatkową zaletą jest to, że możesz ponownie używać tego interfejsu tyle razy, ile potrzebujesz, na dowolnej liczbie obiektów.

Powodzenia.


3
To zły pomysł, czyni system typów bezwartościowym. Celem maszynopisu jest umożliwienie systemowi typów zapobieganie błędom, a także zapewnienie lepszej funkcjonalności autouzupełniania w oprzyrządowaniu - w zasadzie wyłącza to wszystkie zalety maszynopisu. Lepiej byłoby nie używać żadnego interfejsu w powyższym przykładzie.
Rick Love

1
@RickLove Zdecydowanie się nie zgadzam. Jest to wyjątkowo przydatne, gdy kilka właściwości jest opcjonalnych, ale nadal chcemy jasno zdefiniować wszystkie typy w sobie (np. Zawierające argumenty dla funkcji). Można to po prostu postrzegać jako cukier syntaktyczny i dosłownie działa jak operator rozprzestrzeniania właściwości interfejsów .
CPHPython

@CPHPython, dlaczego nie określić opcjonalnych parametrów wraz z ich konkretnymi typami? Ma to sens tylko wtedy, gdy nazwy nie są znane w czasie kodowania (tzn. Pochodzą z bazy danych lub źródła zewnętrznego). Również w przypadku złożonych kombinacji argumentów typy związków działają świetnie. Jeśli kodujesz dla określonych nazw, należy je zdefiniować, jeśli to możliwe. Jeśli tylko dane nie wpływają na logikę, to oczywiście pomiń to z systemu typów.
Rick Love,

1
@ RickLove „nazwy nie są znane w czasie kodowania” -> jest to dobry i praktyczny przykład, wartości tych typów kluczy są znane i wszystkie są takie same (np. Ciąg znaków ). Pamiętaj, że zalecałem tylko użycie tej [key: string]części, a nie żadnej części jako definicji typu wartości ... To by rzeczywiście wydobyło użyteczność typów.
CPHPython

1
Jak miałbym postępować, jeśli chcę pozwolić na dodawanie ciągów i liczb, ale nie na inne typy?
DonkeyBanana,

14

Jeśli próbujesz napisać adnotację typu, składnia jest następująca:

var x: { property: string; } = ...;

Jeśli próbujesz napisać literał obiektu, składnia jest następująca:

var x = { property: 'hello' };

Twój kod próbuje użyć nazwy typu w pozycji wartości.


10
Twój var x: { property: string; } = ...;złośliwiec! Miałem nadzieję, że elipsa była poprawną składnią, której należy użyć var x: { property: string; } = { property: 'hello' };.
Jason Kleban

10

W TypeScript, jeśli deklarujemy obiekt, zastosowalibyśmy następującą składnię:

[access modifier] variable name : { /* structure of object */ }

Na przykład:

private Object:{ Key1: string, Key2: number }

7

Jeśli próbujesz dodać pisma do zniszczonego literału obiektu, na przykład w argumentach funkcji, składnia jest następująca:

function foo({ bar, baz }: { bar: boolean, baz: string }) {
  // ...
}

foo({ bar: true, baz: 'lorem ipsum' });

5

Jeśli twoje właściwości mają ten sam typ, możesz użyć predefiniowanego typu narzędzia Record:

type Keys = "property" | "property2"

var obj: Record<Keys, string> = {
  property: "my first prop",
  property2: "my second prop",
};

Możesz oczywiście pójść dalej i zdefiniować niestandardowy typ dla swoich wartości właściwości:

type Keys = "property" | "property2"
type Value = "my prop" | "my other allowed prop"

var obj: Record<Keys, Value> = {
  property: "my prop",
  property2: "my second prop", // TS Error: Type '"my second prop"' is not assignable to type 'Value'.
};

2
Dokładnie to, czego potrzebowałem. Dzięki! Maszynopis do wygranej!
Audacitus

Użyłbym również letzamiast vartutaj.
Fwd079

3
// Use ..

const Per = {
  name: 'HAMZA',
  age: 20,
  coords: {
    tele: '09',
    lan: '190'
  },
  setAge(age: Number): void {
    this.age = age;
  },
  getAge(): Number {
    return age;
  }
};
const { age, name }: { age: Number; name: String } = Per;
const {
  coords: { tele, lan }
}: { coords: { tele: String; lan: String } } = Per;

console.log(Per.getAge());

3
Hej i witamy w SO! Czy możesz w ogóle rozszerzyć swoją odpowiedź, pomocne byłoby wyjaśnienie, w jaki sposób / dlaczego to działa.
party-ring

1

W twoim kodzie:

var obj = {
  myProp: string;
};

W rzeczywistości tworzysz literał obiektu i przypisujesz ciąg zmiennej do właściwości myProp. Chociaż bardzo zła praktyka, tak naprawdę byłby to prawidłowy kod TS (nie używaj tego!):

var string = 'A string';

var obj = {
  property: string
};

Jednak chcesz, aby literał obiektu był wpisany. Można to osiągnąć na różne sposoby:

Berło:

interface myObj {
    property: string;
}

var obj: myObj = { property: "My string" };

Typ niestandardowy:

type myObjType = {
    property: string
};

var obj: myObjType = { property: "My string" };

Operator obsady:

var obj = {
    myString: <string> 'hi',
    myNumber: <number> 132,
};

Literał typu obiektu:

var obj: { property: string; } = { property: "Mystring" };
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.