Typescript: Saran: izinkan get/set accessor memiliki tipe yang berbeda

Dibuat pada 26 Mar 2015  ·  125Komentar  ·  Sumber: microsoft/TypeScript

Akan sangat bagus jika ada cara untuk mengendurkan batasan saat ini yang membutuhkan pengakses get/set untuk memiliki tipe yang sama. ini akan membantu dalam situasi seperti ini:

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Saat ini, ini sepertinya tidak mungkin, dan saya harus menggunakan sesuatu seperti ini:

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: moment.Moment) {
        assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
    }

    setMyDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Ini jauh dari ideal, dan kodenya akan jauh lebih bersih jika jenis yang berbeda diizinkan.

Terima kasih!

Design Limitation Suggestion Too Complex

Komentar yang paling membantu

Pengambil dan penyetel JavaScript dengan tipe berbeda benar-benar valid dan berfungsi dengan baik dan saya yakin ini adalah keuntungan/tujuan utama fitur ini. Harus memberikan setMyDate() hanya untuk menyenangkan TypeScript merusaknya.

Pikirkan juga tentang perpustakaan JS murni yang akan mengikuti pola ini: .d.ts harus mengekspos serikat pekerja atau any .

Masalahnya adalah bahwa pengakses tidak muncul di .d.ts secara berbeda dari properti normal

Maka batasan ini harus diperbaiki dan masalah ini harus tetap terbuka:

// MyClass.d.ts

// Instead of generating:
declare class MyClass {
  myDate: moment.Moment;
}

// Should generate:
declare class MyClass {
  get myDate(): moment.Moment;
  set myDate(value: Date | moment.Moment);
}

// Or shorter syntax:
declare class MyClass {
  myDate: (get: moment.Moment, set: Date | moment.Moment);
  // and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'
}

Semua 125 komentar

Saya dapat melihat bagaimana ini akan menyenangkan di sini (dan ini telah diminta sebelumnya meskipun saya tidak dapat menemukan masalahnya sekarang) tetapi tidak mungkin apakah utilitas tersebut cukup untuk menjaminnya atau tidak. Masalahnya adalah bahwa pengakses tidak muncul di .d.ts secara berbeda dari properti normal karena mereka tampak sama dari perspektif itu. Artinya tidak ada perbedaan antara pengambil dan penyetel sehingga tidak ada cara untuk a) memerlukan implementasi menggunakan pengakses daripada anggota instance tunggal dan b) menentukan perbedaan tipe antara pengambil dan penyetel.

Terima kasih atas balasan cepatnya, Dan. Saya akan mengikuti cara yang kurang elegan. Terima kasih atas kerja bagusnya!

Pengambil dan penyetel JavaScript dengan tipe berbeda benar-benar valid dan berfungsi dengan baik dan saya yakin ini adalah keuntungan/tujuan utama fitur ini. Harus memberikan setMyDate() hanya untuk menyenangkan TypeScript merusaknya.

Pikirkan juga tentang perpustakaan JS murni yang akan mengikuti pola ini: .d.ts harus mengekspos serikat pekerja atau any .

Masalahnya adalah bahwa pengakses tidak muncul di .d.ts secara berbeda dari properti normal

Maka batasan ini harus diperbaiki dan masalah ini harus tetap terbuka:

// MyClass.d.ts

// Instead of generating:
declare class MyClass {
  myDate: moment.Moment;
}

// Should generate:
declare class MyClass {
  get myDate(): moment.Moment;
  set myDate(value: Date | moment.Moment);
}

// Or shorter syntax:
declare class MyClass {
  myDate: (get: moment.Moment, set: Date | moment.Moment);
  // and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'
}

Saya menyadari ini hanya opini, tetapi menulis setter sedemikian rupa sehingga a.x === y bukan true segera setelah a.x = y; adalah bendera merah raksasa. Bagaimana konsumen perpustakaan seperti itu mengetahui properti mana yang memiliki efek samping ajaib dan mana yang tidak?

Bagaimana [Anda] tahu properti mana yang memiliki efek samping ajaib dan mana yang tidak?

Dalam JavaScript itu bisa menjadi tidak intuitif, dalam TypeScript alat (IDE + compiler) akan mengeluh. Mengapa kita menyukai TypeScript lagi? :)

Pengambil dan penyetel JavaScript dengan tipe berbeda benar-benar valid dan berfungsi dengan baik dan saya yakin ini adalah keuntungan/tujuan utama fitur ini.

Ini menyatakan bahwa JavaScript diketik dengan lemah, jadi TypeScript harus diketik dengan lemah. :-S

Ini menyatakan bahwa JavaScript diketik dengan lemah, jadi TypeScript harus diketik dengan lemah

C# mengizinkannya dan itu tidak membuat bahasa ini diketik dengan lemah C# tidak mengizinkan get/set dari tipe yang berbeda.

C# mengizinkannya dan itu tidak membuat bahasa ini diketik dengan lemah C# tidak mengizinkan get/set dari tipe yang berbeda.

:mengedip:

Tidak ada yang berdebat (sejauh yang saya bisa lihat) bahwa pengakses harus diketik dengan lemah, mereka berpendapat bahwa kita harus memiliki fleksibilitas untuk menentukan jenisnya.

Seringkali diperlukan untuk menyalin objek lama biasa ke instance objek.

    get fields(): Field[] {
      return this._fields;
    }

    set fields(value: any[]) {
      this._fields = value.map(Field.fromJson);
    }

Itu jauh lebih bagus daripada alternatifnya dan memungkinkan setter saya untuk merangkum jenis yang tepat dari setter logika yang dibuat.

@paulwalker pola yang Anda gunakan di sana (setter mengambil any dan pengambil mengembalikan tipe yang lebih spesifik) valid hari ini.

@danquirk Senang mengetahuinya, terima kasih! Sepertinya saya hanya perlu memperbarui kompiler plugin IDE saya untuk ST.

@danquirk Itu sepertinya tidak berfungsi sesuai dengan taman bermain (atau versi 1.6.2):
http://www.typescriptlang.org/Playground#src =%0A%0Aclass%20Foo%20%7B%0A%0A%20%20get%20items()%3A%20string%5B%5D%20%7B%0A %09%20%20pengembalian%20%5B%5D%3B%0A%20%20%7D%0A%20%20%0A%20%20set%20items(nilai%3A%20any)%20%7B%0A% 09%20%20%0A%20%20%7D%0A%7D

Saya baru saja menguji dengan TypeScript@next (Versi 1.8.0-dev.20151102), dan juga memiliki kesalahan.

~$ tsc --version
message TS6029: Version 1.8.0-dev.20151102
~$ cat a.ts
class A {
    get something(): number {return 5;}
    set something(x: any) {}
}

~$ tsc -t es5 a.ts
a.ts(2,2): error TS2380: 'get' and 'set' accessor must have the same type.
a.ts(3,2): error TS2380: 'get' and 'set' accessor must have the same type.

Ironisnya, setelah memperbarui Sublime linter saya, tidak lagi muncul kesalahan, yaitu menggunakan TypeScript 1.7.x. Saya berasumsi ini adalah peningkatan yang akan datang di 1.7+, jadi mungkin 1.8.0 mengalami kemunduran.

Bahkan dengan versi kode visual studio (0,10.5 (Desember 2015)) yang mendukung TypeScript 1.7.5 dan dengan TypeScript 1.7.5 yang diinstal secara global di mesin saya, ini masih menjadi masalah:

image

Jadi versi apa ini yang akan didukung?
Terima kasih

Saya pikir Dan salah. Getter dan setter harus memiliki tipe yang sama.

malu. akan menjadi fitur yang bagus ketika menulis objek halaman untuk digunakan dalam tes busur derajat.

Saya akan bisa menulis tes busur derajat:

po.email = "[email protected]";
expect(po.email).toBe("[email protected]");

... dengan membuat objek halaman:

class PageObject {
    get email(): webdriver.promise.Promise<string> {
        return element(by.model("ctrl.user.email")).getAttribute("value")
    }
    set email(value: any) {
        element(by.model("ctrl.user.email")).clear().sendKeys(value);
    }
}

Bagaimana dengan kode yang mengharuskan penyetel bertipe any ?

Getter akan mengembalikan webdriver.promise.Promise<string> namun setter yang ingin saya tetapkan nilai string .

Mungkin bentuk tes busur derajat yang lebih panjang berikut ini membuatnya lebih jelas:

po.email = "[email protected]";
var currentEmail : webdriver.promise.Promise<string> = po.email;
expect(currentEmail).toBe("[email protected]")

@RyanCavanaugh Dengan pengenalan anotasi null, ini mencegah kode yang memungkinkan memanggil setter dengan null untuk mengaturnya ke beberapa nilai default.

class Style {
    private _width: number = 5;

    // `: number | null` encumbers callers with unnecessary `!`
    get width(): number {
        return this._width;
    }

    // `: number` prevents callers from passing in null
    set width(newWidth: number | null) {
        if (newWidth === null) {
            this._width = 5;
        }
        else {
            this._width = newWidth;
        }
    }
}

Bisakah Anda mempertimbangkan setidaknya mengizinkan jenisnya berbeda dengan adanya | null dan | undefined ?

Ini benar-benar akan menjadi fitur yang bagus.

Jadi apakah ini akan menjadi fitur?

@artyil ditutup dan diberi tag _By Design_ yang menunjukkan bahwa saat ini tidak ada rencana untuk menambahkannya. Jika Anda memiliki kasus penggunaan yang menarik yang menurut Anda mengesampingkan kekhawatiran yang diungkapkan di atas, Anda dapat dengan bebas mengajukan kasus Anda dan umpan balik tambahan dapat membuat tim inti mempertimbangkan kembali posisi mereka.

@kitsonk Saya pikir lebih dari cukup kasus penggunaan yang menarik telah disediakan di komentar di atas. Sementara desain saat ini mengikuti pola umum dari kebanyakan bahasa lain yang diketik dengan pembatasan ini, itu tidak perlu dan terlalu membatasi dalam konteks Javascript. Meskipun dengan desain, desainnya salah.

Saya setuju.

Setelah memikirkan ini lagi. saya pikir masalahnya di sini adalah kompleksitas implementasinya. Saya pribadi menemukan contoh @Arnavion menarik, tetapi sistem tipe hari ini memperlakukan getter/setter sebagai properti biasa. agar ini berfungsi, keduanya harus memiliki nilai yang sama. untuk mendukung tipe baca/tulis akan menjadi perubahan besar, saya tidak yakin utilitas di sini akan sepadan dengan biaya implementasi.

