Bagaimana cara mencegah kesalahan "Tanda tangan indeks dari jenis objek secara implisit memiliki tipe 'apa saja'" ketika mengkompilasi naskah dengan noImplicitAny flag diaktifkan?

309

Saya selalu mengkompilasi naskah dengan flag --noImplicitAny. Ini masuk akal karena saya ingin memeriksa tipe saya sekencang mungkin.

Masalah saya adalah bahwa dengan kode berikut ini saya mendapatkan kesalahan 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];

Penting untuk dicatat adalah idenya adalah bahwa variabel kunci berasal dari tempat lain dalam aplikasi dan dapat berupa salah satu kunci dalam objek.

Saya sudah mencoba secara eksplisit casting type dengan:

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

Atau skenario saya tidak mungkin dengan --noImplicitAny?

Jasper Schulte
sumber

Jawaban:

337

Menambahkan tanda tangan indeks akan membuat TypeScript tahu apa tipe yang seharusnya.

Dalam kasus Anda itu akan menjadi [key: string]: string;

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

Namun, ini juga memberlakukan semua jenis properti agar sesuai dengan tanda tangan indeks. Karena semua properti adalah stringberfungsi.

Sementara tanda tangan indeks adalah cara yang ampuh untuk menggambarkan larik dan pola 'kamus', mereka juga menegaskan bahwa semua properti cocok dengan tipe pengembalian mereka.

Edit:

Jika tipenya tidak cocok, tipe gabungan bisa digunakan [key: string]: string|IOtherObject;

Dengan tipe gabungan, lebih baik jika Anda membiarkan TypeScript menyimpulkan tipe daripada mendefinisikannya.

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

Meskipun itu bisa menjadi sedikit berantakan jika Anda memiliki banyak jenis tanda tangan indeks. Alternatif untuk itu adalah menggunakan anytanda tangan. [key: string]: any;Maka Anda perlu melemparkan jenis seperti yang Anda lakukan di atas.

thoughtrepo
sumber
Dan jika antarmuka Anda terlihat seperti antarmuka ISomeObject {firstKey: string; secondKey: IOtherObject; } kurasa ini tidak mungkin?
Jasper Schulte
Terima kasih! Kombinasi jenis apa pun bersama-sama dengan casting jenis per kasus tampaknya cara yang dijual untuk pergi.
Jasper Schulte
Hai, Bagaimana menangani "anyObject [key: Object] ['name']"?
Code_Crash
atau katakan sesuatu seperti _obj = {}; biarkan _dbKey = _props [kunci] ['name']; _obj [_dbKey] = this [key]; di sini _props adalah objek dan objek [kunci] juga akan mengembalikan objek yang akan memiliki properti nama.
Code_Crash
180

Cara lain untuk menghindari kesalahan adalah dengan menggunakan para pemain seperti ini:

let secondValue: string = (<any>someObject)[key]; (Perhatikan tanda kurung)

Satu-satunya masalah adalah bahwa ini bukan tipe-aman lagi, karena Anda casting ke any. Tetapi Anda selalu dapat kembali ke jenis yang benar.

ps: Saya menggunakan naskah 1.7, tidak yakin tentang versi sebelumnya.

Pedro Villa Verde
sumber
20
Untuk menghindari peringatan tslint, Anda juga dapat menggunakan:let secondValue: string = (someObject as any)[key];
briosheje
97

TypeScript 2.1 memperkenalkan cara yang elegan untuk menangani masalah ini.

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

Kami dapat mengakses semua nama properti objek selama fase kompilasi dengan keyofkata kunci (lihat changelog ).

Anda hanya perlu mengganti stringtipe variabel dengankeyof ISomeObject . Sekarang kompiler tahu keyvariabel diizinkan hanya berisi nama properti dari ISomeObject.

Contoh lengkap:

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];

Kode langsung di typescriptlang.org ( noImplicitAnyopsi setel )

Bacaan lebih lanjut dengan lebih banyak keyofpenggunaan .

Piotr Lewandowski
sumber
6
Namun itu tidak akan berfungsi jika kita menyatakan keysebagai const key = (keyof ISomeObject)= 'detik' + 'Kunci'
Kecewa
55

Berikut pengaturan tsconfig akan memungkinkan Anda untuk mengabaikan kesalahan ini - setel ke true.

suppressImplicitAnyIndexErrors

Menekan kesalahan noImplicitAny untuk objek pengindeksan yang tidak memiliki tanda tangan indeks.

Scott Munro
sumber
14
itu adalah sesuatu yang seharusnya tidak Anda lakukan - mungkin seseorang di tim Anda telah secara eksplisit mengatur opsi kompiler ini untuk membuat kode lebih tahan peluru!
atsu85
12
Saya tidak setuju inilah yang dibuat untuk opsi ini: Izinkan notasi tanda kurung --noImplicitAny. Cocokkan dengan sempurna pertanyaan op.
Ghetolay
4
Saya setuju dengan @Ghetolay. Ini juga satu-satunya opsi jika memodifikasi antarmuka tidak memungkinkan. Misalnya, dengan antarmuka internal seperti XMLHttpRequest.
Marco Roy
1
Saya juga setuju dengan @Ghetolay. Saya ingin tahu bagaimana ini secara kualitatif berbeda dari jawaban Pedro Villa Verde (terlepas dari kenyataan bahwa kodenya kurang jelek). Kita semua tahu bahwa mengakses properti objek menggunakan string harus dihindari jika memungkinkan, tetapi kadang-kadang kita menikmati kebebasan itu sambil memahami risikonya.
Stephen Paul
Ini hanya pengorbanan. Pilih yang Anda suka: lebih sedikit area permukaan kesalahan dan akses indeks yang ketat, atau lebih banyak area permukaan untuk kesalahan dan mudah mengakses indeks yang tidak diketahui. keyofOperator TS2.1 dapat membantu menjaga semuanya tetap ketat, lihat jawaban Piotr!
trusktr
24

