Typescript: Izinkan pengindeksan dengan simbol

Dibuat pada 30 Jan 2015  ·  93Komentar  ·  Sumber: microsoft/TypeScript

TypeScript sekarang memiliki mode target ES6 yang menyertakan definisi Symbol . Namun ketika mencoba mengindeks objek dengan simbol, saya mendapatkan kesalahan (Argumen ekspresi indeks harus berjenis 'string', 'number', atau 'any').

var theAnswer = Symbol('secret');
var obj = {};
obj[theAnswer] = 42; // Currently error, but should be allowed
Moderate Fix Available Suggestion help wanted

Komentar yang paling membantu

Ketikan 3.0.1, digigit oleh ini.
Saya ingin rekaman yang menerima symbol tetapi TS tidak mengizinkan saya.

Sudah 3,5 tahun sejak terbitan ini dibuka, bolehkah kita punya simbol sekarang, tolong 🙏

Ironisnya, TS berkontradiksi dengan dirinya sendiri.
TS memperluas keyof any = number | string | symbol .

Tapi kemudian ketika Anda melakukan record[symbol] TS menolak mengatakan
_Type 'symbol' tidak dapat digunakan sebagai pengindeks_.

Semua 93 komentar

Itu adalah bagian dari dukungan Simbol ES6 yang sedang kami

@wereHamster , dengan pull request # 1978, ini akan menjadi legal, dan obj[theAnswer] akan memiliki tipe any . Apakah itu cukup untuk apa yang Anda cari, atau apakah Anda perlu mengetik lebih kuat?

Apakah mungkin untuk menentukan tipe properti yang diindeks oleh simbol? Sesuatu seperti berikut ini:

var theAnswer = Symbol('secret');
interface DeepThought {
   [theAnswer]: number;
}

Berdasarkan komentar di PR tersebut, tidak:

_Ini tidak mencakup pengindeks simbol, yang memungkinkan objek bertindak sebagai peta dengan kunci simbol arbitrer._

Saya pikir @wereHamster berbicara tentang pengetikan yang lebih kuat daripada @danquirk. Ada 3 level dukungan di sini. Level paling dasar disediakan oleh PR saya, tetapi itu hanya untuk simbol yang merupakan properti dari objek Simbol global, bukan simbol yang ditentukan pengguna. Begitu,

var theAnswer = Symbol('secret');
interface DeepThought {
    [Symbol.toStringTag](): string; // Allowed
    [theAnswer]: number; // not allowed
}

Tingkat dukungan selanjutnya adalah mengizinkan pengindeks simbol:

var theAnswer = Symbol('secret');
interface DeepThought {
   [s: symbol]: number;
}
var d: DeepThought;
d[theAnswer] = 42; // Typed as number

Ini ada di radar kami, dan dapat diterapkan dengan mudah.

Level terkuat adalah apa yang Anda minta, seperti:

var theAnswer = Symbol('secret');
var theQuestion = Symbol('secret');
interface DeepThought {
   [theQuestion]: string;
   [theAnswer]: number;
}
var d: DeepThought;
d[theQuesiton] = "why";
d[theAnswer] = 42;

Ini akan sangat bagus, tetapi sejauh ini kami belum menemukan desain yang masuk akal untuk itu. Ini pada akhirnya tampaknya bergantung pada membuat tipe bergantung pada nilai runtime dari simbol-simbol ini. Kami akan terus memikirkannya, karena ini jelas hal yang berguna untuk dilakukan.

Dengan PR saya, Anda setidaknya harus dapat menggunakan simbol untuk menarik nilai _out_ dari suatu objek. Ini akan menjadi any , tetapi Anda tidak akan lagi mendapatkan kesalahan.

@hamster Saya membuat artikel kecil # 2012 yang mungkin menarik bagi Anda.

Saya telah menggabungkan permintaan # 1978, tetapi saya akan membiarkan bug ini terbuka, karena tampaknya meminta lebih dari yang saya berikan dengan perubahan itu. Namun, dengan perubahan saya, kesalahan asli akan hilang.

@wereHamster Dapatkah Anda memposting pembaruan tentang apa lagi yang Anda ingin lihat terjadi di sini? Tidak segera jelas bagi saya apa yang telah kami terapkan vs apa yang Anda poskan

Adakah ide ketika symbol akan menjadi tipe yang valid sebagai pengindeks? Apakah ini sesuatu yang bisa dilakukan sebagai humas komunitas?

Kami akan mengambil PR untuk ini. @JsonFreeman dapat memberikan detail tentang beberapa masalah yang mungkin Anda

Saya benar-benar berpikir menambahkan pengindeks simbol akan sangat mudah. Ini akan bekerja seperti angka dan string, kecuali bahwa itu tidak akan kompatibel dengan salah satu dari mereka dalam penugasan, jenis argumen inferensi, dll. Tantangan utamanya adalah memastikan Anda ingat untuk menambahkan logika di semua tempat yang sesuai.

@RyanCavanaugh , alangkah baiknya jika akhirnya memiliki contoh terakhir di https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456 typecheck. Tetapi jika Anda lebih suka, Anda dapat membagi masalah ini menjadi beberapa masalah kecil yang saling bertumpuk.

Apakah ada pembaruan di bagian depan ini? AFAIU versi terbaru dari kompilator hanya mendukung level pertama yang dijelaskan di https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456.

Kami akan dengan senang hati menerima PR untuk perubahan ini.

Mungkin ada baiknya melacak dua tingkat sebagai dua masalah yang terpisah. Pengindeks tampak cukup mudah, tetapi kegunaannya tidak jelas. Dukungan penuh dengan pelacakan konstan tampaknya cukup sulit, tetapi mungkin lebih berguna.

Pelacakan konstan sudah dilacak di https://github.com/Microsoft/TypeScript/issues/5579. Masalah ini untuk menambahkan dukungan untuk pengindeks simbol, mirip dengan pengindeks string dan angka.

Oke, masuk akal.

@JsonFreeman @mhegazy masalah tersedia di # 12932

Hanya berpikir aku akan melempar kasus penggunaanku ke dalam ring. Saya sedang menulis alat yang memungkinkan kueri dijelaskan dengan menentukan kunci teks biasa untuk pencocokan terhadap properti objek arbitrer, dan simbol untuk menentukan operator yang cocok. Dengan menggunakan simbol untuk operator terkenal, saya menghindari ambiguitas mencocokkan operator versus bidang yang kuncinya sama dengan operator terkenal.

Karena simbol tidak dapat ditentukan sebagai kunci indeks, berbeda dengan yang diizinkan secara eksplisit oleh JavaScript, saya terpaksa memasukkan <any> di sejumlah tempat, yang menurunkan kualitas kode.

interface Query {
  [key: string|symbol]: any;
}

const Q = {
  startsWith: Symbol('startsWith'),
  gte: Symbol('gte'),
  lte: Symbol('lte')
}

const sample: Query = {
  name: {
    [Q.startsWith]: 'M',
    length: {
      [Q.lte]: 25
    }
  },
  age: {
    [Q.gte]: 18
  }
};

