ฉันจะกำหนดคุณสมบัติให้กับวัตถุใน TypeScript แบบไดนามิกได้อย่างไร


359

หากฉันต้องการกำหนดคุณสมบัติให้กับวัตถุใน Javascript โดยทางโปรแกรมฉันจะทำเช่นนี้:

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

แต่ใน TypeScript สิ่งนี้จะสร้างข้อผิดพลาด:

คุณสมบัติ 'prop' ไม่มีอยู่ในค่าประเภท '{}'

ฉันควรจะกำหนดคุณสมบัติใหม่ให้กับวัตถุใน TypeScript อย่างไร


1
FYI ฉันได้เปิดการสนทนาที่เกี่ยวข้องกับเรื่องนี้หากคุณต้องการติดตาม
joshuapoehls

คำตอบ:


455

มันเป็นไปได้ที่จะแสดงobjว่าเป็นanyแต่เอาชนะจุดประสงค์ทั้งหมดของการใช้ typescript 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
Peter Olson

วิธีการเกี่ยวกับวัตถุพื้นเมืองเช่นข้อผิดพลาด
Wayou

@Wayou วัตถุพื้นเมืองเช่น Error และ Array สืบทอดมาจาก Object ดังนั้นLooseObjectสามารถรับข้อผิดพลาดได้เช่นกัน
Akash Kurian Jose

คุณสามารถตรวจสอบว่าวัตถุนั้นมีคุณสมบัติ (ไดนามิก) หรือไม่?
phhbr

1
สำหรับวัตถุที่หลวมเหล่านี้คุณจะเขียนมันในรูปแบบของ getter-setter ได้อย่างไร?
JokingBatman

81

หรือทั้งหมดในครั้งเดียว:

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

41
TypeScript มีจุดประสงค์อะไรถ้าฉันต้องใช้หลาย ๆ อย่างanyในการใช้มัน กลายเป็นเสียงรบกวนพิเศษในรหัสของฉัน .. : /
AjaxLeung

16
@ AjaxLeung หากคุณต้องการทำเช่นนั้นคุณใช้ผิด
อเล็กซ์

2
ดูการแก้ไขเพิ่มเติมด้านบนที่รักษาวัตถุประเภทก่อนหน้า
Andre Vianna

6
@AjaxLeung anyคุณมากไม่ค่อยควรโยนไป TypeScript จะใช้ในการจับข้อผิดพลาด (ที่อาจเกิดขึ้น) ในเวลารวบรวม หากคุณส่งไปanyที่ข้อผิดพลาดในการปิดเสียงคุณจะสูญเสียพลังในการพิมพ์และอาจกลับไปที่ JS บริสุทธิ์ anyควรใช้อย่างสมบูรณ์แบบหากคุณกำลังนำเข้ารหัสที่คุณไม่สามารถเขียนคำจำกัดความของ TS หรือในขณะที่โยกย้ายรหัสของคุณจาก JS ไปยัง TS
Prec

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 สำหรับการเป็นทางออกเดียวที่รักษาความปลอดภัยของประเภท เพียงตรวจสอบให้แน่ใจว่าคุณได้ติดตั้งคุณสมบัติที่ไม่ใช่ตัวเลือกทั้งหมดโดยตรงหลังจากนั้นเพื่อหลีกเลี่ยงข้อบกพร่องที่จะทำให้คุณรำคาญในภายหลัง
Aidiakapi

ใช้งานได้จริงหรือ หลังจากรวบรวมฉันยังมี <any> {} อยู่ใน javascript ของฉัน
bvs

