Як динамічно призначити властивості об’єкту в TypeScript?


359

Якби я хотів програмно призначити властивість об’єкту в Javascript, я зробив би це так:

var obj = {};
obj.prop = "value";

Але в TypeScript це створює помилку:

Властивість 'prop' не існує у значенні типу '{}'

Як я повинен призначити будь-яку нову властивість об’єкту в TypeScript?


1
FYI. Я розпочав дискусію, пов’язану з цим, якщо ви хочете слідувати за цим.
joshuapoehls

Відповіді:


455

Можна позначити objяк any, але це перемагає цілі використання машинопису. obj = {}означає obj, що це Object. Позначати це як anyнемає сенсу. Для досягнення потрібної консистенції інтерфейс можна визначити наступним чином.

interface LooseObject {
    [key: string]: any
}

var obj: LooseObject = {};

АБО зробити компактним:

var obj: {[k: string]: any} = {};

LooseObjectможе приймати поля з будь-яким рядком як ключ та anyтип як значення.

obj.prop = "value";
obj.prop2 = 88;

Справжня елегантність цього рішення полягає в тому, що ви можете включити в інтерфейс поля typesafe.

interface MyType {
    typesafeProp1?: number,
    requiredProp1: string,
    [key: string]: any
}

var obj: MyType ;
obj = { requiredProp1: "foo"}; // valid
obj = {} // error. 'requiredProp1' is missing
obj.typesafeProp1 = "bar" // error. typesafeProp1 should be a number

obj.prop = "value";
obj.prop2 = 88;

Хоча це відповідає на оригінальне запитання, відповідь тут від @GreeneCreations може дати іншу точку зору, як підійти до проблеми.


13
Я думаю, це зараз найкраще рішення. Я думаю, що на той момент було задано такі властивості індексу, як ця, ще не реалізована в TypeScript.
Пітер Ольсон

як щодо рідного об’єкта, як помилка
Wayou

@Wayou рідні об'єкти, такі як помилка та масив, успадковує від Object. Так LooseObjectможе помилка також.
Акаш Куріан Хосе

Чи можете ви перевірити, чи об’єкт має певну (динамічну) властивість?
phhbr

1
для цих сипучих об'єктів, як ви пишете їх у формі геттера-сеттера?
JokingBatman

81

Або все за один раз:

  var obj:any = {}
  obj.prop = 5;

41
Який сенс TypeScript, якщо мені доведеться передати стільки речей any, щоб ним користуватися? Просто стає зайвим шумом у моєму коді ..: /
AjaxLeung

16
@AjaxLeung Якщо вам потрібно це зробити, ви неправильно використовуєте.
Олексій

2
Див. Додаткову редакцію вище, яка зберігає попередній тип об'єкта.
Андре Віанна

6
@AjaxLeung Вам слід дуже рідко ставитись any. TypeScript використовується для лову (потенційних) помилок під час компіляції. Якщо ви anyнамагаєтесь вимкнути помилки, тоді ви втрачаєте силу набору тексту, а також повернетесь до чистого JS. anyв ідеалі слід використовувати лише у тому випадку, коли ви імпортуєте код, для якого ви не можете записати визначення TS або під час переміщення свого коду з JS в TS
Precastic

63

Я схильний ставити anyз іншого боку, тобто var foo:IFoo = <any>{};щось подібне досі є безпечним:

interface IFoo{
    bar:string;
    baz:string;
    boo:string;     
}

// How I tend to intialize 
var foo:IFoo = <any>{};

foo.bar = "asdf";
foo.baz = "boo";
foo.boo = "boo";

// the following is an error, 
// so you haven't lost type safety
foo.bar = 123; 

Ви також можете позначити ці властивості як необов'язкові:

interface IFoo{
    bar?:string;
    baz?:string;
    boo?:string;    
}

// Now your simple initialization works
var foo:IFoo = {};

Спробуйте в Інтернеті


5
+1 за те, що це єдине рішення, яке забезпечує безпеку типу. Просто переконайтеся, що ви використовуєте всі необов'язкові властивості безпосередньо після нього, щоб уникнути помилок, які вас кусають згодом.
Айдіакапі

Це насправді працює? Після компіляції я все ще маю <any> {} у своєму JavaScript.
bvs

