ฉันจะป้องกันข้อผิดพลาดได้อย่างไร "ลายเซ็นดัชนีของชนิดวัตถุโดยนัยมีประเภท" ใด ๆ "เมื่อรวบรวม typescript ที่มีการเปิดใช้งานธง noImplicitAny?


309

ฉันมักจะรวบรวม typescript กับธง - noImplicitAny สิ่งนี้สมเหตุสมผลแล้วที่ฉันต้องการให้การตรวจสอบประเภทของฉันแน่นที่สุด

ปัญหาของฉันคือด้วยรหัสต่อไปนี้ฉันได้รับข้อผิดพลาดIndex signature of object type implicitly has an 'any' type:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

สิ่งสำคัญที่ควรทราบคือแนวคิดคือตัวแปรสำคัญมาจากที่อื่นในแอปพลิเคชันและอาจเป็นคีย์ใด ๆ ในวัตถุ

ฉันได้ลองใช้การคัดเลือกนักแสดงโดย:

let secondValue: string = <string>someObject[key];

หรือสถานการณ์ของฉันเป็นไปไม่ได้ด้วย--noImplicitAny?

คำตอบ:


337

การเพิ่มลายเซ็นดัชนีจะทำให้ TypeScript ทราบประเภทที่ควร

ในกรณีของคุณที่จะเป็น [key: string]: string;

interface ISomeObject {
    firstKey:      string;
    secondKey:     string;
    thirdKey:      string;
    [key: string]: string;
}

อย่างไรก็ตามสิ่งนี้ยังบังคับใช้ชนิดคุณสมบัติทั้งหมดเพื่อให้ตรงกับลายเซ็นดัชนี เนื่องจากคุณสมบัติทั้งหมดเป็นstringไปได้

ในขณะที่ลายเซ็นดัชนีเป็นวิธีที่มีประสิทธิภาพในการอธิบายอาร์เรย์และรูปแบบ 'พจนานุกรม' พวกเขายังบังคับให้คุณสมบัติทั้งหมดตรงกับประเภทที่ส่งคืน

แก้ไข:

หากประเภทไม่ตรงกันคุณสามารถใช้ประเภทร่วม [key: string]: string|IOtherObject;

ด้วยประเภทยูเนี่ยนจะดีกว่าถ้าคุณให้ TypeScript อนุมานประเภทแทนการกำหนด

// Type of `secondValue` is `string|IOtherObject`
let secondValue = someObject[key];
// Type of `foo` is `string`
let foo = secondValue + '';

ถึงแม้ว่ามันอาจดูยุ่งเหยิงไปบ้างหากคุณมีหลาย ๆ ชนิดในลายเซ็นของดัชนี ทางเลือกที่จะใช้anyในลายเซ็น [key: string]: any;จากนั้นคุณจะต้องใช้นักแสดงประเภทที่คุณทำข้างต้น


และถ้าอินเทอร์เฟซของคุณดูเหมือนอินเตอร์เฟส ISomeObject {firstKey: string; secondKey: IOtherObject; } ฉันเดาไม่ได้เหรอ?
Jasper Schulte

ขอบคุณ! การรวมกันของประเภทใด ๆ พร้อมกับการหล่อประเภทต่อกรณีดูเหมือนจะเป็นวิธีการขาย
Jasper Schulte

สวัสดีวิธีจัดการกับ "anyObject [key: Object] ['name']"
Code_Crash

หรือพูดอะไรบางอย่างเช่น _obj = {}; ให้ _dbKey = _props [key] ['name']; _obj [_dbKey] = นี่ [สำคัญ]; _props ที่นี่คือวัตถุและวัตถุ [คีย์] จะส่งคืนวัตถุซึ่งจะมีคุณสมบัติชื่อ
Code_Crash

180

อีกวิธีในการหลีกเลี่ยงข้อผิดพลาดก็คือใช้ตัวละครแบบนี้:

let secondValue: string = (<any>someObject)[key]; (หมายเหตุวงเล็บ)

anyปัญหาเดียวก็คือว่าเรื่องนี้ไม่ได้พิมพ์ปลอดภัยอีกต่อไปขณะที่คุณกำลังจะหล่อ แต่คุณสามารถย้อนกลับไปยังประเภทที่ถูกต้องได้เสมอ

ป.ล. : ฉันใช้ typescript 1.7 ไม่แน่ใจเกี่ยวกับรุ่นก่อนหน้า


20
เพื่อหลีกเลี่ยงคำเตือน tslint คุณอาจใช้:let secondValue: string = (someObject as any)[key];
briosheje

96

TypeScript 2.1เปิดตัววิธีการที่สวยงามในการจัดการปัญหานี้

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

เราสามารถเข้าถึงชื่อคุณสมบัติของวัตถุทั้งหมดในระหว่างขั้นตอนการคอมไพล์ด้วยkeyofคำสำคัญ (ดูการเปลี่ยนแปลง )

คุณจะต้องเปลี่ยนชนิดตัวแปรที่มีstring keyof ISomeObjectตอนนี้คอมไพเลอร์รู้ตัวแปรที่ได้รับอนุญาตให้มีชื่อคุณสมบัติเฉพาะจากkeyISomeObject