Penggunaan karakter pertama yang "tidak mungkin" seperti karakter $ bukanlah kompromi yang sesuai, mengingat variasi data yang mungkin perlu diperiksa oleh mesin kueri.

Hai kawan. Apakah ada gerakan dalam hal ini? Saya membutuhkannya jadi saya akan dengan senang hati menyumbangkan perubahan yang diperlukan. Belum pernah berkontribusi pada TS sebelumnya.

@mhegazy @RyanCavanaugh Saya tahu kalian sangat sibuk, tetapi bisakah Anda mempertimbangkan ketika ada kesempatan? Simbol adalah alat yang sangat penting untuk merancang perpustakaan dan kerangka kerja, dan kurangnya kemampuan untuk menggunakannya dalam antarmuka adalah masalah yang pasti.

Saya bertanya apakah ada sesuatu yang sedang berlangsung? Saya sangat berharap fitur ini didukung.

Ya masih mencari ini hari ini, inilah yang saya lihat di Webstorm:

screenshot 2017-10-08 21 37 17

Itu benar-benar berhasil

var test: symbol = Symbol();

const x = {
    [test]: 1
};

x[test];

console.log(x[test]);

console.log(x['test']);

tetapi jenis x tidak benar, dianggap sebagai

{
  [key: string]: number
}

Ya masih mencari ini hari ini, inilah yang saya lihat di Webstorm:

Harap dicatat bahwa layanan bahasa JetBrains sendiri yang diaktifkan secara default di WebStorm, intelliJ IDEA, dan seterusnya.

Ini bekerja di TS 2.7

const key = Symbol('key')
const a: { [key]?: number } = {}
a[key] = 5

Ada pembaruan tentang ini?

Masalahku:

export interface Dict<T> {
  [index: string]: T;

  [index: number]: T;
}

const keyMap: Dict<number> = {};

function set<T extends object>(index: keyof T) {
  keyMap[index] = 1; // Error Type 'keyof T' cannot be used to index type 'Dict<number>'
}

Tetapi ini juga tidak berhasil, karena simbol tidak bisa menjadi tipe indeks.

export interface Dict<T> {
  [index: string]: T;
  [index: symbol]: T; // Error: An index signature parameter type must be 'string' or 'number'
  [index: number]: T;
}

Perilaku yang diharapkan:
symbol harus merupakan tipe indeks yang valid

Perilaku sebenarnya:
symbol bukan tipe indeks yang valid

Menggunakan solusi dengan casting as string | number bagi saya tampaknya sangat buruk.

Bagaimana seharusnya util.promisify.custom digunakan dalam TypeScript? Tampaknya menggunakan simbol konstan sekarang didukung, tetapi hanya jika mereka didefinisikan secara eksplisit. Jadi ini adalah TypeScript yang valid (selain dari f tidak diinisialisasi):
typescript const custom = Symbol() interface PromisifyCustom<T, TResult> extends Function { [custom](param: T): Promise<TResult> } const f: PromisifyCustom<string, void> f[custom] = str => Promise.resolve()
Tetapi jika promisify.custom digunakan sebagai pengganti custom , upaya untuk mereferensikan f[promisify.custom] menghasilkan kesalahan Element implicitly has an 'any' type because type 'PromisifyCustom<string, void>' has no index signature. :
typescript import {promisify} from 'util' interface PromisifyCustom<T, TResult> extends Function { [promisify.custom](param: T): Promise<TResult> } const f: PromisifyCustom<string, void> f[promisify.custom] = str => Promise.resolve()
Saya ingin menetapkan ke bidang promisify.custom fungsi, tetapi tampaknya (mengingat perilaku yang dijelaskan di atas) bahwa satu-satunya cara untuk melakukan ini adalah mentransmisikan fungsi ke tipe any .

Saya tidak mengerti mengapa simbol tidak diperbolehkan sebagai indeks kunci, kode di bawah ini seharusnya berfungsi dan diterima oleh Ketikan 2.8 tetapi tidak diperbolehkan oleh Ketikan 2.9

/**
 * Key can only be number, string or symbol
 * */
export class SimpleMapMap<K extends PropertyKey, V> {
  private o: { [k: string ]: V } = {};

  public has (k: K): boolean {
    return k in this.o;
  }

  public get (k: K): V {
    return this.o[k as PropertyKey];
  }

  public set (k: K, v: V) {
    this.o[k as PropertyKey] = v;
  }

  public getMap (k: K): V {
    if (k in this.o) {
      return this.o[k as PropertyKey];
    }
    const res = new SimpleMapMap<K, V>();
    this.o[k as PropertyKey] = res as any as V;
    return res as any as V;
  }

  public clear () {
    this.o = {};
  }
}

Saya mencoba di bawah ini, yang lebih 'benar' bagi saya tetapi tidak diterima oleh kedua versi compiler Typecript

/**
 * Key can only be number, string or symbol
 * */
export class SimpleMapMap<K extends PropertyKey, V> {
  private o: { [k: K ]: V } = {};

  public has (k: K): boolean {
    return k in this.o;
  }

  public get (k: K): V {
    return this.o[k];
  }

  public set (k: K, v: V) {
    this.o[k] = v;
  }

  public getMap (k: K): V {
    if (k in this.o) {
      return this.o[k];
    }
    const res = new SimpleMapMap<K, V>();
    this.o[k as PropertyKey] = res as any as V;
    return res as any as V;
  }

  public clear () {
    this.o = {};
  }
}

Status tiket ini menunjukkan bahwa apa yang Anda sarankan adalah perilaku yang diinginkan, tetapi tim inti pada saat ini tidak melakukan sumber daya untuk menambahkan peningkatan fitur ini, mereka membukanya untuk ditangani oleh komunitas.

@beenotung Meskipun ini bukan solusi yang ideal, dengan asumsi bahwa kelas yang Anda posting adalah satu-satunya tempat di mana Anda memerlukan perilaku seperti itu, Anda dapat melakukan cast yang tidak aman di dalam kelas, tetapi menjaga tanda tangan kelas dan metode tetap sama , sehingga konsumen dari kelas tidak akan melihatnya:

/**
 * Key can only be number, string or symbol
 * */
export class SimpleMapMap<K extends PropertyKey, V> {
  private o: { [k: string]: V } = {};

  public has(k: K): boolean {
    return k in this.o;
  }

  public get(k: K): V {
    return this.o[k as any];
  }

  public set(k: K, v: V) {
    this.o[k as any] = v;
  }

  public getMap(k: K): V {
    if (k in this.o) {
    return this.o[k as any];
    }

    const res = new SimpleMapMap<K, V>();
    this.o[k as any] = res as any as V;
    return res as any as V;
  }

  public clear() {
    this.o = {};
  }
}

Karena tanda tangannya sama, setiap kali Anda menggunakan kelas ini, Anda akan menerapkan validasi jenis dengan benar, dan, saat masalah ini diselesaikan, Anda hanya perlu mengubah kelas ini (ini akan transparan bagi konsumen).

Contoh konsumen seperti di bawah ini (kode tidak perlu diubah saat masalah ini diperbaiki):

const s1 = Symbol(1);
const s2 = Symbol(2);

let m = new SimpleMapMap<symbol, number>()
m.set(s1, 1);
m.set(s2, 2);
m.get(s1);
m.get(1); //error