4
I still have <any>{то ви ще не склали. TypeScript видалить це в своєму
еміте

4
У ці дні var foo: IFoo = {} as anyє кращим. Старий синтаксис для набору типів стикався з TSX (JSX з ідентифікацією Typescript).
Дон

51

Це рішення корисно, коли ваш об’єкт має специфічний тип. Як і при отриманні об'єкта до іншого джерела.

let user: User = new User();
(user as any).otherProperty = 'hello';
//user did not lose its type here.

7
Це правильне рішення для разових завдань.
soundly_typed

Ця відповідь спрацювала для мене, оскільки для входу в facebook я повинен був додати властивості до об’єкта вікна . Моє перше використання ключового слова як TypeScript.
AlanObject

33

Хоча компілятор скаржиться, він все одно повинен виводити його так, як вам потрібно. Однак це спрацює.

var s = {};
s['prop'] = true;

2
так добре, це не дуже тип сценарію, ви втратили інтелігенцію.
прегматч

25

Ще один варіант зробити це - отримати доступ до ресурсу як колекції:

var obj = {};
obj['prop'] = "value";


2
Це найбільш стислий спосіб. Object.assign(obj, {prop: "value"})від ES6 / ES2015 також працює.
Чарльз

9

Я здивований, що жодна з відповідей не посилається на Object.assign, оскільки це техніка, яку я використовую, коли я думаю про "склад" у JavaScript.

І це працює, як очікувалося в TypeScript:

interface IExisting {
    userName: string
}

interface INewStuff {
    email: string
}

const existingObject: IExisting = {
    userName: "jsmith"
}

const objectWithAllProps: IExisting & INewStuff = Object.assign({}, existingObject, {
    email: "jsmith@someplace.com"
})

console.log(objectWithAllProps.email); // jsmith@someplace.com

Переваги

  • безпека типу на всьому протязі, оскільки вам взагалі не потрібно використовувати цей anyтип
  • використовує агрегатний тип TypeScript (як позначається &при оголошенні типу objectWithAllProps), який чітко повідомляє, що ми створюємо новий тип на ходу (тобто динамічно)

Що слід пам’ятати

  1. Object.assign має свої унікальні аспекти (які добре відомі найбільш досвідченим розробникам JS), які слід враховувати при написанні TypeScript.
    • Він може бути використаний у видозміненому вигляді або незмінний спосіб (я демонструю непорушний спосіб вище, а це означає, що він existingObjectзалишається недоторканим і, отже, не має emailвластивості. Для більшості програмістів у функціональному стилі це добре, оскільки результат є єдина нова зміна).
    • Object.assign найкраще працює, коли у вас плоскіші об’єкти. Якщо ви поєднуєте два вкладені об'єкти, які містять зведені властивості, ви можете перезаписати тритимі значення з невизначеними. Якщо ви стежите за порядком аргументів Object.assign, вам слід добре.

Для мене, у випадках, коли вам потрібно використовувати це для відображення даних, це, здається, працює добре, але коли вам потрібно додати записи модифікованих типів до масиву, це, здається, не надто добре працює. Я вдався до динамічного складання об’єкта і призначення його в одну лінію, а потім присвоєння динамічних властивостей послідовно лініям. Це дістало мені більшу частину шляху там, тож дякую.
Шафік Джета

8

Ви можете створити новий об’єкт на основі старого об'єкта за допомогою оператора спред

interface MyObject {
    prop1: string;
}

const myObj: MyObject = {
    prop1: 'foo',
}

const newObj = {
    ...myObj,
    prop2: 'bar',
}

console.log(newObj.prop2); // 'bar'

TypeScript зробить висновок про всі поля вихідного об'єкта, а VSCode виконає автодоповнення тощо.


приємно, але у випадку, якщо вам потрібно використовувати prop2 в напр. prop3, буде важко реалізувати
ya_dimon


5

Можна додати члена до вже наявного об'єкта за допомогою

  1. розширення типу (читати: розширити / спеціалізувати інтерфейс)
  2. передати оригінальний об'єкт розширеному типу
  3. додати члена до об’єкта
interface IEnhancedPromise<T> extends Promise<T> {
    sayHello(): void;
}

const p = Promise.resolve("Peter");

const enhancedPromise = p as IEnhancedPromise<string>;

enhancedPromise.sayHello = () => enhancedPromise.then(value => console.info("Hello " + value));

// eventually prints "Hello Peter"
enhancedPromise.sayHello();

1
найкраще рішення для мене. Я дізнався щось нове сьогодні, дякую
Моріс

я отримую TS2352 помилку з цим кодом
ya_dimon

@ya_dimon спробуйте на майданчику TypeScript: typecriptlang.org/play Це чудово працює!
Даніель Дітріх

@DanielDietrich зараз працює .. thx, не знаю, що сталося.
ya_dimon

будь ласка! зміна трапляється;)
Даніель Дітріх

5

Оскільки ви не можете цього зробити:

obj.prop = 'value';

Якщо ваш компілятор TS і ваш лінійка вас не суворі, ви можете написати це:

obj['prop'] = 'value';

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

var obj = {};
obj = obj as unknown as { prop: string };
obj.prop = "value";

3
Це якщо "noImplicitAny: false" на tsconfig.json
LEMUEL ADANE

Ще ви можете зробити відповідь GreeneCreations.
LEMUEL ADANE

4

Зберігайте будь-яку нову власність на будь-якому об’єкті, набравши її на "будь-яку":

var extend = <any>myObject;
extend.NewProperty = anotherObject;