ตัวอย่างเต็มรูปแบบ:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   number;
}

const someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   3
};

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

// You can mix types in interface, keyof will know which types you refer to.
const keyNumber: (keyof ISomeObject) = 'thirdKey';
const numberValue: number = someObject[keyNumber];

โค้ดสดบนtypescriptlang.org (ชุดnoImplicitAnyตัวเลือก)

อ่านเพิ่มเติมที่มีมากขึ้นในkeyofการใช้งาน


6
แต่มันจะไม่ทำงานถ้าเราประกาศkeyเป็นconst key = (keyof ISomeObject)= 'สอง' + 'คีย์'
ผิดหวัง

55

การตั้งค่า tsconfigต่อไปนี้จะช่วยให้คุณสามารถละเว้นข้อผิดพลาดเหล่านี้ - ตั้งค่าเป็นจริง

suppressImplicitAnyIndexErrors

ไม่แสดงข้อผิดพลาด noImplicitAny ใด ๆ สำหรับการทำดัชนีวัตถุที่ไม่มีลายเซ็นดัชนี


14
นั่นคือสิ่งที่คุณไม่ควรทำ - อาจมีบางคนในทีมของคุณได้กำหนดตัวเลือกคอมไพเลอร์นี้อย่างชัดเจนเพื่อให้รหัสมีคุณสมบัติกันกระสุนได้มากขึ้น!
atsu85

12
ผมไม่เห็นด้วยตรงนี้เป็นสิ่งที่ตัวเลือกนี้ถูกสร้างขึ้นมาสำหรับ: --noImplicitAnyอนุญาตให้สัญกรณ์วงเล็บด้วย ตรงกับคำถามของ op อย่างสมบูรณ์
Ghetolay

4
ฉันเห็นด้วยกับ @Ghetolay นี่เป็นเพียงตัวเลือกเดียวหากไม่สามารถแก้ไขอินเตอร์เฟสได้ XMLHttpRequestยกตัวอย่างเช่นกับการเชื่อมต่อภายในเช่น
Marco Roy

1
ฉันเห็นด้วยกับ @Ghetolay ฉันสงสัยว่าสิ่งนี้แตกต่างจากคำตอบของ Pedro Villa Verde อย่างไร (นอกเหนือจากความจริงที่ว่ารหัสนั้นน่าเกลียดน้อยกว่า) เราทุกคนรู้ว่าการเข้าถึงคุณสมบัติวัตถุโดยใช้สตริงควรหลีกเลี่ยงหากเป็นไปได้ แต่บางครั้งเราก็มีเสรีภาพในขณะที่เข้าใจความเสี่ยง
สตีเฟ่นพอล

มันเป็นแค่การแลกเปลี่ยน เลือกสิ่งที่คุณต้องการ: พื้นที่ผิวข้อผิดพลาดน้อยลงและการเข้าถึงดัชนีที่เข้มงวดหรือมีพื้นที่ผิวมากขึ้นเพื่อหาข้อผิดพลาดและเข้าถึงดัชนีที่ไม่รู้จักได้ง่าย keyofผู้ประกอบการTS2.1 สามารถช่วยให้ทุกอย่างเข้มงวดดูคำตอบของ Piotr!
trusktr

24

โซลูชัน 'keyof' ที่กล่าวถึงข้างต้นใช้งานได้ แต่หากใช้ตัวแปรเพียงครั้งเดียวเช่นการวนลูปวัตถุ ฯลฯ คุณสามารถพิมพ์มันซ้ำได้

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}

ขอบคุณ ใช้งานได้กับการเข้าถึงคีย์โดยพลการเมื่อทำซ้ำคีย์ของวัตถุอื่น
bucabay


7

คล้ายกับคำตอบของ @Piotr Lewandowski แต่ภายในforEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });

คุณทำงานนี้ได้อย่างไร ฉันพยายามในสิ่งเดียวกัน (ts 3.8.3) Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'แม้ว่ามันจะพ่นข้อผิดพลาดว่า: รหัสของฉันดูเหมือนObject.keys(components).forEach((comp: Component) => {...}ว่าComponentจะอยู่ที่ไหนประเภท (ชอบMyConfig)
theGirrafish


6

ไม่มีตัวจัดทำดัชนี? จากนั้นทำด้วยตัวเอง!

ฉันได้กำหนดสิ่งนี้ทั่วโลกเป็นวิธีง่ายๆในการกำหนดวัตถุลายเซ็น Tสามารถanyถ้าจำเป็น:

type Indexer<T> = { [ key: string ]: T };

ฉันเพิ่งเพิ่มindexerเป็นสมาชิกชั้นเรียน

indexer = this as unknown as Indexer<Fruit>;

ดังนั้นฉันจะจบลงด้วยสิ่งนี้:

constructor(private breakpointResponsiveService: FeatureBoxBreakpointResponsiveService) {

}

apple: Fruit<string>;
pear: Fruit<string>;

// just a reference to 'this' at runtime
indexer = this as unknown as Indexer<Fruit>;

