Определение типа в литерале объекта в TypeScript


346

В классах TypeScript можно объявлять типы для свойств, например:

class className {
  property: string;
};

Как объявить тип свойства в литерале объекта?

Я пробовал следующий код, но он не компилируется:

var obj = {
  property: string;
};

Я получаю следующую ошибку:

Имя 'строка' не существует в текущей области

Я делаю что-то не так или это ошибка?

Ответы:


424

Вы довольно близко, вам просто нужно заменить =с :. Вы можете использовать литерал типа объекта (см. Раздел 3.5.3 спецификации) или интерфейс. Использование литерала типа объекта близко к тому, что у вас есть:

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

Но вы также можете использовать интерфейс

interface MyObjLayout {
    property: string;
}

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

14
Опция «СУХОЙ (не повторяйся») @Rick Love с оператором приведения выглядит намного лучше. Просто комментируя, а не понижая голос ...
spechter

Я знаю, что на этот вопрос уже был дан ответ, но я ошибаюсь в предположениях, что проблемы могут возникнуть, когда у класса есть методы и свойства? Так как класс не инициализируется, и мы только присваиваем свойства, вызов метода класса вызовет нулевое исключение. По сути, объект, который мы создаем, только «действует» как класс, потому что мы назначаем его тип, но на самом деле он не является экземпляром этого класса. Т.е. нам нужно создать класс с ключевым словом 'new'. И затем мы возвращаемся к квадрату 1, так как мы не можем сделать что-то вроде new class () {prop: 1}; в TS, как мы можем в C #, например.
DeuxAlpha

@DeuxAlpha Это назначение интерфейса , у которого не может быть методов. Я не верю, что вы можете назначить такой класс.
Mog0

ссылка на спецификацию была бы хороша :)
ахонг

@DeuxAlpha - это создание литерала объекта, а не объекта класса. Также интерфейс может иметь методы. Если ваш интерфейс определяет метод, то литерал объекта также должен его определять - он не будет нулевым. Литерал объекта должен выполнять все, что определяет интерфейс, иначе система типов выдаст ошибку.
Рик Лав

291

Обновление 2019-05-15 (улучшенный шаблон кода в качестве альтернативы)

После многих лет использования constи использования более функционального кода я рекомендовал бы в большинстве случаев не использовать приведенное ниже. (При построении объектов принудительное использование системы типов в конкретном типе вместо того, чтобы позволять выводить типы, часто является признаком того, что что-то не так).

Вместо этого я бы рекомендовал использовать constпеременные в максимально возможной степени, а затем составить объект как последний шаг:

const id = GetId();
const hasStarted = true;
...
const hasFinished = false;
...
return {hasStarted, hasFinished, id};
  • Это будет правильно печатать все без необходимости явной печати.
  • Нет необходимости перепечатывать имена полей.
  • Это приводит к чистому коду из моего опыта.
  • Это позволяет компилятору обеспечить больше проверки состояния (например, если вы вернетесь в нескольких местах, компилятор будет гарантировать, что всегда возвращается один и тот же тип объекта - что побуждает вас объявлять все возвращаемое значение в каждой позиции - давая совершенно ясную информацию). намерение этой ценности).

Дополнение 2020-02-26

Если вам действительно нужен тип, который можно лениво инициализировать: отметьте, что это объединяемый тип, допускающий значение NULL (нуль или тип). Система типов не позволит вам использовать ее, не убедившись, что она имеет значение.

В tsconfig.json, убедитесь, что вы включаете строгие проверки нуля:

"strictNullChecks": true

Затем используйте этот шаблон и позвольте системе типов защитить вас от случайного нулевого / неопределенного доступа:



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

Не делайте ниже в 99% случаев:

Обновление 2016-02-10 - для обработки TSX (спасибо @ Джош)

Используйте asоператор для TSX.

var obj = {
    property: null as string
};

Более длинный пример:

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

Оригинальный ответ

Используйте оператор приведения, чтобы сделать это кратким (приведя значение null к нужному типу).

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

Более длинный пример:

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

Это намного лучше, чем две части (одна для объявления типов, вторая для объявления значений по умолчанию):

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

10
Если вы используете TSX (TS с JSX), вы не можете использовать именование угловых скобок, поэтому эти линии становятся чем-то вроде того, property: null as stringгде важным отличием является asоператор.
Джош