Пізніше ви можете отримати його, відкинувши розширений об’єкт на "будь-який":

var extendedObject = <any>myObject;
var anotherObject = <AnotherObjectType>extendedObject.NewProperty;

Це абсолютно правильне рішення. Скажімо, у вас є об'єкт, нехай o: ObjectType; .... пізніше ви можете передавати o будь-якому (<any> o) .newProperty = 'foo'; і його можна отримати як (<any> o) .newProperty. Немає помилок компілятора і працює як шарм.
Метт Ешлі

це гальмує інтеліссенс ... чи є спосіб до цього, окрім як тримати інтелісенс?
прегматч

4

Щоб гарантувати, що тип є Object(тобто пари ключ-значення ), використовуйте:

const obj: {[x: string]: any} = {}
obj.prop = 'cool beans'

4
  • Випадок 1:

    var car = {type: "BMW", model: "i8", color: "white"}; car['owner'] = "ibrahim"; // You can add a property:

  • Випадок 2:

    var car:any = {type: "BMW", model: "i8", color: "white"}; car.owner = "ibrahim"; // You can set a property: use any type




3

Найкращою практикою є використання безпечного набору тексту, я рекомендую вам:

interface customObject extends MyObject {
   newProp: string;
   newProp2: number;
}

3

Щоб зберегти ваш попередній тип, тимчасово передайте об'єкт будь-якому

  var obj = {}
  (<any>obj).prop = 5;

Нова динамічна властивість буде доступна лише тоді, коли ви використовуєте команду:

  var a = obj.prop; ==> Will generate a compiler error
  var b = (<any>obj).prop; ==> Will assign 5 to b with no error;

3

Ось спеціальна версія Object.assign, яка автоматично коригує тип змінної з кожною зміною властивості. Немає необхідності в додаткових змінних, твердженнях типу, явних типів або об'єктних копій:

function assign<T, U>(target: T, source: U): asserts target is T & U {
    Object.assign(target, source)
}

const obj = {};
assign(obj, { prop1: "foo" })
//  const obj now has type { prop1: string; }
obj.prop1 // string
assign(obj, { prop2: 42 })
//  const obj now has type { prop1: string; prop2: number; }
obj.prop2 // number

//  const obj: { prop1: "foo", prop2: 42 }

Примітка. Зразок використовує функції твердження TS 3.7 . Тип повернення assignє void, на відміну від Object.assign.


1

Якщо ви використовуєте Typescript, імовірно, ви хочете використовувати безпеку типу; у такому випадку голий предмет та «будь-який» протипоказані.

Краще не використовувати Object або {}, але якийсь названий тип; або ви можете використовувати API із певними типами, який потрібно розширити за допомогою власних полів. Я виявив, що це працює:

class Given { ... }  // API specified fields; or maybe it's just Object {}

interface PropAble extends Given {
    props?: string;  // you can cast any Given to this and set .props
    // '?' indicates that the field is optional
}
let g:Given = getTheGivenObject();
(g as PropAble).props = "value for my new field";

// to avoid constantly casting: 
let k:PropAble = getTheGivenObject();
k.props = "value for props";

1

динамічно призначити властивості об’єкту в TypeScript.

для цього Вам просто потрібно використовувати інтерфейси машинопису типу:

interface IValue {
    prop1: string;
    prop2: string;
}

interface IType {
    [code: string]: IValue;
}

Ви можете використовувати його так

var obj: IType = {};
obj['code1'] = { 
    prop1: 'prop 1 value', 
    prop2: 'prop 2 value' 
};

1
Я намагаюся з вашим кодом, але я не отримую жодної помилки: pastebin.com/NBvJifzN
pablorsk

1
спробуйте ініціалізувати attributesполе всередині, SomeClass і це має виправити public attributes: IType = {}; pastebin.com/3xnu0TnN
Nerdroid

1

Спробуйте це:

export interface QueryParams {
    page?: number,
    limit?: number,
    name?: string,
    sort?: string,
    direction?: string
}

Потім використовуйте його

const query = {
    name: 'abc'
}
query.page = 1

0

Єдине рішення, яке є повністю безпечним для типів, це це , але воно мало словесне і змушує вас створювати кілька об'єктів.

Якщо спочатку потрібно створити порожній об’єкт, виберіть одне з цих двох рішень. Майте на увазі, що кожного разу, коли ви користуєтеся as, ви втрачаєте безпеку.

Більш безпечне рішення

Тип objectє безпечним всередині getObject, що кошти object.aбудуть мати типstring | undefined

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object: Partial<Example> = {};
  object.a = 'one';
  object.b = 1;
  return object as Example;
}

Коротке рішення

Тип всередині неobject є безпечнимgetObject , а значить, object.aбуде типовим stringще до його призначення.

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object = {} as Example;
  object.a = 'one';
  object.b = 1;
  return object;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.