Solusi 'keyof' yang disebutkan di atas berfungsi. Tetapi jika variabel digunakan hanya sekali misalnya perulangan melalui objek dll, Anda juga dapat mengetikkannya.

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}
Karna
sumber
Terima kasih. Ini berfungsi untuk akses kunci sewenang-wenang ketika iterasi kunci objek lain.
bucabay
19

menggunakan keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]
alsotang
sumber
7

Mirip dengan jawaban @Piotr Lewandowski, tetapi dalam forEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });
Steve Brush
sumber
Bagaimana Anda membuatnya bekerja? Saya mencoba hal yang sama (ts 3.8.3), meskipun melempar kesalahan mengatakan: Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'. Kode saya terlihat seperti itu Object.keys(components).forEach((comp: Component) => {...}, di mana Componentada tipe (seperti MyConfig).
theGirrafish
6

Nyatakan objek seperti ini.

export interface Thread {
    id:number;
    messageIds: number[];
    participants: {
        [key:number]: number
    };
}
Supun Dharmarathne
sumber
6

Tidak ada pengindeks? Lalu buat sendiri!

Saya secara global mendefinisikan ini sebagai cara mudah untuk mendefinisikan tanda tangan objek. Tbisa anyjika diperlukan:

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

Saya hanya menambahkan indexersebagai anggota kelas.

indexer = this as unknown as Indexer<Fruit>;

Jadi saya berakhir dengan ini:

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

Manfaat melakukan ini adalah Anda mendapatkan kembali tipe yang tepat - banyak solusi yang digunakan <any> akan kehilangan mengetik untuk Anda. Ingat ini tidak melakukan verifikasi runtime. Anda masih perlu memeriksa apakah ada sesuatu jika Anda tidak tahu pasti itu ada.

Jika Anda ingin sangat berhati-hati, dan Anda menggunakan strictAnda dapat melakukan ini untuk mengungkapkan semua tempat Anda mungkin perlu melakukan pemeriksaan yang tidak ditentukan secara eksplisit:

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

Saya biasanya tidak menemukan ini diperlukan karena jika saya memiliki properti string dari suatu tempat saya biasanya tahu bahwa itu valid.

Saya menemukan metode ini sangat berguna jika saya memiliki banyak kode yang perlu mengakses pengindeks, dan pengetikan dapat diubah hanya dalam satu tempat.

Catatan: Saya menggunakan strictmode, dan unknownitu pasti diperlukan.

Kode yang dikompilasi hanya akan indexer = this, jadi sangat mirip dengan saat naskah dibuat _this = thisuntuk Anda.

Simon_Weaver
sumber
1
Beberapa kasus, Anda mungkin dapat menggunakan Record<T>jenis sebagai gantinya - Saya tidak dapat saat ini untuk meneliti detail halus ini, tetapi untuk beberapa kasus terbatas ini dapat bekerja lebih baik.
Simon_Weaver
5

Buat antarmuka untuk mendefinisikan antarmuka 'pengindeks'

Kemudian buat objek Anda dengan indeks itu.

Catatan: ini akan tetap memiliki masalah yang sama dengan jawaban lain yang dijelaskan sehubungan dengan menegakkan jenis setiap item - tetapi itu sering kali persis seperti yang Anda inginkan.

Anda dapat membuat parameter tipe generik apa pun yang Anda butuhkan: 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];

Taman Bermain Anak


Anda bahkan dapat menggunakan ini dalam batasan generik ketika mendefinisikan tipe generik:

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

Kemudian Tdi dalam kelas dapat diindeks :-)

Simon_Weaver
sumber
Saya tidak berpikir ada antarmuka 'built-in' standar untuk Dictionaryyang mewakili { [key: string]: T }, tetapi jika ada silakan edit pertanyaan ini untuk menghapus saya ObjectIndexer.
Simon_Weaver
3

Nyatakan tipe yang kuncinya adalah string dan nilainya dapat berupa apa saja kemudian mendeklarasikan objek dengan tipe ini dan serat tidak akan muncul

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

Jadi kode Anda akan menjadi

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

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

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];
O.AbedElBaset
sumber
1

Saat ini solusi yang lebih baik adalah dengan mendeklarasikan tipe. Suka

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];
Artsiom Tymchanka
sumber
1

Solusi paling sederhana yang bisa saya temukan menggunakan TypeScript 3.1 dalam 3 langkah adalah:

1) Buat antarmuka

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

2) Buat salinan yang diketik

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

3) Gunakan di mana saja (termasuk BEJ)

<input customProp={copy} />
Artokun
sumber