Meskipun saya menyukai TypeScript dan menghargai semua upaya yang dilakukan tim ke dalamnya (sungguh, kalian keren!), Saya harus mengakui bahwa saya kecewa dengan keputusan ini. Ini akan menjadi solusi yang jauh lebih baik daripada alternatif getFoo()/setFoo() atau get foo()/set foo()/setFooEx() .

Hanya daftar singkat masalah:

  • Saat ini kami menganggap properti memiliki tepat satu jenis. Sekarang kita perlu membedakan antara tipe "baca" dan tipe "tulis" dari setiap properti di setiap lokasi
  • Semua hubungan tipe menjadi jauh lebih kompleks karena kita harus mempertimbangkan dua tipe per properti alih-alih satu (apakah { get foo(): string | number; set foo(): boolean } dapat ditetapkan ke { foo: boolean | string | number } , atau sebaliknya?)
  • Saat ini kami berasumsi bahwa sebuah properti, setelah disetel, masih memiliki jenis nilai yang ditetapkan pada baris berikut (yang tampaknya merupakan asumsi yang salah dalam basis kode beberapa orang, wat?). Kami mungkin hanya perlu "mematikan" analisis kontrol aliran apa pun pada properti seperti ini

Sejujurnya, saya benar-benar mencoba untuk menahan diri dari menjadi preskriptif di sini tentang cara menulis kode, tetapi saya benar-benar keberatan dengan gagasan bahwa kode ini

foo.bar = "hello";
console.log(foo.bar);

harus pernah mencetak apa pun selain "hello" dalam bahasa yang mencoba memiliki semantik yang waras. Properti harus memiliki perilaku yang tidak dapat dibedakan dari bidang ke pengamat eksternal.

@RyanCavanaugh sementara saya setuju dengan Anda tentang pendapat yang disuntikkan, saya dapat melihat satu argumen kontra bahwa _might_ sangat TypeScripty ... Melempar sesuatu yang diketik dengan lemah pada setter, tetapi memiliki sesuatu yang selalu diketik dengan kuat kembali, misalnya:

foo.bar = [ '1', 2 ];  // any[]
console.log(foo.bar);  // number[]: [ 1, 2 ]

Meskipun secara pribadi saya cenderung berpikir jika Anda akan menjadi yang ajaib, terbaik untuk membuat metode sehingga pengembang akhir dapat dengan jelas memahami apa yang akan ditekuk, dilipat dan dimutilasi.

Berikut adalah kasus penggunaan kami untuk fitur ini. API kami memperkenalkan kemampuan yang kami beri nama _Autocasting_. Manfaat utama adalah pengalaman pengembang yang disederhanakan yang dapat menghilangkan jumlah kelas yang akan diimpor untuk menetapkan properti yang jenisnya ditentukan dengan baik.

Misalnya, properti warna dapat diekspresikan sebagai instance Color atau sebagai string CSS seperti rgba(r, g, b, a) atau sebagai larik dengan 3 atau 4 angka. Properti masih diketik sebagai instance dari Color , karena ini adalah jenis yang Anda dapatkan saat membaca nilainya.

Beberapa info tentangnya: https://developers.arcgis.com/javascript/latest/guide/autocasting/index.html

Pengguna kami sangat senang mendapatkan fitur itu, mengurangi jumlah impor yang diperlukan, dan sangat memahami bahwa jenisnya mengubah baris setelah penetapan.

Contoh lain untuk masalah ini: https://github.com/gulpjs/vinyl#filebase

file.base = 'd:\\dev';
console.log(file.base); //  'd:\\dev'
file.base = null;
console.log(file.base); //  'd:\\dev\\vinyl' (returns file.cwd)

Jadi penyetelnya adalah string | null | undefined dan pengambilnya hanya string . Tipe apa yang harus kita gunakan dalam definisi tipe untuk perpustakaan ini? Jika kita menggunakan yang pertama, kompiler akan memerlukan pemeriksaan nol yang tidak berguna di mana-mana, Jika kita menggunakan yang terakhir, kita tidak akan dapat menetapkan null ke properti ini.

Saya memiliki contoh lain di mana saya ingin pengambil mengembalikan nullable, tetapi penyetel tidak boleh mengizinkan null sebagai input, bergaya sebagai:

class Memory {
    public location: string;
    public time: Date;
    public company: Person[];
}

class Person
{
    private _bestMemoryEver: Memory | null;

    public get bestMemoryEver(): Memory | null { // Might not have one yet
        return this._bestMemoryEver;
    }

    public set bestMemoryEver(memory: Memory) { // But when he/she gets one, it can only be replaced, not removed
        this._bestMemoryEver = memory;
    }
}

var someDude = new Person();
// ...
var bestMemory: Memory | null = someDude.bestMemoryEver;
//...
someDude.bestMemoryEver = null; // Oh no you don't!

Saya mengerti bahwa mungkin terlalu banyak pekerjaan untuk membangun beberapa logika khusus untuk memungkinkan getter/setter berbeda pada nol, dan itu bukan masalah besar bagi saya, tetapi akan menyenangkan untuk dimiliki.

@Gajah-Kapal Secara filosofis Saya sangat menyukai contoh, ini mewakili sifat manusia yang berubah-ubah dengan baik tetapi saya tidak yakin bahwa itu tidak akan mewakili itu lebih baik dengan mengizinkan null (atau undefined ) yang akan ditetapkan. Bagaimana saya bisa memodelkan kegagalan sinapsis dalam sistem?

@aluanhaddad Mengapa Anda ingin sinapsis gagal? Saya tidak ingin hal-hal buruk semacam itu di alam semesta saya;)

Ada pembaruan tentang ini? Bagaimana dengan memiliki nilai default saat disetel ke nol atau tidak ditentukan?

Saya tidak ingin konsumen harus memeriksa nol terlebih dahulu. Saat ini saya harus menjadikan penyetel sebagai metode terpisah, tetapi saya ingin mereka sama.

Di bawah ini adalah apa yang saya ingin memiliki:

export class TestClass {
  private _prop?: number;

  get prop(): number {
    // return default value if not defined
    this._prop === undefined ? 0 : this._prop;
  }
  set prop(val: number | undefined) {
    this._prop = val;
  }
}

Bagi saya, mendapatkan manfaat dari pemeriksaan non-null datang dengan gotcha, dan ini adalah salah satunya. Dengan pemeriksaan nol yang ketat dimatikan, ini mungkin, tetapi Anda tidak mendapatkan bantuan kompiler untuk mencegah pengecualian referensi nol. Namun, jika Anda menginginkan bantuan kompiler, saya merasa itu harus datang dengan lebih banyak dukungan seperti memiliki definisi terpisah untuk getter dan setter sehubungan dengan setidaknya nullability jika tidak ada yang lain.

Label pada masalah menunjukkan itu adalah batasan desain dan implementasinya akan dianggap terlalu rumit, yang pada dasarnya berarti bahwa jika seseorang memiliki alasan yang sangat kuat mengapa ini harus terjadi, itu tidak akan berhasil.

@mhegazy @kitsonk Saya mungkin bias, tetapi saya merasa ini adalah bug yang muncul untuk pemeriksaan nol ketat pada pola umum, terutama dalam kurung kurawal lain seperti bahasa di mana mereka belum memiliki pemeriksaan nol. Solusinya akan mengharuskan konsumen untuk menggunakan operator bang atau memeriksa bahwa itu sebenarnya tidak pernah nol (yang merupakan titik untuk tidak pernah menjadi nol dengan menggunakan nilai default).

Ini rusak setelah Anda menambahkan pemeriksaan nol yang ketat karena sekarang secara teknis jenisnya berbeda. Saya tidak meminta tipe berbeda yang kuat untuk disetel, tetapi sepertinya persyaratan desain untuk mengaktifkan ini juga akan mengaktifkan tipe berbeda yang kuat juga.

Desain alternatif dapat digunakan untuk tipe yang lemah, sehingga tipe seperti null dan undefined akan menjadi case khusus untuk definisi antarmuka dan file d.ts jika mereka tidak ingin mengaktifkan tipe yang sepenuhnya berbeda.

Menanggapi https://github.com/Microsoft/TypeScript/issues/2521#issuecomment -1996500959
Berikut adalah desain proposal yang seharusnya tidak terlalu rumit untuk diterapkan:

export interface Test {
  undefset prop1: number; // property [get] type number and [set] type number | undefined
  nullset prop2: number; // property [get] type number and [set] type number | null
  nilset prop3: number; // property [get] type number and [set] type number | null | undefined
  undefget prop4: number; // property [get] type number | undefined and [set] type number
  nullget prop5: number; // property [get] type number | null and [set] type number
  nilget prop6: number; // property [get] type number | null | undefined and [set] type number
}

Sepertinya ada beberapa orang yang menonton utas ini yang jauh lebih akrab dengan TypeScript, jadi mungkin seseorang yang masih memperhatikan dapat menjawab pertanyaan terkait. Pada masalah Cesium ini saya menyebutkan batasan tipe get/set yang sedang kita diskusikan di sini, dan orang-orang Cesium mengatakan bahwa pola yang mereka ikuti berasal dari C# -- ini adalah constructor implisit .

Bisakah TypeScript mendukung konstruktor implisit? Artinya, dapatkah saya mengatakan bahwa myThing.foo selalu mengembalikan Bar , dan dapat diberi Bar secara langsung, tetapi juga dapat diberi number yang akan diam-diam dibungkus / digunakan untuk menginisialisasi Bar , sebagai kenyamanan bagi pengembang? Jika mungkin untuk melakukan ini dengan membubuhi keterangan Bar , atau mungkin secara khusus mengatakan bahwa " number dapat ditetapkan ke Bar<number> ", itu akan membahas kasus penggunaan yang dibahas dalam masalah Cesium, dan juga banyak masalah yang diangkat di utas ini.

Jika tidak, apakah saya perlu menyarankan dukungan konstruktor implisit dalam edisi terpisah?

Semakin saya memikirkan/membacanya, semakin saya yakin bahwa "pola konstruktor implisit" akan membutuhkan fitur yang dijelaskan dalam masalah ini. Satu-satunya cara yang mungkin dilakukan di Vanilla JS adalah menggunakan objek get/set accessor, karena itulah satu-satunya saat penugasan nominal ( operator = ) sebenarnya memanggil fungsi yang ditentukan pengguna. (Benar?) Jadi, saya pikir kita benar-benar akan membutuhkan

class MyThing{
  set foo(b: Bar<boolean> | boolean);
  get foo(): Bar<boolean>;
}

yang sepertinya menurut @RyanCavanaugh bukan "semantik waras".

Fakta sederhananya adalah, ada perpustakaan JS yang cukup populer di luar sana yang menggunakan pola ini, dan sepertinya sulit jika bukan tidak mungkin untuk dijelaskan mengingat kendala TS yang ada. Saya harap saya salah.