Ketikan 3.0.1, digigit oleh ini.
Saya ingin rekaman yang menerima symbol tetapi TS tidak mengizinkan saya.

Sudah 3,5 tahun sejak terbitan ini dibuka, bolehkah kita punya simbol sekarang, tolong 🙏

Ironisnya, TS berkontradiksi dengan dirinya sendiri.
TS memperluas keyof any = number | string | symbol .

Tapi kemudian ketika Anda melakukan record[symbol] TS menolak mengatakan
_Type 'symbol' tidak dapat digunakan sebagai pengindeks_.

Ya, saya telah menderita dengan yang ini untuk sementara waktu, sayangnya, pertanyaan terbaru saya tentang topik ini:

https://stackoverflow.com/questions/53404675/ts2538-type-unique-symbol-cannot-be-used-as-an-index-type

@RyanCavanaugh @DanielRosenwasser @mhegazy Ada pembaruan? Terbitan ini mendekati ulang tahunnya yang keempat.

Jika seseorang dapat mengarahkan saya ke arah yang benar, saya dapat mencobanya. Jika ada tes yang cocok, bahkan lebih baik.

@jhpratt ada PR di # 26797 (perhatikan peringatan tentang simbol-simbol terkenal). Ada catatan rapat desain terbaru tentangnya di # 28581 (tetapi tidak ada resolusi yang tercatat di sana). Ada sedikit lebih banyak umpan balik tentang mengapa PR itu ditahan di sini . Tampaknya ini dianggap sebagai masalah pinggiran / berdampak rendah, jadi mungkin lebih banyak suara positif pada PR dapat membantu meningkatkan profil masalah.

Terima kasih @yortus. Saya baru saja bertanya kepada Ryan apakah PR-nya masih direncanakan untuk 3,2, yang diindikasikan dengan milestone. Semoga begitu, dan ini akan diselesaikan!

PR yang ditunjuk oleh @yortus sepertinya perubahan besar,
Bukankah perbaikan untuk bug ini harus sangat kecil? misalnya menambahkan pernyataan atau dalam pemeriksaan kondisi.
(Saya belum menemukan tempat untuk pindah.)

solusi temp di sini https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -412287117, agak jelek tetapi menyelesaikan pekerjaan

const DEFAULT_LEVEL: string = Symbol("__default__") as any;

https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -460650063 lainnya, karena linters h8 any

const ItemId: string = Symbol('Item.Id') as unknown as string;
type Item = Record<string, string>;
const shoes: Item = {
  name: 'whatever',
}
shoes[ItemId] = 'randomlygeneratedstring'; // no error
{ name: 'whatever', [Symbol(Item.Id)]: 'randomlygeneratedstring' }

Saya kira salah satu gotcha yang saya perhatikan dalam menggunakan simbol adalah jika Anda memiliki proyek yang melibatkan modul child_process, ya Anda dapat berbagi jenis / enum / antarmuka antara dua proses tetapi tidak pernah simbol.

sangat bagus untuk menyelesaikan ini, simbol sangat bagus dalam melacak objek tanpa mencemari kunci mereka dan diharuskan menggunakan peta / set, di atas itu tolok ukur dalam beberapa tahun terakhir menunjukkan bahwa mengakses simbol sama cepatnya dengan mengakses string / tombol angka


Edit: Ternyata pendekatan ini hanya bekerja dengan Record<X,Y> tetapi tidak Interfaces . Akhirnya menggunakan // @ts-ignore untuk saat ini karena itu masih benar secara sintaksis dan masih dapat dikompilasi dengan baik ke JS sebagaimana mestinya.

Satu hal yang perlu diperhatikan adalah ketika menggunakan // @ts-ignore pada baris yang melibatkan simbol, sebenarnya mungkin (dan membantu) untuk secara manual menentukan jenis simbol itu. VSCode masih menangkapnya.

const id = Symbol('ID');

interface User {
  name: string;
  age: number;
}

const alice: User = {
  name: 'alice',
  age: 25,
};

// @ts-ignore
alice[id] = 'maybeSomeUUIDv4String';

// ...

// then somewhere, when you need this User's id

// @ts-ignore
const id: string = alice[id];

console.log(id); // here you can hover on id and it will say it's a string

Entahlah, apakah ada yang telah memulai sesuatu untuk memperbaikinya, tetapi jika tidak, saya punya sekarang.