2
@RickLove Ограничивает ли это тип переменной объекта или это только способ указать типы при назначении? Другими словами, после назначения переменной callво втором примере, можете ли вы назначить ей совершенно другой тип?
Даниэль Гриском

3
Это должен быть ответ
Дрю Р

4
не работает. Error:(33, 15) TS2352:Type 'null' cannot be converted to type 'string'.
slideshowp2

2
Это просто злоупотребление языком или легит? Не могли бы вы предоставить ссылку для прочтения официальных документов? Спасибо!
Qwerty

31

Я удивлен, что никто не упомянул об этом, но вы могли бы просто создать интерфейс ObjectLiteral, который принимает key: valueпары типа string: any:

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

Тогда вы бы использовали его, вот так:

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

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

Удачи.


3
Это плохая идея, она делает систему типов бесполезной. Смысл машинописи в том, чтобы позволить системе типов помогать предотвращать ошибки, а также обеспечивать улучшенную функциональность автозаполнения в инструментах - это в основном отключает все преимущества Typescript. Было бы лучше не использовать какой-либо интерфейс в приведенном выше примере.
Рик Лав

1
@RickLove Я категорически не согласен. Это было исключительно полезно, когда несколько свойств являются необязательными, но мы все же хотим четко определить все типы внутри (например, содержащие аргументы для функции). Это можно просто рассматривать как синтаксический сахар и буквально работает как оператор распространения для свойств интерфейсов .
CPHPython

@CPHPython, почему бы просто не указать дополнительные параметры с их конкретными типами? Единственный раз, когда это имеет смысл, - это если имена не известны во время кода (т. Е. Они поступают из базы данных или внешнего источника). Также для сложных комбинаций аргументов типы объединения отлично работают. Если вы кодируете против определенных имен, они должны быть определены, если это возможно. Если это просто данные, которые не влияют на логику, то, конечно, не включайте их в систему типов.
Рик Лав

1
@RickLove "имена не известны во время кода" -> это хороший и практичный пример, значения этих типов ключей известны и они все одинаковы (например, строка ). Имейте в виду, что я выступал только за использование [key: string]части, а не какой-либо части в качестве определения типа значения ... Это действительно исключило бы полезность типов.
CPHPython

1
Как мне поступить, если я хочу разрешить добавление строки и чисел, но не других типов?
DonkeyBanana

14

Если вы пытаетесь написать аннотацию типа, синтаксис:

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

Если вы пытаетесь написать литерал объекта, синтаксис:

var x = { property: 'hello' };

Ваш код пытается использовать имя типа в позиции значения.


10
Вы var x: { property: string; } = ...;дразните! Я надеялся, что многоточие было правильным синтаксисом, который будет сокращённым var x: { property: string; } = { property: 'hello' };.
Джейсон Клебан

10

В TypeScript, если мы объявляем объект, мы бы использовали следующий синтаксис:

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

Например:

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

7

Если вы пытаетесь добавить типизацию к литералу деструктурированного объекта, например, в аргументах функции, синтаксис:

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

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

5

Если ваши свойства имеют одинаковый тип, вы можете использовать предопределенный тип утилиты Record:

type Keys = "property" | "property2"

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

Конечно, вы можете пойти дальше и определить пользовательский тип для значений вашего свойства:

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
Именно то, что мне было нужно. Спасибо! Машинопись за победу!
Audacitus

Я бы тоже использовал letвместо varздесь.
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
Привет и добро пожаловать на ТАК! Можете ли вы вообще расширить свой ответ, было бы полезно объяснить, как / почему это работает.
вечеринка

1

В вашем коде:

var obj = {
  myProp: string;
};

Вы фактически создаете литерал объекта и присваиваете строку переменной свойству myProp. Хотя это очень плохая практика, на самом деле это будет действительный код TS (не используйте это!):

var string = 'A string';

var obj = {
  property: string
};

Однако вы хотите, чтобы литерал объекта был напечатан. Это может быть достигнуто различными способами:

Интерфейс:

interface myObj {
    property: string;
}

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

Пользовательский тип:

type myObjType = {
    property: string
};

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

Оператор приведения:

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

Тип объекта литерал:

var obj: { property: string; } = { property: "Mystring" };
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.