JavaScript sudah memungkinkan pola yang Anda gambarkan. _challenge_ adalah bahwa sisi baca dan tulis tipe dalam TypeScript, secara desain, dianggap sama. Ada modifikasi bahasa untuk melarang penugasan ( readonly ) tetapi ada beberapa masalah yang meminta konsep _write only_, yang telah dibahas sebagai terlalu rumit untuk nilai dunia nyata yang kecil.

IMO, sejak JavaScript mengizinkan pengakses, orang berpotensi membuat API yang membingungkan dengan mereka. Saya pribadi merasa bingung bahwa sesuatu pada tugas _secara ajaib_ berubah menjadi sesuatu yang lain. Apa pun yang tersirat, terutama konversi tipe, adalah kutukan dari IMO JavaScript. Justru fleksibilitaslah yang menyebabkan masalah. Dengan jenis konversi tersebut, di mana ada _magic_, saya pribadi suka melihat metode dipanggil, di mana sedikit lebih eksplisit kepada konsumen bahwa semacam akan terjadi dan membuat pengambil hanya baca untuk mengambil nilai.

Itu tidak berarti penggunaan dunia nyata tidak ada, itu berpotensi waras dan rasional. Saya kira itu muncul pada kompleksitas pemisahan seluruh sistem tipe menjadi dua, di mana tipe harus dilacak pada kondisi baca dan tulis mereka. Itu sepertinya skenario yang sangat non-sepele.

Saya setuju tentang :sparkles: di sini, tapi saya tidak menentang atau menentang penggunaan pola, saya hanya mencoba mengikuti kode yang sudah menggunakannya dan menggambarkan bentuknya. Ini sudah bekerja dengan cara kerjanya, dan TS tidak memberi saya alat untuk menggambarkannya.

Baik atau buruk, JS telah memberi kami kemampuan untuk mengubah tugas menjadi panggilan fungsi dan orang-orang menggunakannya. Tanpa kemampuan untuk menetapkan tipe yang berbeda ke pasangan set/get , mengapa bahkan memiliki set/get dalam pengetikan ambien sama sekali? Salsa tidak harus tahu bahwa sebuah properti diimplementasikan dengan pengambil dan penyetel jika properti itu akan selalu memperlakukan myThing.foo sebagai variabel anggota dari satu jenis terlepas dari sisi mana dari penugasan itu. (Jelas, kompilasi TypeScript yang sebenarnya adalah hal lain sama sekali.)

@thw0rted

Sepertinya ada beberapa orang yang menonton utas ini yang jauh lebih akrab dengan TypeScript, jadi mungkin seseorang yang masih memperhatikan dapat menjawab pertanyaan terkait. Pada masalah Cesium ini saya menyebutkan batasan tipe get/set yang sedang kita diskusikan di sini, dan orang-orang Cesium mengatakan bahwa pola yang mereka ikuti berasal dari C# -- ini adalah konstruktor implisit.

Operator konversi yang ditentukan pengguna implisit C# melakukan pembuatan kode statis berdasarkan jenis nilai. Jenis TypeScript dihapus dan tidak mempengaruhi perilaku runtime ( async / await edge case untuk Promise polyfiller tidak bertahan).

@kitsonk Saya harus tidak setuju secara umum sehubungan dengan properti. Setelah menghabiskan cukup banyak waktu dengan Java, C++, dan C#, saya sangat menyukai properti (seperti yang terlihat di C#) karena mereka menyediakan abstraksi sintaksis kritis (ini agak benar dalam JavaScript). Mereka memungkinkan antarmuka untuk memisahkan kemampuan baca/tulis dengan cara yang berarti tanpa menggunakan sintaks yang berubah. Saya benci melihat kata kerja terbuang pada operasi sepele seperti getX() ketika mendapatkan X dapat implisit.
API membingungkan IMO, banyak di antaranya melakukan seperti yang Anda katakan menyalahgunakan pengakses, lebih banyak berasal dari terlalu banyak _setter_ melakukan hal-hal ajaib.

Jika saya memiliki readonly, tetapi live view atas beberapa data, katakanlah registry , saya pikir properti readonly sangat mudah dimengerti.

interface Entry {key: string; value: any;}

export function createRegistry() {
  let entries: Entry[] = [];
  return {
    register(key: string, value: any) {
      entries = [...entries, {key, value}];
    },
    get entries() {
      return [...entries];
    }
  }
}

const registry = createRegistry();

registry.register('hello', '您好');
console.log(registry.entries); //[{key: 'hello', value: '您好'}]
registry.register('goodbye', '再见');
console.log(registry.entries); //[{key: 'hello', value: '您好'}, {key: 'goodbye', value: '再见'}]

Maaf untuk garis singgungnya, tetapi saya suka pengakses untuk ini dan berpikir mereka mudah dimengerti tetapi saya bersedia diyakinkan sebaliknya dan keterbacaan adalah perhatian pertama saya.

Ketika TypeScript membatasi JavaScript, itu menjadi gangguan daripada keuntungan. Bukankah TypeScript dimaksudkan untuk membantu pengembang berkomunikasi satu sama lain?

Juga, setter disebut Mutator karena suatu alasan. Jika saya tidak memerlukan konversi apa pun, saya tidak akan menggunakan setter, saya akan mengatur variabel sendiri.

porting proyek javascript ke TypeScript. saya menemui masalah ini..

Ini akan bagus untuk digunakan pada dekorator @Input sudut. Karena nilai yang dilewatkan dari template, menurut saya, akan lebih bersih untuk menangani berbagai jenis objek yang masuk.

Pembaruan: ini tampaknya berhasil untuk saya

import { Component, Input } from '@angular/core';
import { flatMap, isString, isArray, isFalsy } from 'lodash';

@Component({
  selector: 'app-error-notification',
  templateUrl: './error-notification.component.html',
})

export class ErrorNotificationComponent {
  private _errors: Array<string> = [];
  constructor() { }
  /**
   * 'errors' is expected to be an input of either a string or an array of strings
   */
  @Input() set errors(errors: Array<string> | any){
      // Caller just passed in a string instead of an array of strings
      if (isString(errors)) {
        this._errors = [errors];
      }
      // Caller passed in array, assuming it is a string array
      if (isArray(errors)) {
        this._errors = errors;
      }
      // Caller passed in something falsy, which means we should clear error list
      if (isFalsy(errors)) {
        this._errors = [];
      }
      // At this point just set it to whatever might have been passed in and let
      // the user debug when it is broken.
      this._errors = errors;
  }

  get errors() {
    return this._errors;
  }
}

Jadi apakah kita? Saya benar-benar ingin memiliki kemungkinan untuk mengembalikan tipe yang berbeda dengan pengambil daripada dari setter. Sebagai contoh:

class Field {
  private _value: string;

  get value(): string {
    return this._value;
  }

  set value(value: any) {
    this._value = String(value);
  }
}