something() {

    this.indexer['apple'] = ...    // typed as Fruit

ข้อดีของการทำเช่นนี้คือคุณได้รับประเภทที่ถูกต้องกลับมา - โซลูชันที่ใช้งานมากมาย<any>จะทำให้การพิมพ์ของคุณเสียไป จำไว้ว่านี่ไม่ได้ทำการตรวจสอบรันไทม์ใด ๆ คุณจะต้องตรวจสอบว่ามีบางสิ่งอยู่หรือไม่ถ้าคุณไม่แน่ใจว่ามีอยู่จริงหรือไม่

หากคุณต้องการระมัดระวังให้มากจนเกินไปและคุณกำลังใช้งานอยู่strictคุณสามารถทำสิ่งนี้เพื่อเปิดเผยสถานที่ทั้งหมดที่คุณอาจต้องทำการตรวจสอบที่ไม่ได้กำหนดอย่างชัดเจน:

type OptionalIndexed<T> = { [ key: string ]: T | undefined };

ฉันมักจะไม่พบสิ่งนี้จำเป็นเพราะถ้าฉันมีคุณสมบัติสตริงจากที่อื่นฉันมักจะรู้ว่ามันถูกต้อง

ฉันพบว่าวิธีนี้มีประโยชน์อย่างยิ่งหากฉันมีรหัสจำนวนมากที่ต้องเข้าถึงตัวทำดัชนีและการพิมพ์สามารถเปลี่ยนแปลงได้ในที่เดียว

หมายเหตุ: ฉันกำลังใช้strictโหมดและunknownจำเป็นอย่างยิ่ง

โค้ดที่คอมไพล์จะเป็นindexer = thisดังนั้นจึงคล้ายกับเวลาที่ typescript สร้าง_this = thisให้คุณ


1
บางกรณีคุณอาจใช้Record<T>ประเภทแทนได้ - ตอนนี้ฉันไม่สามารถค้นคว้ารายละเอียดได้อย่างละเอียด แต่สำหรับบางกรณีมันอาจทำงานได้ดีขึ้น
Simon_Weaver

5

สร้างอินเทอร์เฟซเพื่อกำหนดอินเทอร์เฟซ 'ตัวสร้างดัชนี'

จากนั้นสร้างวัตถุของคุณด้วยดัชนีนั้น

หมายเหตุ:นี่จะยังคงมีปัญหาเดียวกันคำตอบอื่น ๆ ที่อธิบายไว้เกี่ยวกับการบังคับใช้ประเภทของแต่ละรายการ - แต่นั่นก็มักจะเป็นสิ่งที่คุณต้องการ

คุณสามารถสร้างพารามิเตอร์ประเภททั่วไปตามที่คุณต้องการ: ObjectIndexer< Dog | Cat>

// this should be global somewhere, or you may already be 
// using a library that provides such a type
export interface ObjectIndexer<T> {
  [id: string]: T;
}

interface ISomeObject extends ObjectIndexer<string>
{
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

สนามเด็กเล่น typescript


คุณสามารถใช้สิ่งนี้ในข้อ จำกัดทั่วไปเมื่อกำหนดประเภททั่วไป:

export class SmartFormGroup<T extends IndexableObject<any>> extends FormGroup

จากนั้นTภายในชั้นเรียนสามารถจัดทำดัชนี :-)


ผมไม่คิดว่ามีมาตรฐาน 'ในตัว' อินเตอร์เฟซสำหรับDictionaryที่แสดงถึงแต่ถ้ามีเคยเป็นโปรดแก้ไขคำถามนี้จะลบของฉัน{ [key: string]: T } ObjectIndexer
Simon_Weaver

3

ประกาศประเภทซึ่งเป็นกุญแจสำคัญของสตริงและค่าสามารถใด ๆ ประกาศวัตถุที่มีประเภทนี้และผ้าสำลีจะไม่ปรากฏขึ้น

type MyType = {[key: string]: any};

ดังนั้นรหัสของคุณจะเป็น

type ISomeType = {[key: string]: any};

    let someObject: ISomeType = {
        firstKey:   'firstValue',
        secondKey:  'secondValue',
        thirdKey:   'thirdValue'
    };

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];

1

ในวันนี้ทางออกที่ดีกว่าคือการประกาศประเภท ชอบ

enum SomeObjectKeys {
    firstKey = 'firstKey',
    secondKey = 'secondKey',
    thirdKey = 'thirdKey',
}

let someObject: Record<SomeObjectKeys, string> = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue',
};

let key: SomeObjectKeys = 'secondKey';

let secondValue: string = someObject[key];

1

ทางออกที่ง่ายที่สุดที่ฉันสามารถหาได้โดยใช้Typescript 3.1ใน 3 ขั้นตอนคือ:

1) สร้างอินเตอร์เฟส

interface IOriginal {
    original: { [key: string]: any }
}

2) ทำสำเนาพิมพ์

let copy: IOriginal = (original as any)[key];

3) ใช้งานได้ทุกที่ (รวม JSX)

<input customProp={copy} />
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.