4
I still have <any>{จากนั้นคุณไม่ได้รวบรวม TypeScript จะลบมันออกมา
basarat

4
วันนี้var foo: IFoo = {} as anyเป็นที่ต้องการ ไวยากรณ์เก่าสำหรับ typecasting กำลังชนกับ TSX (Typescript-ified JSX)
Don

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
ใช่แล้วนี่ไม่ใช่วิธีพิมพ์สคริปต์จริง ๆ คุณหลวม intellisense
pregmatch

25

อีกทางเลือกหนึ่งคือการเข้าถึงคุณสมบัติเป็นชุดรวม:

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


2
นี่เป็นวิธีที่รัดกุมที่สุด Object.assign(obj, {prop: "value"})จาก ES6 / ES2015 ก็ใช้งานได้เช่นกัน
Charles

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 devs ที่มีประสบการณ์มากที่สุด) ซึ่งควรพิจารณาเมื่อเขียน TypeScript
    • มันสามารถใช้ในแบบที่ไม่แน่นอนหรือลักษณะที่ไม่เปลี่ยนรูป (ฉันแสดงให้เห็นถึงวิธีที่ไม่เปลี่ยนรูปด้านบนซึ่งหมายความว่าexistingObjectยังคงไม่มีใครแตะต้องและดังนั้นจึงไม่มีemailคุณสมบัติสำหรับโปรแกรมเมอร์สไตล์การทำงานส่วนใหญ่นั่นเป็นสิ่งที่ดีเนื่องจากผลลัพธ์คือ การเปลี่ยนแปลงใหม่เท่านั้น)
    • Object.assign ทำงานได้ดีที่สุดเมื่อคุณมีวัตถุที่แบนราบ หากคุณกำลังรวมวัตถุซ้อนกันสองตัวที่มีคุณสมบัติที่ไม่สามารถใช้งานได้คุณสามารถจบลงด้วยการเขียนทับค่าความจริงด้วยไม่ได้กำหนด หากคุณระวังลำดับของการโต้แย้ง Object.assign คุณควรจะปรับ

สำหรับฉันในกรณีที่คุณจำเป็นต้องใช้สิ่งนี้เพื่อแสดงข้อมูลดูเหมือนว่าจะทำงานได้ดี แต่เมื่อคุณต้องการเพิ่มรายการประเภทที่แก้ไขไปยังอาร์เรย์สิ่งนี้ดูเหมือนจะไม่ได้ผลดีนัก ฉันหันไปเขียนวัตถุแบบไดนามิกและกำหนดมันในหนึ่งบรรทัดจากนั้นกำหนดคุณสมบัติแบบไดนามิกในบรรทัดต่อเนื่อง สิ่งนี้ทำให้ฉันได้รับประโยชน์มากที่สุดจากที่นั่นดังนั้นขอบคุณ
Shafiq Jetha

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 Playground: typescriptlang.org/playมันใช้ได้ดี!
Daniel Dietrich

@DanielDietrich ใช้งานได้แล้ว .. ขอบคุณไม่รู้ว่าเกิดอะไรขึ้น
ya_dimon

ยินดี! การเปลี่ยนแปลงเกิดขึ้น;)
Daniel Dietrich

5

เนื่องจากคุณไม่สามารถทำสิ่งนี้:

obj.prop = 'value';

หากผู้แปล TS และ linter ของคุณไม่เข้มงวดกับคุณคุณสามารถเขียนสิ่งนี้:

obj['prop'] = 'value';

หากคอมไพเลอร์ TS หรือ linter ของคุณเข้มงวดคำตอบอื่นก็คือ typecast:

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;

หลังจากนั้นคุณสามารถดึงมันกลับมาได้โดยการส่งวัตถุขยายของคุณกลับไปที่ 'any':

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

นี่คือทางออกที่ถูกต้องทั้งหมด ให้บอกว่าคุณมีวัตถุให้ o: ObjectType; .... ในภายหลังคุณสามารถส่ง o ไปที่ใดก็ได้ (<any> o) .newProperty = 'foo'; และสามารถเรียกคืนได้เช่น (<any> o). newProperty ไม่มีข้อผิดพลาดของคอมไพเลอร์และใช้งานได้อย่างมีเสน่ห์
Matt Ashley

เบรกนี้เป็นระบบ Intellisense ... มีวิธีใดในการทำเช่นนี้ แต่จะรักษาระบบ Intellisense หรือไม่?
pregmatch

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เป็นซึ่งแตกต่างจากvoidObject.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 แบบไดนามิก

ในการทำเช่นนั้นคุณต้องใช้อินเตอร์เฟสของ 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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.