Inilah yang 99% dari implementasi asli (jika Anda memberikan nomor ke (input as HTMLInputElement).value itu akan selalu mengembalikan string. Sebenarnya get dan set harus dianggap sebagai metode dan harus mengizinkan beberapa:

set value(value: string);
set value(value: number);
set value(value: any) {
  this._value = String(value);
}
  // AND/OR
set value(value: string | number) {
  this._value = String(value);
}

@raysuelzer , ketika Anda mengatakan kode Anda "berfungsi", bukankah ErrorNotificationComponent.errors mengembalikan jenis Array<string> | any ? Itu berarti Anda membutuhkan penjaga tipe setiap kali Anda menggunakannya, ketika Anda tahu itu hanya dapat benar- benar mengembalikan Array<string> .

@lifaon74 , sejauh yang saya tahu tidak ada gerakan tentang masalah ini. Saya pikir kasus yang menarik telah dibuat -- beberapa skenario disajikan, banyak kode JS lama yang tidak dapat dijelaskan dengan benar dalam TypeScript karena ini -- tetapi masalah telah ditutup. Mungkin jika Anda berpikir fakta di lapangan telah berubah, buka yang baru? Saya tidak tahu kebijakan tim tentang vulkanisir argumen lama, tapi saya akan mendukung Anda.

Saya tidak tahu kebijakan tim tentang vulkanisir argumen lama, tapi saya akan mendukung Anda.

Mereka akan mempertimbangkan kembali mata pelajaran yang sebelumnya ditutup. Memberikan 👍 di bagian atas masalah memberikan kepercayaan bahwa itu berarti. Saya percaya bahwa itu umumnya jatuh di bawah kategori "terlalu rumit" karena itu berarti bahwa sistem tipe harus bercabang dua pada setiap membaca dan menulis dan saya menduga perasaan itu adalah upaya dan biaya untuk memasukkannya untuk memuaskan apa yang ada. kasus penggunaan yang valid tetapi agak tidak biasa tidak sepadan.

Perasaan pribadi saya adalah akan _menyenangkan untuk dimiliki_, terutama karena dapat memodelkan kode JavaScript yang ada yang menggunakan pola ini secara efektif.

Saya pribadi akan mempertimbangkan masalah ini diselesaikan jika kami menemukan beberapa solusi yang tidak terlalu berat untuk masalah JS lama. Saya masih semacam TS greenhorn, jadi mungkin solusi saya saat ini (pengecoran paksa atau pelindung tipe yang tidak perlu) bukanlah penggunaan yang optimal dari kemampuan yang ada?

Setuju. setter akan sangat terbatas jika jenisnya harus sama... Harap tingkatkan.

Mencari rekomendasi untuk mencapai ini:

get price() {
    return (this._price as number);
  }

  set price(price) {
    this._price = typeof price === 'string' ? parseFloat(parseFloat(price).toFixed(8)) : parseFloat(price.toFixed(8));
  }

Saya ingin nilai yang disimpan menjadi angka tetapi saya ingin mengonversi dari string ke float di setter jadi saya tidak perlu parseFloat setiap kali saya menetapkan properti.

Saya pikir putusannya adalah bahwa ini adalah pola Javascript yang valid (atau setidaknya diterima dengan cukup baik), yang tidak cocok dengan internal TypeScript. Mungkin jika mengubah internal terlalu besar, kita bisa mendapatkan semacam anotasi yang mengubah cara TS mengkompilasi secara internal? Sesuatu seperti

class Widget {
  get price(): number | /** <strong i="7">@impossible</strong> */ string | undefined { return this._price; }
  set price(val: number|string|undefined){ ... }
}

let w = new Widget();
w.price = 10;
// Annotation processes as "let p:number|undefined = (w.price as number|undefined)"
let p: number|undefined = w.price;

Dengan kata lain, mungkin kita bisa menandai TS sedemikian rupa sehingga pra-prosesor (?) mengonversi semua pembacaan w.price menjadi pemeran eksplisit, sebelum TS ditranspilasikan ke JS. Tentu saja, saya tidak tahu internal bagaimana TS mengelola anotasi, jadi ini mungkin benar-benar sampah, tetapi gagasan umumnya adalah mungkin akan lebih mudah untuk mengubah TS menjadi TS lain, daripada mengubah cara transpiler TS menghasilkan JS.

Bukan berarti fitur ini tidak diperlukan, tetapi inilah cara untuk memotong set . IMO get harus selalu mengembalikan tipe variabel yang sama, jadi ini cukup baik untuk saya.

class Widget {
    get price(): number { return this._price; }
    set price(val){ return this.setPrice(val); } // call another function

    // do processing here
    private setPrice(price: number | string): number {
        let num = Number(price);
        return isNaN(num) ? 0 : num;
    }
}

@iamjoyce widget.price = '123' memancarkan kesalahan kompilasi dalam kode Anda

@iamjoyce => salah karena compiler mengasumsikan val: number di set price(val)

Ya, saya juga di sini karena penggunaan komponen Angular tampaknya ingin kita memiliki tipe pengambil/penyetel yang berbeda.

Angular akan selalu menyuntikkan string jika Anda menetapkan properti komponen (melalui atribut) dari markup (kecuali menggunakan ekspresi pengikatan) terlepas dari jenis properti input. Jadi pasti akan menyenangkan bisa melakukan model seperti ini:

private _someProperty: SomeEnum;
set someProperty(value: SomeEnum | string) {
   this._someProperty = this.coerceSomeEnum(value);
} 
get someProperty(): SomeEnum {
  return this._someProperty;
}

Hari ini, ini berfungsi jika kita meninggalkan | string tetapi untuk memilikinya akan lebih akurat menggambarkan bagaimana Angular akhirnya menggunakan komponen. Dalam hal itu jika Anda mengaksesnya dari kode, jenis properti itu penting, tetapi jika Anda mengaturnya seperti atribut, itu akan memaksa string masuk.

Saya tidak berpikir kami umumnya menginginkan fungsi ini karena kami ingin mendesain API dengan cara ini. Saya setuju, bahwa properti lebih baik jika mereka tidak melakukan efek samping pemaksaan di bawah selimut. Untuk menyiasatinya, akan sangat bagus jika Angular dirancang sedemikian rupa sehingga set atribut versus set properti yang mengikat akan masuk ke titik masuk yang berbeda.

Tetapi jika kita melihat ini secara pragmatis, akan sangat membantu jika TypeScript memungkinkan kita untuk memodelkan interaksi dengan perpustakaan eksternal yang kebetulan menggunakan tipe kita sedemikian rupa sehingga menentang aksioma kesetaraan tipe baca/tulis.

Dalam hal ini, akan sangat bermasalah untuk menggunakan tipe serikat pekerja pada pengambil, karena akan membutuhkan semua jenis penjaga/pernyataan yang mengganggu. Tetapi meninggalkan tipe serikat dari penyetel terasa salah karena salah satu perkakas dapat memutuskan di jalan untuk mulai mencoba memverifikasi bahwa properti yang disetel dari atribut harus dapat ditetapkan dari string.

Jadi dalam kasus ini, sistem tipe tidak cukup ekspresif untuk menangkap bagaimana perpustakaan eksternal diizinkan untuk menggunakan tipe tersebut. Ini tidak _segera_ penting, karena perpustakaan eksternal tidak benar-benar menggunakan tipe ini pada tingkat TypeScript, dengan informasi tipe. Tetapi pada akhirnya mungkin penting, karena perkakas mungkin sangat baik mengkonsumsi informasi jenis.

Satu orang di atas menyebutkan solusi alternatif yang tampaknya agak menjengkelkan, tetapi mungkin bisa berhasil, di mana kita bisa menggunakan tipe serikat pekerja untuk penyetel/pengambil, tetapi memiliki beberapa cara untuk menunjukkan bahwa tipe tertentu dari serikat tidak mungkin untuk pengambil. Jadi, pada dasarnya, pra-dihapus dari pertimbangan dalam aliran kontrol situs situs panggilan pengambil seolah-olah seseorang telah menggunakan pelindung tipe untuk memeriksa mereka tidak ada. Itu tampaknya menjengkelkan dibandingkan dengan mengizinkan serikat superset pada setter dibandingkan dengan serikat subset pada pengambil.

Tapi mungkin itu cara untuk menyelesaikan masalah secara internal. Selama setter hanyalah gabungan superset dari tipe pengambil. Perlakukan jenis pengambil dan penyetel sebagai setara secara internal, tetapi tandai bagian dari jenis serikat pengambil sebagai tidak mungkin. Sehingga analisis aliran kontrol menghilangkannya dari pertimbangan. Apakah itu akan mengatasi kendala desain?

Untuk menguraikan hal di atas. Mungkin itu akan menjadi hal yang berguna, dari sudut pandang ekspresifitas tipe untuk dapat menunjukkan bagian-bagian dari tipe majemuk sebagai hal yang mustahil. Ini tidak akan memengaruhi kesetaraan dengan tipe lain dengan struktur yang sama tetapi tanpa pengubah yang mustahil. Pengubah yang tidak mungkin hanya akan berdampak pada analisis aliran kontrol.

Sebagai saran alternatif, jika ada beberapa sintaks untuk menerapkan penjaga tipe yang ditentukan pengguna ke nilai yang dikembalikan, ini juga sudah cukup. Saya menyadari bahwa ini, dalam keadaan normal, agak konyol, tetapi memiliki ekspresi semacam itu pada nilai pengembalian serta pada argumen akan membantu menyelesaikan kasus Edge ini.

Penjaga tipe yang ditentukan pengguna pada nilai yang dikembalikan, juga memiliki manfaat menjadi sesuatu yang mungkin dapat diekspresikan dalam deklarasi tipe/antarmuka saat dijepit ke properti?

Hanya menambahkan kasus penggunaan lain di sini, mobx-state-tree memungkinkan pengaturan properti dalam beberapa cara berbeda (dari instance dan jenis snapshot), namun hanya akan mengembalikannya dalam satu cara standar (instance) dari get accessor , jadi ini akan sangat berguna jika didukung.

Saya menyiasatinya seperti ini:

interface SomeNestedString {
  foo: string;
}

...

private _foo: SomeNestedString | string;

get foo(): SomeNestedString | string {
  return this._foo;
}

set foo(value: SomeNestedString | string) {
  this._foo = (value as SomeNestedString).foo;
}

Saya tidak berpikir itu berhasil mengatasi masalah yang ada. Anda masih memerlukan pelindung tipe saat menggunakan pengambil, meskipun, pada kenyataannya, pengambil hanya mengembalikan subset dari tipe serikat pekerja.

Saya menggunakan any untuk mengatasi TSLint. Dan kompiler juga tidak mengeluh.

export class FooBar {
  private bar: string;

  get foo (): string | any {
    return this.bar;
  }

  set foo (value: Date | string | any) {
    // Type guarding enables IntelliSense in VS Code
    if (value instanceof Date) {
      this.bar = value.toISOString();
    } else if (typeof value === 'string') {
      this.bar = value;
    } else {
      this.bar = String(value); // Or throw an error
    }
  }
}

@jpidelatorre dengan cara ini Anda kehilangan keamanan tipe.

const fooBar = new FooBar()
const a: number = fooBar.foo // works while it should fail
fooBar.foo = 123 // fails only at runtime, not compile time. It doesnt fail in this particular case with strings and numbers, but it will with something more complex

@keenondrums Itulah mengapa kami ingin pengakses memiliki tipe yang berbeda. Apa yang saya temukan adalah solusi, bukan solusi.

@jpidelatorre solusi saya saat ini adalah menggunakan fungsi lain sebagai penyetel

export class FooBar {
  private bar: string;

  get foo (): string {
    return this.bar;
  }

  setFoo (value: Date | string ) {}
}

@keenondrums Tidak sebagus pengakses, tetapi opsi terbaik.

Bukan model untuk apa yang terjadi dengan properti @input pada komponen Angular, kecuali jika Anda menggunakan fungsi terpisah untuk pengambil.

Yang terlalu jelek.

Saya juga ingin fitur ini, tetapi membutuhkannya untuk bekerja dengan baik di file .d.ts, di mana tidak ada solusi. Saya mencoba mendokumentasikan beberapa kelas yang diekspos melalui Mocha (jembatan Objective-C/Javascript), dan properti instance dari item yang dibungkus diatur seperti ini:

class Foo {
    get bar:()=>number;
    set bar:number;
}

const foo = new Foo();
foo.bar = 3;
foo.bar(); // 3

Saya juga telah melihat banyak kasus di mana API memungkinkan Anda menyetel properti dengan objek yang cocok dengan antarmuka, tetapi pengambil selalu mengembalikan instance kelas nyata:

interface IFoo {
    bar: string;
}

class Foo implements IFoo {
    bar: string;
    toString():string;
}

class Example {
    get foo:Foo;
    set foo:Foo|IFoo;
}

Saya sebagian besar telah melupakan masalah ini, tetapi sebuah pemikiran muncul di benak saya saat membacanya kembali. Saya pikir kami telah menetapkan gagasan bahwa ini layak dilakukan secara abstrak, tetapi terlalu rumit secara teknis untuk menjadi layak. Itu adalah perhitungan tradeoff -- secara teknis tidak mustahil , hanya saja tidak sepadan dengan waktu dan usaha (dan menambahkan kompleksitas kode) untuk diterapkan. Benar?

Apakah fakta bahwa ini membuat tidak mungkin untuk menggambarkan inti DOM API secara akurat mengubah matematika sama sekali? @RyanCavanaugh berkata

Saya benar-benar keberatan dengan gagasan bahwa kode ini foo.bar = "hello"; console.log(foo.bar); harus pernah mencetak apa pun selain "halo" dalam bahasa yang mencoba memiliki semantik yang waras.

Kita dapat berdebat tentang apakah itu harus digunakan dengan cara ini, tetapi DOM selalu mendukung konstruksi seperti el.hidden=1; console.log(el.hidden) // <-- true, not 1 . Ini bukan hanya pola yang digunakan beberapa orang, bukan hanya itu di perpustakaan populer jadi mungkin ide yang baik untuk mendukungnya. Ini adalah prinsip inti tentang bagaimana JS selalu bekerja -- sedikit DWIM dimasukkan ke dalam jiwa bahasa -- dan membuatnya tidak mungkin pada tingkat bahasa melanggar prinsip dasar TS, bahwa itu harus menjadi "superset" dari JS . Ini adalah gelembung jelek yang mencuat dari diagram Venn, dan kita tidak boleh melupakannya.

Inilah sebabnya saya masih menyukai gagasan untuk menjaga setter/getter memiliki tipe yang sama:

number | boolean

Tetapi memperkenalkan semacam typegaurd di mana Anda dapat menunjukkan bahwa, sementara pengambil secara teknis memiliki tipe union yang sama, pada kenyataannya itu hanya akan mengembalikan subset dari tipe dalam union. Tidakkah memperlakukannya sebagai semacam pelindung yang menyempit pada pengambil (hal aliran inferensi?) membuatnya lebih mudah daripada modifikasi pada model tipe? (Dia mengatakan tidak tahu apa-apa tentang internal ...)

Ini bisa, sebagai alternatif, hanya tersirat jika tipe yang digunakan pada pengambil adalah subset ketat dari tipe setter?

Ini bisa, sebagai alternatif, hanya tersirat jika tipe yang digunakan pada pengambil adalah subset ketat dari tipe setter?

👍 !

Saya pikir kita semua bisa setuju bahwa Anda seharusnya tidak bisa mendapatkan tipe yang tidak dapat diatur; Saya hanya ingin beberapa cara untuk secara otomatis mempersempit tipe pada get menjadi tipe yang saya tahu akan selalu seperti itu.

Saya tidak mengerti mengapa ada yang keberatan bahwa itu akan melanggar keamanan tipe dan semuanya. Saya tidak melihatnya melanggar keamanan jenis apa pun jika kami mengizinkan setidaknya penyetel memiliki jenis yang berbeda, karena bagaimanapun kami memiliki pemeriksaan di tempat yang Anda tidak akan mengizinkan jenis lain untuk disetel ke properti.
Mantan:
Katakanlah saya memiliki properti sebagai Arraytetapi dari DB ini akan dikembalikan sebagai string dengan pemisahan koma seperti katakanlah '10,20,40'. Tapi saya tidak bisa memetakannya ke properti mode sekarang, jadi akan sangat membantu jika Anda mengizinkan like

private _employeeIdList : nomor[]

dapatkan EmployeeIDList(): nomor[] {
kembalikan this._employeeIdList ;
}
set EmployeeIDList(_idList: any ) {
if (typeof _idList== 'string') {
this._employeeIdList = _idList.split(',').map(d => Nomor(d));
}
lain jika (typeof _idList== 'objek') {
this._employeeIdList = _idList sebagai nomor[];
}
}

Saya akan dengan mudah memecahkan masalah ini dan itu adalah tipe aman yang sempurna, meskipun Anda mengizinkan tipe yang berbeda di SET tetapi tetap saja itu menghentikan kami untuk menetapkan tipe yang salah ke properti. Jadi menang menang. Saya berharap anggota tim akan meletakkan ego mereka dan mencoba memikirkan masalah yang ditimbulkannya dan memperbaikinya.

Saya ingin berpadu bahwa saya masih berpikir ini sangat penting untuk memodelkan perilaku perpustakaan JS yang ada.

Meskipun semuanya baik dan bagus untuk mengangkat hidung dan mendefinisikan setter yang memaksa tipe ke subset dari tipe yang masuk dan selalu mengembalikan subset itu di pengambil sebagai bau kode, pada kenyataannya, ini adalah pola yang cukup umum di tanah JS .

Saya pikir TypeScript indah karena memungkinkan kita untuk menjadi sangat ekspresif ketika menggambarkan cagaries dari JS API yang ada, bahkan jika mereka tidak memiliki API terstruktur yang ideal. Saya pikir ini adalah pemandangan di mana jika TypeScript cukup ekspresif untuk memungkinkan kami menunjukkan bahwa pengambil akan selalu mengembalikan tipe yang merupakan subset ketat dari tipe pengambil, ini akan menambah nilai yang luar biasa dalam memodelkan API yang ada, bahkan DOM!

Trusted Types adalah proposal API browser baru untuk melawan DOM XSS. Itu sudah diterapkan di Chromium (di belakang bendera). Sebagian besar API memodifikasi penyetel berbagai properti DOM untuk menerima Jenis Tepercaya. Misalnya, .innerHTML menerima TrustedHTML | string sementara selalu mengembalikan string . Untuk mendeskripsikan API dalam TypeScript, kami perlu memperbaiki masalah ini.

Perbedaan dari komentar sebelumnya adalah bahwa ini adalah API browser (dan bukan perpustakaan pengguna) yang tidak dapat diubah dengan mudah. Juga dampak dari mengubah jenis Element.innerHTML menjadi any (yang merupakan satu-satunya solusi yang mungkin saat ini) lebih besar daripada mendeskripsikan perpustakaan pengguna secara tidak tepat.

Apakah ada kemungkinan pull-request ini akan dibuka kembali? Atau ada solusi lain yang saya lewatkan?

Cc: @mprobst , @koto.

Dalam bahasa yang mendukung penyatuan tipe seperti TypeScript, fitur ini alami dan merupakan nilai jual.

@RyanCavanaugh bahkan ketika pengambil dan penyetel memiliki tipe yang sama, itu tidak dijamin bahwa o.x === y setelah o.x = y karena penyetel dapat melakukan sanitasi sebelum menyimpan nilainya.

element.scrollTop = -100;
element.scrollTop; // returns 0

Saya mendukung kekhawatiran oleh @vrana. Perilaku saat ini tidak memungkinkan untuk memodelkan beberapa API yang ada di TypeScript.

Hal ini terutama berlaku untuk API Web, karena banyak jenis penyetel dan pengambilnya berbeda. Dalam praktiknya, pembuat API web melakukan semua jenis pemaksaan, beberapa di antaranya ditentukan secara langsung untuk fitur browser tertentu, tetapi sebagian besar secara implisit melalui IDL . Banyak setter juga mengubah nilainya, lihat misalnya Spesifikasi antarmuka lokasi . Ini bukan kesalahan pengembang tunggal - ini adalah spesifikasi API yang ditentang oleh pengembang web.

Mempersempit tipe pengambil memungkinkan TypeScript untuk mewakili API tersebut, yang sekarang tidak mungkin.

Anda dapat merepresentasikan API tersebut karena benar untuk mengatakan bahwa tipe properti adalah gabungan dari kemungkinan tipe yang dapat Anda berikan kepada penyetel atau dapatkan dari pengambil.

Tidak _efisien_ untuk mendeskripsikan API seperti itu. Anda mengharuskan konsumen untuk menggunakan pelindung tipe di setiap contoh yang mereka gunakan pengambil untuk mempersempit kemungkinan tipe.

Tidak apa-apa jika Anda belum membuatnya secara aksiomatis bahwa Anda akan selalu mengembalikan tipe yang dipersempit dari pengambil, karena banyak API Web bahkan mengunci spesifikasinya.

Tetapi bahkan mengesampingkannya sejenak, dan berbicara tentang API pengguna, kasus penggunaan yang kuat untuk menerima jenis serikat pekerja pada setter adalah yang "berskrip". Kami ingin menerima berbagai jenis diskrit yang dapat kami paksakan dengan baik ke jenis yang sebenarnya kami inginkan.

Mengapa memungkinkan untuk itu? Kemudahan penggunaan.

Itu mungkin tidak masalah untuk API yang Anda kembangkan secara internal untuk penggunaan tim Anda sendiri, tetapi bisa sangat berarti untuk API yang dirancang untuk konsumsi umum dan umum.

Sangat menyenangkan bahwa TypeScript dapat memungkinkan kita untuk secara tepat menggambarkan API yang telah melonggarkan tipe yang dapat diterima untuk beberapa propertinya, tetapi manfaat itu dirusak oleh gesekan di ujung lain di mana pemeriksaan/penjagaan tipe yang berlebihan diperlukan untuk menentukan tipe pengembalian pengambil , yang kami lebih suka _specify_.

Saya berpendapat itu skenario yang berbeda dari kasus ideal yang dikemukakan @RyanCavanaugh . Kasing itu menyiratkan bahwa Anda pengambil dan penyetel harus selalu memiliki tipe gabungan yang sama karena bidang pendukung Anda juga memiliki tipe gabungan yang sama, dan Anda akan selalu bolak-balik nilai itu, dan untuk mengubah jenisnya hanya tidak masuk akal.

Saya pikir kasus itu berpusat di sekitar penggunaan tipe serikat yang lebih ideal, di mana Anda berurusan dengan tipe yang dibangun dan benar-benar memperlakukan tipe serikat itu sebagai unit semantik, yang mungkin seharusnya Anda buat alias.

type urlRep = string | Url;

Sebagian besar hal hanya akan membuatnya tersandung, dan hanya berurusan dengan alat peraga umum, dan dalam beberapa kasus Anda akan memecahkan kotak hitam dengan beberapa jenis pelindung.

Saya berpendapat itu adalah skenario yang sama sekali berbeda dari apa yang kami gambarkan di sini. Apa yang kami gambarkan adalah kenyataan bahwa API penggunaan umum/publik, terutama untuk digunakan dalam bahasa skrip seperti JavaScript sering kali dengan sengaja melonggarkan tipe yang dapat diterima untuk penyetel, karena ada rentang tipe yang akan mereka setujui untuk memaksa tipe ideal, jadi mereka menawarkannya sebagai peningkatan kualitas hidup untuk menerima semua itu.

Hal semacam ini mungkin tampak tidak masuk akal jika Anda adalah produsen dan konsumen API, karena hanya membuat lebih banyak pekerjaan, tetapi bisa sangat masuk akal jika Anda merancang API untuk konsumsi massal.

Dan saya tidak berpikir ada orang yang ingin setter/getter memiliki tipe _disjoint_. Apa yang sedang dibahas di sini adalah bahwa produsen API menegaskan kepada konsumen API bahwa pengambil akan mengembalikan nilai dengan tipe yang merupakan subset ketat dari tipe gabungan setter.

Dan saya tidak berpikir ada orang yang ingin setter/getter memiliki tipe yang terputus-putus.

Hanya untuk memberikan sudut pandang kontra untuk itu. Saya pasti menginginkannya karena memiliki tipe terputus-putus untuk penyetel/pengambil sepenuhnya legal di javascript. Dan saya pikir tujuan utamanya adalah membuat sistem tipe cukup ekspresif untuk mengetik dengan benar apa pun yang legal di javascript (setidaknya pada level .d.ts). Saya mengerti itu bukan praktik yang baik, begitu juga objek global misalnya, atau mengubah prototipe bawaan seperti fungsi dll, namun kami masih dapat mengetiknya dengan baik di file .d.ts dan saya belum pernah mendengar ada yang menyesalinya kita bisa melakukannya (walaupun itu menyebabkan beberapa file definisi tipe yang dirancang dengan buruk muncul pada tipe pasti).

Baru saja mengalami situasi di mana saya membutuhkan ini untuk menjadi fitur untuk memiliki tipe yang benar yang tidak saya kendalikan. Setter harus lebih liberal dan memaksa ke tipe yang lebih konservatif yang dapat secara konsisten dikembalikan oleh pengambil.

Saya sangat berharap fitur ini ada. Saya mem-porting kode JavaScript ke TypeScript, dan sungguh mengecewakan bahwa saya harus memfaktorkan ulang setter jika saya ingin mengetik dengan aman.

Misalnya, saya punya sesuatu seperti ini:

class Vector3 { /* ... */ }

type XYZ = Vector3 | [number, number, number] | {x: number, y: number, z: number}
type PropertyAnimator = (x: number, y: number, z: number, timestamp: number) => XYZ
type XYZSettables =  XYZ | PropertyAnimator

export class Transformable {
        // ...

        set position(newValue: XYZSettables) {
            this._setPropertyXYZ('position', newValue)
        }
        get position(): Vector3 {
            return this._props.position
        }

        // ...
}

Dan seperti yang dapat Anda bayangkan, penggunaannya sangat fleksibel:

const transform = new Transformable

// use an array
transform.position = [20, 30, 40]

// use an object
transform.position = {y: 30, z: 40} // skip `x` this time

// animate manually, a property directly
requestAnimationFrame((time) => {
  transform.position.x = 100 * Math.sin(time * 0.001)
})

// animate manually, with an array, which could be shared across instances
const pos = [10, 20, 30]
requestAnimationFrame((time) => {
  pos[2] = 100 * Math.sin(time * 0.001)
  transform.position = pos
})

// Animate with a property function
transform.position = (x, y, z, time) => [x, y, 100 * Math.sin(time * 0.001)]

// or a simple increment:
transform.position = (x, y, z) => [x, y, ++z]

// etc

// etc

// etc

Ini adalah 4 tahun. Bagaimana MASIH tidak diperbaiki? Fitur ini, seperti yang disebutkan dalam komentar di atas, sangat penting! Setidaknya pertimbangkan untuk membuka kembali masalah ini?

Dan saya sepenuhnya setuju dengan @kitsonk :

@kitsonk Saya pikir lebih dari cukup kasus penggunaan yang menarik telah disediakan di komentar di atas. Sementara desain saat ini mengikuti pola umum dari kebanyakan bahasa lain yang diketik dengan pembatasan ini, itu tidak perlu dan terlalu membatasi dalam konteks Javascript. Meskipun dengan desain, desainnya salah.

TypeScript 3.6 memperkenalkan sintaks untuk mengetik pengakses dalam file deklarasi , sambil mempertahankan batasan bahwa jenis pengambil dan penyetel harus identik.

Toolkit UI kami sangat bergantung pada penyetel transmisi otomatis, di mana penyetel menerima lebih banyak tipe daripada tipe tunggal yang dikembalikan oleh pengambil. Jadi alangkah baiknya jika TS menawarkan cara untuk membuat tipe pola ini aman.

Sepertinya @RyanCavanaugh memberikan bantahan paling konkret dari fitur ini 3 tahun lalu . Saya ingin tahu apakah kasus penggunaan DOM yang baru-baru ini dilaporkan serta ketersediaan sintaksis deklarasi baru memungkinkan keputusan baru?

Ya, begitu saya melihat fitur baru itu, saya langsung memikirkan permintaan fitur ini juga. Saya juga menginginkannya untuk keperluan kerangka kerja UI kami. Ini adalah kasus penggunaan nyata, teman-teman.

TSConf 2019 akan diadakan pada 11 Oktober , berpikir ini akan menjadi retrospeksi yang bagus dalam sesi tanya jawab, jika seseorang mendapat kesempatan

Saya mungkin telah mengatakan ini sebelumnya di utas panjang ini, tetapi saya pikir itu perlu diulang.

IMO sistem tipe ekspresif dalam TypeScript melayani dua tujuan terpisah. Yang pertama memungkinkan Anda untuk menulis kode baru yang lebih aman. Dan jika itu satu-satunya tujuan, mungkin Anda bisa membuat argumen bahwa Anda bisa menghindari kasus penggunaan ini karena kode mungkin lebih aman jika Anda melarang skenario ini (tapi saya pikir kita masih bisa berdebat tentang ini).

Namun tujuan kedua adalah untuk menangkap perilaku perpustakaan sebagaimana adanya dan itu adalah praktik yang cukup umum di DOM dan di perpustakaan JS untuk memaksa secara otomatis di setter, tetapi mengembalikan satu tipe yang diharapkan di pengambil. Untuk memungkinkan kita untuk menangkap ini dalam sistem tipe memungkinkan kita untuk lebih akurat menggambarkan kerangka kerja yang ada sebagaimana adanya .

Setidaknya, saya pikir Design Limitation dapat dihapus di sini, saya rasa itu tidak berlaku lagi.

Tampaknya inilah alasan dari fakta bahwa per #33749 .style.display = ...something nullable... tidak lagi typechecks; yang disajikan sebagai masalah kebenaran nullablity. Itu menjadi sedikit tidak jujur ​​bukan? Sangat menjengkelkan harus mencari tahu perubahan baru yang melanggar ketika mereka salah dilabeli sebagai perbaikan bug (saya mencari masalah aktual yang mungkin disebabkan oleh ini). Secara pribadi, saya merasa jauh lebih tidak mengejutkan bahwa null memiliki perilaku khusus "gunakan default", daripada yang dilakukan oleh string kosong; dan sampai typescipt 3.7 saya lebih suka menggunakan null untuk menangkapnya. Bagaimanapun, akan lebih baik jika anotasi jenis yang sengaja dibuat salah untuk mengatasi batasan ini diberi label dengan jelas seperti itu, untuk menghemat waktu mengatasi masalah pemutakhiran.

Saya juga tertarik untuk menemukan jalan ke depan untuk ini. Bagaimana jika itu hanya diperbolehkan dalam konteks ambient? @RyanCavanaugh akankah itu membantu mengatasi masalah kompleksitas Anda?

Kasus penggunaan saya untuk ini adalah saya memiliki API tempat proxy mengembalikan janji, tetapi operasi yang ditetapkan tidak menetapkan janji. Saya tidak bisa menjelaskan ini di TypeScript sekarang.

let post = await loadPost()
let user = await loadUser()
post.author = user // Proxy handles links these two objects via remote IDs
await save(post)

// Somewhere else in code
let post = await loadPost()
let author = await post.author

Apakah tipe mengoreksi fungsi asli, atau proxy secara umum, TypeScript tampaknya berpendapat bahwa jenis fitur tersebut adalah sebuah kesalahan.

Mereka benar-benar harus menghapus #6 dan #7 dari daftar ini (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals)

Masalah saya

Saya ingin mendorong item ke array dalam metode set dan mendapatkan seluruh array dalam metode get. Untuk saat ini saya harus menggunakan set myArray(...value: string[]) yang berfungsi dengan baik. Sering mengalami masalah ini... harap pertimbangkan untuk menghapus ini. Info atau peringatan akan berfungsi dengan baik untuk ini.

Contoh (apa yang ingin saya lakukan)

class MyClass {
   _myArray: string[] = [];

   set myArray(value: string) {
      this._myArray.push(value);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

Contoh (apa yang harus saya lakukan)

class MyClass {
   _myArray: string[] = [];

   set myArray(...value: string[]) {
      this._myArray.push(value[0]);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

Salah satu solusi yang saya temukan ketika saya membutuhkan ini adalah mengatur properti menjadi union type . Ini karena waktu saya masuk sebagai string dari API, tetapi saya perlu menetapkan sebagai objek Momen untuk UI saya.

Contoh Saya

class TimeStorage {
    _startDate: string | Moment = ""
    _endDate: string | Moment = ""

    set startDate(date: Moment | string) {
        this._startDate = moment(date).utc()
    }
    get startDate(): Moment | string {
        return moment.utc(this._startDate)
    }

    set endDate(date: Moment) { 
        this._endDate = moment.utc(date)
    }
    get endDate() { 
        return moment.utc(this._endDate)
    }
}

Saya agak terlambat ke pesta tetapi saya sendiri baru-baru ini mengalami masalah ini. Ini adalah dilema yang menarik jadi saya mengutak-atiknya sedikit.

Mari kita asumsikan ini adalah sesuatu yang ingin kita buat, kelas sederhana yang memiliki properti yang menerima string dan angka (untuk tujuan apa pun) tetapi properti itu benar-benar disimpan sebagai string:

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    get bar(): string {
        return this._bar;
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    set bar(value: string | number) {
        this._bar = value.toString();
    }

    public baz: string;
    private _bar: string;
}

Karena ini tidak mungkin dan kami masih menginginkan pengetikan yang kuat, kami memerlukan beberapa kode tambahan. Sekarang mari kita berpura-pura untuk tujuan contoh kita bahwa seseorang telah membuat perpustakaan Properti yang terlihat seperti ini:

/**
 * Abstract base class for properties.
 */
abstract class Property<GetType, SetType> {
    abstract get(): GetType;
    abstract set(value: SetType): void;
}

/**
 * Proxify an object so that it's get and set accessors are proxied to
 * the corresponding Property `get` and `set` calls.
 */
function proxify<T extends object>(obj: T) {
    return new Proxy<any>(obj, {
        get(target, key) {
            const prop = target[key];
            return (prop instanceof Property) ? prop.get() : prop;
        },
        set(target, key, value) {
            const prop = target[key];
            if (prop instanceof Property) {
                prop.set(value);
            } else {
                target[key] = value;
            }
            return true;
        }
    });
}

Dengan menggunakan perpustakaan ini, kita dapat mengimplementasikan properti yang diketik untuk kelas kita seperti ini:

class Bar extends Property<string, string | number> {
    constructor(bar: string | number) {
        super();
        this.set(bar);
    }

    get(): string {
        return this._bar;
    }

    set(value: string | number) {
        this._bar = value.toString();
    }

    private _bar: string;
}

class Foo {
    constructor() {
        this.bar = new Bar('');
        this.baz = '';
    }

    public bar: Bar;
    public baz: string;
}

Dan kemudian, menggunakan kelas ini akan memiliki tipe pengambil dan penyetel aman untuk bar :

const foo = new Foo();

// use property's typed setter
foo.bar.set(42);
foo.baz = 'foobar';

// use property's typed getter
// output: 42 foobar
console.log(foo.bar.get(), foo.baz);

Dan jika Anda perlu menggunakan properti setter atau getter secara lebih dinamis, Anda dapat menggunakan fungsi proxify untuk membungkusnya:

const foo = new Foo();

// use proxified property setters
Object.assign(proxify(foo), { bar: 100, baz: 'hello world' });

// use property's typed getter
// output: 100 hello world
console.log(foo.bar.get(), foo.baz);

// use proxified property getters
// output: {"bar":"100","baz":"hello world"}
console.log(JSON.stringify(proxify(foo)));

Penafian: Jika seseorang ingin mengambil kode ini dan meletakkannya di perpustakaan atau melakukan apa pun yang dia inginkan dengannya, silakan lakukan. Aku terlalu malas.

Hanya beberapa catatan tambahan dan kemudian saya berjanji akan berhenti mengirim spam ke daftar panjang orang ini!

Jika kita menambahkan ini ke perpustakaan imajiner:

/**
 * Create a property with custom `get` and `set` accessors.
 */
function property<GetType, SetType>(property: {
    get: () => GetType,
    set: (value: SetType) => void
}) {
    const obj = { ...property };
    Object.setPrototypeOf(obj, Property.prototype);
    return obj;
}

Kami mendapatkan implementasi yang sedikit lebih dekat dengan kelas asli, dan yang paling penting memiliki akses ke this :

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    public bar = property({
        get: (): string => {
            return this._bar;
        },
        set: (value: string | number) => {
            this._bar = value.toString();
        }
    });

    public baz: string;
    private _bar: string;
}

Ini bukan hal tercantik di dunia tetapi berhasil. Mungkin seseorang dapat memperbaiki ini dan membuat sesuatu yang benar-benar bagus.

Jika tidak ada orang yang membaca utas ini tidak yakin bahwa fitur ini penting, saya memberi Anda peretasan yang sangat buruk yang baru saja diperkenalkan Angular :

Akan ideal untuk mengubah jenis nilai di sini, dari boolean ke boolean|'', agar sesuai dengan kumpulan nilai yang benar-benar diterima oleh penyetel. TypeScript mengharuskan pengambil dan penyetel memiliki tipe yang sama, jadi jika pengambil harus mengembalikan boolean maka penyetel terjebak dengan tipe yang lebih sempit.... Dukungan sudut memeriksa tipe yang lebih luas dan lebih permisif untuk @Input() daripada dideklarasikan untuk field input itu sendiri. Aktifkan ini dengan menambahkan properti statis dengan awalan ngAcceptInputType_ ke kelas komponen:

````
kelas KirimButton {
pribadi _disabled: boolean;

dinonaktifkan(): boolean {
kembalikan this._disabled;
}

set dinonaktifkan (nilai: boolean) {
this._disabled = (nilai === '') || nilai;
}

static ngAcceptInputType_disabled: boolean|'';
}
````

Tolong, tolong perbaiki ini.

Yup, kami benar-benar memiliki keburukan ini di semua tempat untuk mendukung pola yang ingin digunakan Angular. Pembatasan saat ini terlalu berpendirian.

Jika TS ingin digunakan untuk memodelkan seluruh perpustakaan di tanah web, maka ia perlu berusaha untuk tidak terlalu beropini, atau akan gagal untuk memodelkan semua desain.

Pada titik ini, saya yakin tanggung jawab ada pada kontributor untuk mengomunikasikan mengapa masalah ini tidak dibuka kembali? Apakah ada anggota yang berkontribusi yang bahkan sangat menentang hal ini pada saat ini?

Kami sedang membangun perpustakaan DOM untuk NodeJs yang cocok dengan spesifikasi W3C, tetapi masalah TypeScript ini membuatnya tidak mungkin. Perubahan apapun?

Sangat mengejutkan bagi saya bahwa beberapa di dalam tim inti TypeScript menentang fitur yang DIBUTUHKAN untuk membuat ulang salah satu pustaka JavaScript yang paling banyak digunakan di planet ini: DOM.

Tidak ada cara lain untuk a) mengimplementasikan banyak bagian dari spesifikasi DOM W3C sementara b) menggunakan sistem tipe TypeScript (kecuali jika Anda menggunakan any di semua tempat, yang mengalahkan seluruh tujuan TypeScript).

Beranda TypeScriptlang.org saat ini tidak akurat ketika menyatakan:

TypeScript adalah superset yang diketik dari JavaScript yang dikompilasi ke JavaScript biasa.

Ketidakmampuan untuk membuat ulang spesifikasi DOM JavaScript menunjukkan bahwa TypeScript masih merupakan subset dari Javascript BUKAN superset .

Sehubungan dengan penerapan fitur ini, saya setuju dengan @gmurray81 dan banyak orang lain yang berpendapat bahwa tipe pengambil harus menjadi bagian dari tipe serikat setter. Ini memastikan tidak ada pengetikan yang terputus-putus. Pendekatan ini memungkinkan fungsi setter untuk membersihkan input tanpa merusak tipe pengambil (yaitu, terpaksa menggunakan any ).

Inilah yang tidak bisa saya lakukan. Sesuatu yang sederhana yang telah saya lakukan serupa di JS tetapi saya tidak bisa di TS.

Isu 4yo yang BENAR- BENAR bisa diimplementasikan.

export const enum Conns {
  none = 0, d = 1, u = 2, ud = 3,
  r = 4, rd = 5, ru = 6, rud = 7,
  l = 8, ld = 9, lu = 10, lud = 11,
  lr = 12, lrd = 13, lru = 14, lrud = 15,
  total = 16
}

class  Tile {
  public connections = Conns.none;

  get connLeft() { return this.connections & Conns.l; };

  set connLeft(val: boolean) { this.connections = val ? (this.connections | Conns.l) : (this.connections & ~Conns.l); }
}

Berlari ke masalah ini sekarang. Di bawah ini sepertinya solusi yang masuk akal, mengutip @calebjclark :

Sehubungan dengan penerapan fitur ini, saya setuju dengan @gmurray81 dan banyak orang lain yang berpendapat bahwa tipe pengambil harus menjadi bagian dari tipe serikat setter. Ini memastikan tidak ada pengetikan yang terputus-putus. Pendekatan ini memungkinkan fungsi setter untuk membersihkan input tanpa merusak tipe pengambil (yaitu, dipaksa untuk menggunakan apapun).

Untuk getter menjadi subset dari tipe setter, saya pikir itu tidak perlu dibatasi. Sungguh, tipe setter _could_ secara teoritis benar-benar apa saja, selama implementasinya selalu mengembalikan subtipe dari tipe pengambil. Mengharuskan setter menjadi supertype dari tipe getter adalah semacam batasan aneh yang mungkin memenuhi beberapa kasus penggunaan yang disajikan dalam tiket ini, tetapi pasti akan mendapatkan keluhan nanti.

Karena itu, kelas secara implisit juga merupakan antarmuka, dengan getter dan setter pada dasarnya merupakan detail implementasi yang tidak akan muncul di antarmuka. Dalam contoh OP, Anda akan mendapatkan type MyClass = { myDate(): moment.Moment; } . Anda entah bagaimana harus mengekspos tipe pengambil dan penyetel baru ini sebagai bagian dari antarmuka, meskipun saya pribadi tidak melihat mengapa itu diinginkan.

Masalah ini pada dasarnya meminta versi yang lebih terbatas dari kelebihan operator = dan === . (Getter dan setter sudah menjadi operator yang kelebihan beban.) Dan sebagai seseorang yang telah menangani kelebihan operator dalam bahasa lain, saya akan mengatakan bahwa saya merasa mereka harus dihindari dalam banyak kasus. Tentu, seseorang dapat menyarankan beberapa contoh matematis seperti titik kartesius dan kutub, tetapi dalam sebagian besar kasus di mana Anda akan menggunakan TS (termasuk contoh di utas ini), saya berpendapat bahwa membutuhkan operator yang berlebihan mungkin merupakan bau kode. Ini mungkin terlihat seperti membuat API lebih sederhana, tetapi cenderung melakukan yang sebaliknya. Seperti disebutkan sebelumnya, memiliki yang berikut ini belum tentu benar sangat membingungkan dan tidak intuitif.

foo.x = y;
if (foo.x === y) { // this could be false?

(Saya pikir satu-satunya saat saya melihat setter yang digunakan yang tampaknya masuk akal adalah untuk masuk dan untuk menetapkan bendera "kotor" pada objek sehingga sesuatu yang lain dapat mengetahui apakah suatu bidang telah diubah. Tidak satu pun dari ini yang benar-benar mengubah kontrak obyek.)

@MikeyBurkman

[...] tetapi dalam sebagian besar kasus di mana Anda akan menggunakan TS (termasuk contoh di utas ini), saya berpendapat bahwa membutuhkan operator yang berlebihan mungkin merupakan bau kode [...]

 foo.x = y; 
 if (foo.x === y) { // this could be false?

Jadi, hanya memiliki tipe yang cocok tidak mencegah perilaku yang mengejutkan, dan memang el.style.display = ''; //so is this now true: el.style.display === ''? menunjukkan bahwa itu juga tidak teoretis. Bahkan dengan bidang lama yang polos, asumsinya tidak berlaku untuk NaN.

Tetapi yang lebih penting, argumen Anda mengabaikan poin bahwa TS tidak dapat benar-benar berpendapat tentang hal-hal ini karena TS perlu melakukan interop dengan api JS yang ada, termasuk hal-hal besar dan tidak mungkin untuk mengubah hal-hal seperti DOM. Dan karena itu, tidak masalah apakah api itu ideal atau tidak; yang penting adalah TS tidak dapat melakukan interop dengan bersih dengan api yang diberikan . Sekarang, Anda terpaksa mengimplementasikan kembali logika fallback apa pun yang digunakan api secara internal untuk memaksa nilai tipe out-of-getter diteruskan ke setter dan dengan demikian mengetikkan properti sebagai tipe getter, atau mengabaikan pemeriksaan tipe untuk menambahkan pernyataan tipe yang tidak berguna di setiap situs getter dan dengan demikian ketikkan properti sebagai tipe setter. Atau, lebih buruk: lakukan apa pun yang terjadi pada TS untuk memilih anotasi untuk DOM, terlepas dari apakah itu ideal.

Semua pilihan itu buruk. Satu-satunya cara untuk menghindarinya adalah dengan menerima kebutuhan untuk mewakili JS apis setepat mungkin, dan di sini: itu tampaknya mungkin (diakui tanpa sepengetahuan internal TS yang mungkin membuat ini jelas tidak sepele).

itu tampaknya mungkin (diakui tanpa sepengetahuan internal TS yang dapat membuat ini jelas tidak sepele)

Saya menduga beberapa sintaks deklarasi tipe baru yang mereka aktifkan baru-baru ini dapat membuat ini dapat diekspresikan ketika sebelumnya kurang layak, jadi mereka harus benar-benar mempertimbangkan untuk membuka kembali masalah ini... @RyanCavanaugh

Di dunia yang ideal, TS akan melakukan interop dengan semua JS API. Tapi bukan itu masalahnya. Ada banyak idiom JS yang tidak bisa diketik dengan benar di TS. Namun, saya lebih suka mereka fokus memperbaiki hal-hal yang benar-benar mengarah pada idiom pemrograman yang lebih baik, bukan lebih buruk. Meskipun mendukung ini dengan cara yang waras akan menyenangkan, ada selusin hal lain yang saya pribadi lebih suka mereka menghabiskan waktu terbatas mereka terlebih dahulu. Ini jauh di bawah daftar itu.

@MikeyBurkman pertanyaan prioritas valid. Perhatian utama saya adalah keputusan @RyanCavanaugh untuk Menutup masalah ini dan menghapusnya. Mengabaikan semua masalah yang ditunjukkan di utas ini secara langsung bertentangan dengan misi yang dinyatakan Typescript, yaitu menjadi "superset" Javascript (bukan subset).

Ya, saya pasti setuju bahwa masalah ini mungkin tidak boleh ditutup, karena ini adalah sesuatu yang _mungkin_ akhirnya harus diperbaiki. (Meskipun saya benar-benar ragu itu akan terjadi.)

Meskipun mendukung ini dengan cara yang waras akan menyenangkan, ada selusin hal lain yang saya pribadi lebih suka mereka menghabiskan waktu terbatas mereka terlebih dahulu

Saya pikir Anda meremehkan aspek penting dari TypeScript karena seharusnya membuatnya lebih aman untuk menggunakan DOM API yang ada dan JS API yang ada. Itu ada untuk menyimpan lebih dari sekadar kode Anda sendiri, di dalam gelembungnya, aman.

Inilah sudut pandang saya, saya membangun perpustakaan komponen, dan sementara saya tidak perlu dengan sengaja menyebabkan ketidakcocokan antara setter/getter ini ada mengingat pembuat saya. Terkadang tangan saya dipaksa berdasarkan bagaimana perpustakaan saya perlu berinteraksi dengan sistem lain di tempat. Misalnya Angular menetapkan semua properti input pada komponen yang berasal dari markup sebagai string, daripada melakukan pemaksaan jenis apa pun berdasarkan pengetahuan mereka tentang jenis target (setidaknya, terakhir saya periksa). Jadi, apakah Anda menolak menerima string? Apakah Anda membuat semuanya menjadi string, meskipun ini akan membuat tipe Anda buruk untuk digunakan? Atau apakah Anda melakukan apa yang TypeScript ingin Anda lakukan dan menggunakan tipe seperti: string | Warna tetapi membuat menggunakan getter mengerikan. Ini semua adalah opsi yang sangat buruk yang mengurangi keamanan, ketika beberapa ekspresi ekstra dari sistem tipe akan membantu.

Masalahnya adalah, bukan hanya Angular yang menyebabkan masalah ini. Sudut berakhir dalam situasi itu karena mencerminkan banyak skenario di DOM di mana pemaksaan otomatis terjadi pada set properti, tetapi pengambil selalu merupakan tipe tunggal yang diantisipasi.

Bagi saya biasanya masalah ketika Anda ingin membuat pembungkus di sekitar beberapa penyimpanan tujuan umum, yang dapat menyimpan pasangan kunci-nilai dan Anda ingin membatasi pengguna untuk kunci tertentu.

class Store {
  private dict: Map<string, any>;

  get name(): string | null {
    return this.dict.get('name') as string | null;
  }

  set name(value: string) {
    this.dict.set('name', value);
  }
}

Anda ingin memiliki batasan seperti itu: pengguna bisa mendapatkan null , jika nilai sebelumnya tidak disetel, tetapi tidak dapat disetel ke null . Saat ini, Anda tidak dapat melakukannya, karena tipe input penyetel harus menyertakan null .

@fan-tom, kasus penggunaan yang bagus mengapa masalah ini perlu dibuka kembali! Jauhkan mereka datang.

Masalah ini memiliki jumlah suara yang sangat tinggi!

Ini adalah proyek tim TS, sehingga mereka dapat melakukan apa yang mereka inginkan sesuai keinginan mereka. Tetapi jika mereka bertujuan untuk membuat ini berguna mungkin bagi komunitas pengguna eksternal, maka saya berharap tim TS dapat mempertimbangkan jumlah suara komunitas yang tinggi.

Beranda TypeScriptlang.org saat ini tidak akurat ketika menyatakan:

TypeScript adalah superset yang diketik dari JavaScript yang dikompilasi ke JavaScript biasa.

TypeScript adalah _superset_ yang diketik dari _subset_ JavaScript yang dikompilasi ke JavaScript biasa. :senyum:

TypeScript adalah superset yang diketik dari subset JavaScript yang dikompilasi ke JavaScript biasa.

Lebih diplomatis, saya pikir Anda bisa mengatakan itu adalah superset JavaScript yang berpendirian, ketika tim membuat penghilangan opini semacam ini dari pemodelan sistem tipe.

Mereka berkata, "kami tidak akan mencontoh ini, karena sulit bagi kami untuk melakukannya, dan bagaimanapun juga kami pikir Anda tidak boleh melakukannya". Tetapi JavaScript penuh dengan hal-hal yang _tidak boleh_ Anda lakukan, dan umumnya TypeScript tidak akan memblokir Anda dari melakukan hal-hal ini jika Anda memiliki kemauan dan pengetahuan, karena sepertinya strategi umumnya adalah tidak berpendapat tentang apa JavaScript Anda bisa dan tidak bisa hanya menjalankan sebagai TypeScript.

Itulah mengapa sangat aneh untuk menolak memodelkan skenario umum (digunakan dalam DOM!), dengan alasan keyakinan bahwa API tidak boleh melakukan paksaan berbasis penyetel sebagai alasan untuk tidak melakukannya.

Saat ini, ini sepertinya tidak mungkin, dan saya harus menggunakan sesuatu seperti ini:

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: moment.Moment) {
        assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
    }

    setMyDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Yang terbaik yang dapat Anda lakukan adalah menambahkan konstruktor dan memanggil fungsi penyetel khusus di sana, jika itu satu-satunya saat Anda menyetel nilai itu.

constructor(value: Date | moment.Moment) {
    this.setMyDate(value);
}

Masalah saat menetapkan nilai secara langsung masih tetap ada.

Adakah pembaruan tentang ini, setelah lebih dari 5,5 tahun?

@xhliu seperti yang ditunjukkan oleh label, ini adalah batasan desain yang terlalu rumit untuk diatasi, dan oleh karena itu masalah ditutup. Saya tidak akan mengharapkan pembaruan. Lamanya waktu untuk masalah tertutup agak tidak relevan.

Silakan buka kembali. Juga ini perlu ditambahkan pada tingkat antarmuka. Contoh klasik adalah ketika menerapkan Proxy di mana Anda memiliki fleksibilitas penuh atas apa yang dapat Anda baca dan tulis.

@xhliu ...terlalu rumit untuk ditangani...

https://ts-ast-viewer.com/#code/MYewdgzgLgBFCm0DyAjAVjAvDA3gKBkJgDMQQAuGAIhQEMAnKvAXzzwWXQDpSQg

const testObj = {
    foo: "bar"
}

testObj.foo

Simbol foo mengatakan ini:

  foo
    flags: 4
    escapedName:"foo"
    declarations: [
      PropertyAssignment (foo)
    ]
    valueDeclaration: PropertyAssignment (foo)

Ada banyak ruang di sini untuk informasi tambahan, yang merupakan desain hebat atas nama TS. Siapa pun yang merancang ini mungkin melakukannya secara khusus untuk memungkinkan fitur tambahan (bahkan yang besar) di masa mendatang.

Spekulatif dari titik ini:

Jika dimungkinkan untuk mendeklarasikan (untuk contoh kode semu) a accessDelclaration: AccessSpecification (foo) , maka PropertyAccessExpression yang mengetahui simbol foo dan deklarasinya, dapat memeriksa secara kondisional apakah ada "accessDelclaration" dan gunakan pengetikan dari itu sebagai gantinya.

Dengan asumsi ada sintaks untuk menambahkan properti accessDelclaration itu ke simbol "foo", PropertyAccessExpression harus dapat menarik "AccessSpecification" dari simbol yang didapatnya dari ts.createIdentifier("foo") dan menghasilkan tipe yang berbeda.

ts.createPropertyAccess(
  ts.createIdentifier("testObj"),
  ts.createIdentifier("foo")
)

Secara spekulatif, tampaknya bagian tersulit dari tantangan ini kemungkinan adalah jumlah pelepasan sepeda di sekitar sintaks (atau mungkin filosofi perusahaan?), Tetapi dari perspektif implementasi, semua alat harus ada di sana. Satu kondisi akan ditambahkan ke fungsi ts.createPropertyAccess() , dan kelas Deklarasi untuk mewakili kondisi itu dan efeknya harus ditambahkan ke simbol properti objek.

Banyak contoh bagus telah ditulis mengapa ini sangat dibutuhkan (terutama untuk DOM dan Angular).

Saya hanya akan menambahkan bahwa saya terkena ini hari ini ketika memigrasikan kode JS lama ke TS di mana menetapkan string ke window.location tidak berhasil dan saya harus melakukan as any solusi 😟.

Properti hanya-baca Window.location mengembalikan objek Lokasi dengan informasi tentang lokasi dokumen saat ini.

Meskipun Window.location adalah objek Lokasi hanya-baca, Anda juga dapat menetapkan DOMString padanya. Ini berarti bahwa Anda dapat bekerja dengan lokasi seolah-olah itu adalah string dalam banyak kasus: location = ' http://www.example.com ' adalah sinonim dari location.href = ' http://www.example.com ' .
sumber

memigrasikan kode JS lama ke TS di mana menetapkan string ke window.location tidak berhasil dan saya harus melakukan solusi as any

Itu contoh yang bagus.

TS membutuhkan ini. Ini adalah bagian yang sangat normal dari JavaScript.

Saya melihat bahwa masalah saat ini telah ditutup. Tapi seperti yang saya mengerti, fungsi ini tidak terwujud?

@AGluk , masalah ini perlu dibuka kembali.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

MartynasZilinskas picture MartynasZilinskas  ·  3Komentar

bgrieder picture bgrieder  ·  3Komentar

uber5001 picture uber5001  ·  3Komentar

jbondc picture jbondc  ·  3Komentar

CyrusNajmabadi picture CyrusNajmabadi  ·  3Komentar