Waktu saya terbatas, dan saya tidak memiliki pengetahuan apa pun tentang sumber naskah. Saya telah membuat garpu (https://github.com/Neonit/TypeScript), tetapi belum ada permintaan tarik, karena saya tidak ingin menganiaya pengembang dengan perubahan yang belum selesai (?). Saya akan meminta semua orang untuk menyumbangkan apa yang mereka bisa untuk garpu saya. Saya akhirnya akan mengeluarkan PR nanti.

Sejauh ini saya telah menemukan cara untuk memperbaiki batasan tipe indeks antarmuka. Saya tidak tahu, apakah ada lebih dari itu. Saya dapat mengindeks objek dengan simbol di TS 3.4 tanpa perbaikan apa pun. ( https://www.typescriptlang.org/play/#src = const% 20o% 20% 3D% 20% 7B% 7D% 3B% 0D% 0Aconst% 20s% 20% 3D% 20Symbol ('s')% 3B % 0D% 0A% 0D% 0Ao% 5Bs% 5D% 20% 3D% 20123% 3B)

Lihat komitmen saya untuk melihat apa yang saya temukan: https://github.com/Neonit/TypeScript-SymbolKeys/commit/11cb7c13c2494ff32cdec2d4f82673058c825dc3

Hilang:

  • Tes: Saya belum punya waktu untuk melihat bagaimana tes di TS diatur dan terstruktur.
  • Lokalisasi: Pesan diagnostik hanya diperbarui untuk varian bahasa Inggris. Mungkin bahasa lain masih menerima pesan lama. Saya tidak tahu. Saya hanya bisa memberikan terjemahan bahasa Jerman.

Saya berharap ini akan memulai semuanya setelah bertahun-tahun menunggu.

perbaikannya terlihat bagus. dapatkah TypeScript dev memeriksanya?

halo, ada kemajuan untuk ini?

Baru saja membuka utas SO tentang ini: https://stackoverflow.com/questions/59118271/using-symbol-as-object-key-type-in-typescript

Mengapa itu tidak mungkin? Bukankah symbol tipe primitif lain seperti number - jadi mengapa ada perbedaan?

Halo, ada kemajuan untuk ini?

LIMA tahun telah berlalu!

Anda tidak akan percaya berapa lama C ++ untuk menutupnya 😲

lol adil, tetapi C ++ tidak memasarkan dirinya sendiri sebagai superset dari bahasa yang memiliki penutupan :-p

@ljharb terus

Bagi mereka yang menargetkan runtime yang lebih baru, mengapa tidak menggunakan Map ? Saya secara anekdot menemukan bahwa banyak pengembang tidak mengetahui keberadaan Map s, jadi saya ingin tahu apakah ada skenario lain yang saya lewatkan.

let m = new Map<symbol, number>();
let s = Symbol("arbitrary symbol!");

m.set(s, 1000);
let a = m.get(s);


Peta dan objek memiliki kasus penggunaan yang berbeda.

Simbol terkenal Symbol.match , misalnya, tidak akan membuat objek seperti RegExp (dan objek apa pun mungkin menginginkan kunci Symbol.iterable agar dapat diulang tanpa harus secara eksplisit menggunakan TS bawaan Tipe Iterable).

Hampir 5 tahun (

Tolong, terapkan fitur ini, saya tidak dapat menulis kode secara normal ..

Dapatkah peserta memberikan contoh aktual dalam kasus penggunaan mereka?

Saya tidak memahami contoh protokol dan mengapa hal itu tidak memungkinkan hari ini.

Berikut adalah contoh StringConvertible

const intoString = Symbol("intoString")

/**
 * Something that can be converted into a string.
 */
interface StringConvertible {
    [intoString](): string;
}

/**
 * Something that is adorable.
 */
class Dog implements StringConvertible {
    [intoString](): string {
        return "RUFF RUFF";
    }
}

/**
 * <strong i="9">@see</strong> {https://twitter.com/drosenwasser/status/1102337805336768513}
 */
class FontDog implements StringConvertible {
    [intoString](): string {
        return "WOFF WOFF";
    }
}

console.log(new Dog()[intoString]())
console.log(new FontDog()[intoString]())

Berikut adalah contoh Mappable atau Functor (selain tidak ada konstruktor tipe orde tinggi):

const map = Symbol("map")

interface Mappable<T> {
    [map]<U>(f: (x: T) => U): Mappable<U>
}

class MyCoolArray<T> extends Array<T> implements Mappable<T> {
    [map]<U>(f: (x: T) => U) {
        return this.map(f) as MyCoolArray<U>;
    }
}

@DanielRosenwasser sepertinya Anda mengasumsikan semua objek memiliki antarmuka atau merupakan instance kelas atau sudah dikenal sebelumnya; menggunakan contoh terakhir Anda, saya harus dapat menginstal map , katakanlah, ke objek javascript apa pun (atau setidaknya, objek yang tipenya memungkinkan untuk simbol apa pun ditambahkan ke dalamnya), yang kemudian membuatnya dapat dipetakan.

Menginstal properti (simbol atau tidak) ke objek setelah fakta adalah bagian dari permintaan fitur yang berbeda (sering disebut "properti perluasan" atau "jenis perluasan").

Jika tidak, jenis yang Anda perlukan untuk tanda tangan indeks simbol akan memberikan sangat sedikit sebagai pengguna TypeScript, bukan? Jika saya mengerti dengan benar, jenisnya harus seperti unknown atau hanya any agar berguna.

interface SymbolIndexable {
   [prop: symbol]: any; // ?
}

Dalam kasus protokol, biasanya ini adalah fungsi, tetapi tentu saja, ini bisa menjadi unknown .

Yang saya butuhkan adalah simbol (dan bigint) yang setara dengan type O = { [k: string]: unknown } , jadi saya dapat mewakili objek JS yang sebenarnya (sesuatu yang dapat memiliki kunci apa pun) dengan sistem tipe. Saya bisa mempersempitnya nanti sesuai kebutuhan, tetapi tipe dasar untuk objek JS akan menjadi { [k: string | bigint | symbol | number]: unknown } , pada dasarnya.

Ah saya rasa saya melihat poin @DanielRosenwasser . Saat ini saya memiliki kode dengan antarmuka seperti:

export interface Environment<T> {
    [Default](tag: string): Intrinsic<T>;
    [Text]?(text: string): string;
    [tag: string]: Intrinsic<T>;
    // TODO: allow symbol index parameters when typescript gets its shit together
    // [tag: symbol]: Intrinsic<T>;
}

di mana Intrinsic<T> adalah jenis fungsi, dan saya ingin mengizinkan pengembang untuk mendefinisikan properti simbol mereka sendiri pada lingkungan yang mirip dengan string, tetapi sejauh Anda dapat menambahkan [Symbol.iterator] , [Symbol.species] atau properti simbol kustom ke antarmuka apa pun, tanda tangan indeks dengan simbol akan salah membatasi objek yang menerapkan properti ini.

Jadi maksud Anda adalah Anda tidak dapat membuat jenis nilai pengindeksan dengan simbol lebih spesifik dari any ? Bisakah kita entah bagaimana menggunakan perbedaan unique symbol vs symbol untuk memungkinkan ini? Seperti apakah kita dapat membuat tanda tangan indeks sebagai default untuk simbol biasa dan memungkinkan simbol unik / terkenal untuk menggantikan jenis indeks? Meskipun tidak aman untuk mengetik, bisa mendapatkan / menyetel properti dengan indeks simbol secara sewenang-wenang akan membantu.

Alternatifnya adalah meminta pengguna memperluas antarmuka Lingkungan itu sendiri dengan properti simbol mereka, tetapi ini tidak menyediakan jenis keamanan tambahan sejauh pengguna dapat mengetik Simbol sebagai apa pun pada objek.

@DanielRosenwasser di sini adalah contoh nyata dari kode produksi saya. Sebuah Negara digunakan kembali di banyak tempat sebagai peta dan dapat menerima kunci atom (fitur domain). Saat ini saya perlu menambahkan dukungan simbol, tetapi saya mendapatkan banyak kesalahan:


Pokoknya perilaku saat ini tidak sesuai dengan standar ES yang salah.

Satu lagi pemikiran larut malam yang saya miliki tentang jenis simbol. Mengapa ini bukan kesalahan?

const foo = {
  [Symbol.iterator]: 1,
}

JS mengharapkan semua properti Symbol.iterator menjadi fungsi yang mengembalikan iterator, dan objek ini akan merusak banyak kode jika diteruskan di berbagai tempat. Jika ada cara untuk mendefinisikan properti simbol secara global untuk semua objek, kami dapat mengizinkan tanda tangan indeks simbol tertentu sambil juga mengizinkan penggantian global. Ini akan aman untuk mengetik, bukan?

Saya juga kurang paham, kenapa use case dibutuhkan di sini. Ini adalah ketidakcocokan ES6, yang seharusnya tidak ada dalam bahasa yang membungkus ES6.

Di masa lalu saya memposting temuan saya tentang bagaimana ini dapat diperbaiki di sini di utas ini dan jika kode ini tidak kekurangan pemeriksaan atau fitur penting, saya ragu itu lebih memakan waktu untuk mengintegrasikannya ke dalam basis kode daripada melanjutkan diskusi ini.

Saya hanya tidak melakukan permintaan tarik, karena saya tidak tahu tentang kerangka kerja pengujian atau persyaratan Typecript dan karena saya tidak tahu apakah perubahan dalam file yang berbeda akan diperlukan untuk membuat ini berfungsi di semua kasus.

Jadi sebelum melanjutkan menginvestasikan waktu untuk membaca dan menulis di sini, harap periksa apakah menambahkan fitur akan mengurangi waktu yang dihabiskan. Saya ragu ada orang yang akan mengeluh tentang hal itu di Ketikan.

Terlepas dari semua itu, kasus penggunaan umum adalah jika Anda ingin memetakan nilai ke simbol arbitrer. Atau untuk kompatibilitas dengan kode ES6 yang tidak diketik.

Berikut adalah contoh tempat yang menurut saya akan membantu: https://github.com/choojs/nanobus/pull/40/files. Dalam praktiknya, eventName s dapat berupa simbol atau string, jadi saya ingin mengatakannya

type EventsConfiguration = { [eventName: string | Symbol]: (...args: any[]) => void }

di baris pertama.

Tapi saya mungkin salah paham tentang bagaimana saya harus melakukan ini.

Kasus penggunaan sederhana tidak dapat dilakukan tanpa rasa sakit:

type Dict<T> = {
    [key in PropertyKey]: T;
};

function dict<T>() {
    return Object.create(null) as Dict<T>;
}

const has: <T>(dict: Dict<T>, key: PropertyKey) => boolean = Function.prototype.call.bind(Object.prototype.hasOwnProperty);

function forEach<T>(dict: Dict<T>, callbackfn: (value: T, key: string | symbol, dict: Dict<T>) => void, thisArg?: any) {
    for (const key in dict)
        if (has(dict, key))
            callbackfn.call(thisArg, dict[key], key, dict);
    const symbols = Object.getOwnPropertySymbols(dict);
    for (let i = 0; i < symbols.length; i++) {
        const sym = symbols[i];
        callbackfn.call(thisArg, dict[sym], sym, dict); // err
    }
}

const d = dict<boolean>();
const sym = Symbol('sym');
const bi = 9007199254740991n;

d[1] = true;
d['x'] = true;
d[sym] = false; // definitely PITA
d[bi] = false; // another PITA

forEach(d, (value, key) => console.log(key, value));

Saya juga kurang paham, kenapa use case dibutuhkan di sini.

@neonit ada PR untuk mengatasi ini, tetapi pemahaman saya adalah bahwa ada masalah halus dengan bagaimana fitur berinteraksi dengan sistem tipe lainnya. Kurangnya solusi untuk itu, alasan saya meminta kasus penggunaan adalah karena kami tidak bisa hanya bekerja / fokus pada setiap fitur sekaligus yang kami inginkan - jadi kasus penggunaan perlu membenarkan pekerjaan yang sedang dilakukan yang mencakup pekerjaan jangka panjang pemeliharaan fitur.

Tampaknya pada kenyataannya, kasus penggunaan khayalan kebanyakan orang tidak akan diselesaikan semudah yang mereka bayangkan (lihat tanggapan @brainkim di sini https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574550587), atau bahwa keduanya diselesaikan dengan sama baiknya melalui properti simbol (https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574538121) atau Peta (https://github.com/microsoft/TypeScript/issues/1863 # Issuecomment-572733050).

Saya pikir @ Tyler-Murphy memberikan contoh terbaik di sini karena Anda tidak dapat menulis batasan, yang bisa sangat berguna untuk sesuatu seperti emitor acara yang aman dan mendukung simbol.

Jadi sebelum melanjutkan menginvestasikan waktu untuk membaca dan menulis di sini, harap periksa apakah menambahkan fitur akan mengurangi waktu yang dihabiskan. Saya ragu ada orang yang akan mengeluh tentang hal itu di Ketikan.

Ini selalu lebih mudah untuk dikatakan saat Anda tidak harus mempertahankan proyek! 😄 Saya mengerti bahwa ini adalah sesuatu yang berguna untuk Anda, tetapi saya harap Anda menghormatinya.

Ini adalah ketidakcocokan ES6

Ada banyak konstruksi yang tidak dapat diketikkan TypeScript dengan mudah karena itu tidak layak. Saya tidak mengatakan ini tidak mungkin, tetapi saya tidak percaya bahwa ini cara yang tepat untuk membingkai masalah ini.

Jadi sepertinya ketidakmampuan untuk menambahkan kunci simbol karena tanda tangan indeks berasal dari fakta bahwa ada Simbol terkenal global yang memerlukan pengetikan mereka sendiri, yang pasti akan berbenturan dengan tipe indeks simbol. Sebagai solusinya, bagaimana jika kita memiliki modul / antarmuka global yang mewakili semua simbol yang diketahui?

const Answerable = Symbol.for("Answerable");
declare global {
  interface KnownSymbols {
    [Answerable](): string  | number;
  }
}

interface MyObject {
  [name: symbol]: boolean;
}

const MySymbol = Symbol.for("MySymbol");
const obj: MyObject = {
  [MySymbol]: true,
};

obj[Answerable] = () => "42";

Dengan mendeklarasikan properti tambahan pada antarmuka global KnownSymbols , Anda mengizinkan semua objek diindeks oleh simbol itu dan membatasi nilai properti menjadi tidak ditentukan / tipe nilai Anda. Ini akan segera memberikan nilai dengan mengizinkan skrip ketikan untuk memberikan pengetikan untuk simbol terkenal yang disediakan oleh ES6. Menambahkan properti Symbol.iterator ke objek yang bukan merupakan fungsi yang mengembalikan iterator jelas merupakan kesalahan, tetapi saat ini tidak ada dalam skrip ketikan. Dan itu akan membuat penambahan properti simbol terkenal ke objek yang sudah ada menjadi lebih mudah.

Penggunaan modul global ini juga akan memungkinkan Simbol digunakan sebagai kunci arbitrer juga, dan karenanya dalam tanda tangan indeks. Anda hanya perlu memprioritaskan properti simbol yang diketahui global di atas tipe tanda tangan indeks lokal.

Akankah penerapan proposal ini memungkinkan jenis tanda tangan indeks untuk bergerak maju?

Kasus penggunaan individu tidak relevan. Jika itu JavaScript kromulen, itu harus diekspresikan dalam definisi TS.

tetapi pemahaman saya adalah bahwa ada masalah halus dengan cara fitur berinteraksi dengan sistem tipe lainnya

Lebih seperti "refactors bagaimana tanda tangan indeks secara internal bekerja sepenuhnya sehingga merupakan perubahan besar yang menakutkan dan menimbulkan pertanyaan cromulet tentang bagaimana tanda tangan indeks atau harus berbeda dari jenis yang dipetakan yang tidak menggunakan variabel template" tepatnya.

Ini sebagian besar mengarah pada diskusi tentang bagaimana kita gagal mengenali tipe tertutup vs tipe terbuka. Dalam konteks ini, tipe "tertutup" akan menjadi tipe dengan sekumpulan kunci terbatas yang nilainya tidak dapat diperpanjang. Kunci jenis yang tepat, jika Anda mau. Sementara itu, tipe "terbuka" dalam konteks ini adalah tipe yang, ketika diberi subtipe, terbuka untuk memiliki lebih banyak kunci yang ditambahkan (yang, di bawah aturan subtipe kami saat ini, terkadang semua tipe semacam itu, kecuali tipe dengan tanda tangan indeks yang sangat eksplisit) . Tanda tangan indeks menyiratkan membuat tipe terbuka, sementara tipe yang dipetakan sebagian besar terkait seolah-olah mereka beroperasi di atas tipe tertutup. _Biasanya_ ini bekerja cukup baik karena sebagian besar kode, dalam praktiknya, ditulis dengan struktur yang kompatibel dengan tipe objek tertutup. Inilah mengapa flow (yang memiliki sintaks eksplisit untuk tipe objek tertutup vs terbuka) default untuk tipe objek tertutup. Ini muncul dengan kunci indeks generik; Jika saya memiliki T extends string , karena T dipakai tipe yang lebih luas dan lebih luas (dari "a" hingga "a" | "b" hingga string ), objek diproduksi lebih dan lebih terspesialisasi, sampai kami menukar dari "a" | "b" | ... (every other possible string) menjadi string itu sendiri. Setelah itu terjadi, tiba-tiba jenisnya sangat terbuka, dan sementara setiap properti berpotensi ada untuk diakses, itu menjadi legal untuk, misalnya, menugaskan objek kosong padanya. Itulah yang terjadi secara struktural, tetapi ketika kita menghubungkan generik dalam tipe yang dipetakan, kita mengabaikannya - batasan string pada kunci tipe yang dipetakan generik pada dasarnya terkait seolah-olah itu membuat semua kemungkinan kunci ada. Ini secara logis mengikuti dari tampilan berbasis varian sederhana dari jenis kunci, tetapi hanya benar jika kunci berasal dari jenis _closed_ (yang, tentu saja, jenis dengan tanda tangan indeks tidak pernah benar-benar ditutup!). Jadi, jika kita ingin kompatibel ke belakang, kita _tidak dapat memperlakukan {[x: T]: U} sama dengan {[_ in T]: U} , kecuali, tentu saja, kita ingin, karena dalam kasus non-umum {[_ in T]: U} menjadi {[x: T]: U} , sesuaikan cara kami menangani varian kunci jenis yang dipetakan untuk memperhitungkan tipe "edge" terbuka dengan benar, yang merupakan perubahan menarik dalam haknya sendiri yang dapat menyebabkan konsekuensi ekosistem.

Cukup banyak: Karena ini menyatukan tipe yang dipetakan dan tanda tangan indeks, ini menimbulkan banyak pertanyaan tentang bagaimana kami menangani keduanya yang kami belum memiliki jawaban yang memuaskan atau konklusif.

Kasus penggunaan individu tidak relevan.

Ini adalah, dengan sopan, kegilaan murni. Bagaimana kita tahu apakah kita menambahkan fitur dengan perilaku yang diinginkan orang tanpa menggunakan kasus penggunaan untuk menilai perilaku itu atau tidak?

Kami tidak berusaha menjadi sulit di sini dengan mengajukan pertanyaan-pertanyaan ini; kami benar-benar mencoba memastikan bahwa kami menerapkan hal-hal yang diminta orang. Akan sangat memalukan jika kami menerapkan sesuatu yang kami pikir "mengindeks dengan simbol", hanya untuk memiliki orang yang sama di utas ini kembali dan mengatakan bahwa kami melakukannya dengan sangat salah karena tidak menangani kasus penggunaan khusus mereka.

Anda meminta kami untuk terbang buta. Tolong jangan! Tolong beritahu kami kemana Anda ingin pesawat itu pergi!

Saya buruk, saya bisa lebih jelas tentang apa yang saya maksud; menurut saya orang merasa mereka harus membenarkan kasus penggunaan kode mereka yang sebenarnya, daripada keinginan mereka untuk menjelaskannya secara lebih akurat melalui TS

Jadi jika saya memahaminya dengan benar, ini terutama tentang masalah berikut:

const sym = Symbol();
interface Foo
{
    [sym]: number;
    [s: symbol]: string; // just imagine this would be allowed
}

Sekarang compiler Typecript akan melihat ini sebagai konflik, karena Foo[sym] memiliki tipe ambivalen. Kami sudah memiliki masalah yang sama dengan string.

interface Foo
{
    ['str']: number; // <-- compiler error: not assignable to string index type 'string'
    [s: string]: string;
}

Cara ini ditangani dengan indeks string adalah bahwa indeks string tertentu tidak diperbolehkan, jika ada spesifikasi umum untuk kunci string dan tipenya tidak kompatibel.

Saya kira untuk simbol ini akan menjadi masalah yang ada di mana-mana, karena ECMA2015 mendefinisikan simbol standar seperti Symbol.iterator , yang dapat digunakan pada objek apa pun dan dengan demikian harus memiliki pengetikan default. Yang anehnya ternyata tidak mereka miliki. Setidaknya taman bermain tidak mengizinkan saya menjalankan contoh Symbol.iterator dari MDN .

Dengan asumsi itu direncanakan untuk menambahkan pengetikan simbol yang telah ditentukan, itu akan selalu menyebabkan definisi umum [s: symbol]: SomeType menjadi tidak valid, karena indeks simbol yang telah ditentukan sudah memiliki jenis yang tidak kompatibel sehingga tidak dapat ada jenis umum yang umum atau mungkin perlu jadilah tipe function , karena kebanyakan (/ semua?) kunci simbol yang telah ditentukan sebelumnya adalah tipe function .

Masalah dengan campuran jenis indeks umum dan khusus adalah penentuan jenis saat objek diindeks dengan nilai yang tidak diketahui pada waktu kompilasi. Bayangkan contoh saya di atas dengan indeks string akan valid, maka berikut ini mungkin:

const foo: Foo = {str: 42, a: 'one', b: 'two'};
const input: string = getUserInput();
const value = foo[input];

Masalah yang sama akan berlaku untuk kunci simbol. Tidaklah mungkin untuk menentukan tipe yang tepat dari value pada waktu kompilasi. Jika pengguna memasukkan 'str' , itu akan menjadi number , jika tidak maka akan menjadi string (setidaknya Ketik akan mengharapkannya menjadi string , sedangkan itu kemungkinan besar bisa menjadi undefined ). Apakah ini alasan kami tidak memiliki fitur ini? Seseorang dapat mengatasinya dengan memberikan value tipe gabungan yang berisi semua tipe yang mungkin dari definisi (dalam hal ini number | string ).

@Neonit Yah, itu bukan masalah yang menghentikan kemajuan implementasi, tapi itulah salah satu masalah yang saya coba tunjukkan - tergantung pada apa yang Anda coba lakukan, pengindeks simbol mungkin bukan jawabannya.

Jika fitur ini diimplementasikan, simbol bawaan ECMAScript tidak akan selalu merusak segalanya karena tidak semua jenis menggunakan simbol tersebut; tapi jenis yang tidak mendefinisikan properti dengan simbol terkenal (atau simbol yang Anda sendiri mendefinisikan) kemungkinan akan terbatas pada indeks tanda tangan yang kurang berguna untuk simbol.

Itu benar-benar hal yang perlu diingat - kasus penggunaan "Saya ingin menggunakan ini sebagai peta" dan "Saya ingin menggunakan simbol untuk mengimplementasikan protokol" tidak kompatibel dari perspektif sistem tipe. Jadi jika Anda memiliki pemikiran seperti itu, maka tanda tangan indeks simbol mungkin tidak membantu Anda, dan Anda mungkin dilayani dengan lebih baik melalui properti atau peta simbol eksplisit.

Bagaimana dengan sesuatu seperti tipe UserSymbol yang hanya symbol minus simbol bawaan? Sifat simbol memastikan tidak akan pernah ada tabrakan yang tidak disengaja .

Sunting: Berpikir tentang ini, simbol yang lebih terkenal hanyalah penjaga yang kebetulan diimplementasikan menggunakan Symbol . Kecuali tujuannya adalah serialisasi objek atau introspeksi, kode mungkin harus memperlakukan sentinel ini berbeda dari simbol lain, karena mereka memiliki arti khusus untuk bahasa tersebut. Menghapusnya dari tipe symbol kemungkinan besar akan membuat (sebagian besar) kode yang menggunakan simbol 'umum' lebih aman.

@RyanCavanaugh inilah rencana penerbangan saya.

Saya memiliki sistem di mana saya menggunakan simbol seperti ini untuk properti.

const X = Symbol.for(":ns/name")

const txMap = {
  [X]: "fly away with me!"
}

transact(txMap) // what's the index signature here?

Dalam hal ini saya ingin txMap sesuai dengan tipe tanda tangan transact . Tetapi setahu saya, saya tidak bisa mengungkapkannya hari ini. Dalam kasus saya, transact adalah bagian dari perpustakaan yang tidak tahu apa yang diharapkan. Saya melakukan sesuatu seperti ini untuk properti.

// please forgive my tardiness but in essence this is how I'm typing "TxMap" for objects
type TxMapNs = { [ns: string]: TxMapLocal }
type TxMapLocal = { [name: string]: string | TxMapNs } // leaf or non leaf

Saya dapat menghasilkan set tipe yang sesuai dengan transact dari skema dan menggunakannya. Untuk itu saya akan melakukan sesuatu seperti ini dan mengandalkan penggabungan deklarasi.

interface TxMap = {
  [DB_IDENT]: symbol // leaf
  [DB_VALUE_TYPE]?: TxMap // not leaf
  [DB_CARDINALITY]?: TxMap
}

Tetapi alangkah baiknya jika saya setidaknya dapat kembali ke tanda tangan indeks untuk simbol, saya hanya mengharapkan transact untuk diberikan objek JavaScript biasa, saya juga hanya menggunakan simbol dari registri simbol global dalam kasus ini. Saya tidak menggunakan simbol pribadi.


Saya harus menambahkan bahwa ini agak menyebalkan.

const x = Symbol.for(":x");
const y = Symbol.for(":x");

type X = { [x]: string };
type Y = { [y]: string };

const a: X = { [x]: "foo" };
const b: Y = { [x]: "foo" }; // not legal
const c: X = { [y]: "foo" }; // not legal
const d: Y = { [y]: "foo" };

Akan sangat luar biasa jika TypeScript dapat memahami bahwa simbol yang dibuat melalui fungsi Symbol.for sebenarnya sama.


Ini juga sangat menyebalkan.

function keyword(ns: string, name: string): unique symbol { // not possible, why?
  return Symbol.for(":" + ns + "/" + name)
}

const x: unique symbol = keyword("db", "id") // not possible, why?

type X = {
  [x]: string // not possible, why?
}

Fungsi utilitas kecil itu membuat saya menerapkan konvensi di atas tabel simbol global saya. namun, saya tidak dapat mengembalikan unique symbol , meskipun itu dibuat melalui fungsi Symbol.for . Karena cara TypeScript melakukan sesuatu, itu memaksa saya untuk melupakan solusi tertentu. Mereka tidak bekerja. Dan saya pikir itu menyedihkan.

Saya telah menemukan kasus penggunaan lain di mana symbol sebagai nilai pengindeksan akan berguna, saat bekerja dengan ES Proxies untuk membuat fungsi pabrik yang membungkus objek dengan proxy.

Ambil contoh ini:

let original = {
    foo: 'a',
    bar: 'b',
    baz: 1
};

function makeProxy<T extends Object>(source: T) {
    return new Proxy(source, {
        get: function (target, prop, receiver) {
            return target[prop];
        }
    });
}

let proxied = makeProxy(original);

Untuk mencocokkan tanda tangan tipe ProxyConstructor , argumen umum harus diperpanjang Object , tetapi itu kemudian menjadi kesalahan karena argumen umum tidak dikunci. Jadi kita bisa memperpanjang tipe signature:

function makeProxy<T extends Object & { [key: string]: any}>(source: T) {

Tapi sekarang ini akan menimbulkan kesalahan karena argumen ke-2 ( prop ) dari get pada ProxyHandler adalah tipe PropertyKey yang kebetulan adalah PropertyKey .

Jadi saya tidak yakin bagaimana melakukan ini dengan TypeScript karena batasan masalah ini.

@aaronpowell Apa masalah yang Anda hadapi? Saya melihat itu berperilaku baik:

let original = {
    foo: 'a',
    bar: 'b',
    baz: 1
};

function makeProxy<T extends Object>(source: T) {
    return new Proxy(source, {
        get: function (target, prop, receiver) {
            return target[prop];
        }
    });
}

let proxied = makeProxy(original);

function assertString(s:string){}
function assertNumber(x:number){}

assertString(proxied.foo); // no problem as string
assertNumber(proxied.baz); // no problem as number
console.log(proxied.foobar); // fails as expected: error TS2339: Property 'foobar' does not exist on type '{ foo: string; bar: string; baz: number; }'.

tsconfig.json:

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    "target": "es2015"
  }

package.json:

{
  "devDependencies": {
    "typescript": "~3.4.5"
  }
}

@beenotung Saya melihat kesalahan di taman bermain:

image

@aaronpowell kesalahan muncul saat Anda mengaktifkan tanda 'ketat' di 'compilerOptions' di tsconfig.json .

Jadi di bawah versi compiler skrip saat ini, Anda harus mematikan mode ketat atau mentransmisikan target ke any ...

Tentu, tetapi pemeran any tidak benar-benar ideal dan menonaktifkan mode ketat hanya melonggarkan batasan dalam keamanan tipe.

Membaca pesan saya membayangkan "solusi" berikutnya mungkin akan "menonaktifkan skrip jenis".

Kita tidak perlu mencari solusi sementara dan juga tidak perlu menjelaskan mengapa kita membutuhkannya.

Ini adalah fitur standar javascript jadi kami membutuhkannya dalam skrip ketikan.

@DanielRosenwasser Kasus penggunaan saya mirip dengan @aaronpowell - tampaknya ada ketidakcocokan dalam antarmuka ProxyHandler dan aturan TypeScript mencegah saya mengetik perangkap penangan proxy dengan benar.

Contoh ringkas yang mendemonstrasikan masalah ini:

const getValue = (target: object, prop: PropertyKey) => target[prop]; // Error

Sejauh yang saya tahu, tidak mungkin untuk membuat jenis apa pun untuk target yang akan menghindari kesalahan namun hanya mengizinkan objek yang dapat diakses secara sah oleh PropertyKey .

Saya seorang pemula TypeScript jadi maafkan saya jika saya kehilangan sesuatu yang jelas.

Kasus penggunaan lain: Saya mencoba memiliki tipe {[tag: symbol]: SomeSpecificType} untuk pemanggil untuk menyediakan peta nilai yang diberi tag dari tipe tertentu dengan cara yang diuntungkan dari kekompakan sintaks literal objek (sambil tetap menghindari benturan nama risiko menggunakan string biasa sebagai tag).

Kasus penggunaan lain: Saya mencoba mengulang semua properti objek, simbol, dan string yang dapat dihitung keduanya. Kode saya saat ini terlihat seperti ini (nama dikaburkan):

type ContextKeyMap = Record<PropertyKey, ContextKeyValue>

function setFromObject(context: Context, object: ContextKeyMap) {
    for (const key in object) {
        if (hasOwn.call(object, key)) context.setKey(key, object[key])
    }

    for (const symbol of Object.getOwnPropertySymbols(object)) {
        if (propertyIsEnumerable.call(object, symbol)) {
            context.setKey(symbol, object[symbol as unknown as string])
        }
    }
}

Saya sangat ingin bisa melakukan ini saja:

type ContextKeyMap = Record<PropertyKey, ContextKeyValue>

function setFromObject(context: Context, object: ContextKeyMap) {
    for (const key in object) {
        if (hasOwn.call(object, key)) context.setKey(key, object[key])
    }

    for (const symbol of Object.getOwnPropertySymbols(object)) {
        if (propertyIsEnumerable.call(object, symbol)) {
            context.setKey(symbol, object[symbol])
        }
    }
}

Saya juga memiliki masalah dengan pengindeksan simbol. Kode saya adalah sebagai berikut:

const cacheProp = Symbol.for('[memoize]')

function ensureCache<T extends any>(target: T, reset = false): { [key in keyof T]?: Map<any, any> } {
  if (reset || !target[cacheProp]) {
    Object.defineProperty(target, cacheProp, {
      value: Object.create(null),
      configurable: true,
    })
  }
  return target[cacheProp]
}

Saya mengikuti solusi oleh @aaronpowell dan entah bagaimana berhasil mengatasinya

const cacheProp = Symbol.for('[memoize]') as any

function ensureCache<T extends Object & { [key: string]: any}>(target: T, reset = false): { [key in keyof T]?: Map<any, any> } {
  if (reset || !target[cacheProp]) {
    Object.defineProperty(target, cacheProp, {
      value: Object.create(null),
      configurable: true,
    })
  }

  return target[cacheProp]
}

Mentransmisikan ke any dari symbol memang tidak terlalu bagus.

Sangat dihargai untuk solusi lain.

@ahnpnl Untuk kasus penggunaan itu, Anda akan lebih baik menggunakan WeakMap daripada simbol, dan mesin akan mengoptimalkannya dengan lebih baik - ini tidak mengubah peta tipe target . Anda mungkin masih harus membuangnya, tetapi pemain Anda akan hidup dengan nilai pengembalian.

Solusinya adalah dengan menggunakan fungsi generik untuk menetapkan nilai ...

var theAnswer: symbol = Symbol('secret');
var obj = {} as Record<symbol, number>;
obj[theAnswer] = 42; // Currently error, but should be allowed

Object.assign(obj, {theAnswer: 42}) // allowed

Solusinya adalah dengan menggunakan fungsi generik untuk menetapkan nilai ...

var theAnswer: symbol = Symbol('secret');
var obj = {} as Record<symbol, number>;
obj[theAnswer] = 42; // Currently error, but should be allowed

Object.assign(obj, {theAnswer: 42}) // allowed

Saya tidak setuju. Ketiga garis ini sama satu sama lain:

Object.assign(obj, {theAnswer: 42});
Object.assign(obj, {'theAnswer': 42});
obj['theAnswer'] = 42;

@Bayu_joo
Saya memiliki kasus penggunaan ini, Di tautan taman bermain, saya juga menyelesaikannya dengan menggunakan peta, tetapi lihat, itu jelek.

const system = Symbol('system');
const SomeSytePlugin = Symbol('SomeSytePlugin')

/** I would prefer to have this working in TS */
interface Plugs {
    [key: symbol]: (...args: any) => unknown;
}
const plugins = {
    "user": {} as Plugs,
    [system]: {} as Plugs
}
plugins[system][SomeSytePlugin] = () => console.log('awsome')
plugins[system][SomeSytePlugin](); ....

Tautan Playground

Menggunakan simbol di sini mengesampingkan kemungkinan penimpaan tidak disengaja yang terjadi saat menggunakan string. Itu membuat seluruh sistem lebih kuat dan lebih mudah dirawat.

Jika Anda memiliki solusi alternatif yang bekerja dengan TS dan memiliki keterbacaan yang sama dalam kode, saya mendengarkan.

Adakah pejabat yang menjelaskan masalah ini?

Solusinya adalah dengan menggunakan fungsi generik untuk menetapkan nilai ...

var theAnswer: symbol = Symbol('secret');
var obj = {} as Record<symbol, number>;
obj[theAnswer] = 42; // Currently error, but should be allowed

Object.assign(obj, {theAnswer: 42}) // allowed

Kamu sedang mencari

Objet.assign(obj, { [theAnswer]: 42 });

Namun tidak ada cara untuk membaca kembali x[theAnswer] tanpa pemeran AFAIK lihat komentar dua di bawah

Demi kasih Tuhan, jadikanlah ini prioritas.

Kamu sedang mencari

Objet.assign(obj, { [theAnswer]: 42 });

Namun tidak ada cara untuk membaca kembali x[theAnswer] tanpa AFAIK pemeran

Seperti yang ditunjukkan oleh mellonis dan MingweiSamuel, solusi menggunakan fungsi generik adalah:

var theAnswer: symbol = Symbol("secret");
var obj = {} as Record<symbol, number>;

obj[theAnswer] = 42; // Not allowed, but should be allowed

Object.assign(obj, { [theAnswer]: 42 }); // allowed

function get<T, K extends keyof T>(object: T, key: K): T[K] {
  return object[key];
}

var value = obj[theAnswer]; // Not allowed, but should be allowed

var value = get(obj, theAnswer); // allowed

Lima tahun dan Simbol sebagai indeks masih tidak diperbolehkan

Menemukan solusi untuk kasus ini, ini tidak umum tetapi berfungsi dalam beberapa kasus:

const SYMKEY = Symbol.for('my-key');

interface MyObject {   // Original object interface
  key: string
}

interface MyObjectExtended extends MyObject {
  [SYMKEY]?: string
}

const myObj: MyObject = {
  'key': 'value'
}

// myObj[SYMKEY] = '???' // Not allowed

function getValue(obj: MyObjectExtended, key: keyof MyObjectExtended): any {
  return obj[key];
}

function setValue(obj: MyObjectExtended, key: keyof MyObjectExtended, value: any): void {
  obj[key] = value
}

setValue(myObj, SYMKEY, 'Hello world');
console.log(getValue(myObj, SYMKEY));

@ james4388 Apa perbedaan contoh Anda dengan contoh dari @beenotung?

FYI: https://github.com/microsoft/TypeScript/pull/26797

(Baru saja menemukannya - saya sebenarnya bukan bagian dari tim TS.)

Apakah halaman ini membantu?
0 / 5 - 0 peringkat