Typescript: Jenis yang Tepat

Dibuat pada 15 Des 2016  ·  171Komentar  ·  Sumber: microsoft/TypeScript

Ini adalah proposal untuk mengaktifkan sintaks untuk tipe yang tepat. Fitur serupa dapat dilihat di Flow (https://flowtype.org/docs/objects.html#exact-object-types), tetapi saya ingin mengusulkannya sebagai fitur yang digunakan untuk tipe literal dan bukan antarmuka. Sintaks spesifik yang saya usulkan untuk digunakan adalah pipa (yang hampir mencerminkan implementasi Flow, tetapi harus mengelilingi pernyataan tipe), karena ini dikenal sebagai sintaks absolut matematis.

interface User {
  username: string
  email: string
}

const user1: User = { username: 'x', email: 'y', foo: 'z' } //=> Currently errors when `foo` is unknown.
const user2: Exact<User> = { username: 'x', email: 'y', foo: 'z' } //=> Still errors with `foo` unknown.

// Primary use-case is when you're creating a new type from expressions and you'd like the
// language to support you in ensuring no new properties are accidentally being added.
// Especially useful when the assigned together types may come from other parts of the application 
// and the result may be stored somewhere where extra fields are not useful.

const user3: User = Object.assign({ username: 'x' }, { email: 'y', foo: 'z' }) //=> Does not currently error.
const user4: Exact<User> = Object.assign({ username: 'x' }, { email: 'y', foo: 'z' }) //=> Will error as `foo` is unknown.

Perubahan sintaks ini akan menjadi fitur baru dan mempengaruhi file definisi baru yang sedang ditulis jika digunakan sebagai parameter atau tipe terbuka. Sintaks ini dapat dikombinasikan dengan tipe lain yang lebih kompleks.

type Foo = Exact<X> | Exact<Y>

type Bar = Exact<{ username: string }>

function insertIntoDb (user: Exact<User>) {}

Mohon maaf sebelumnya jika ini adalah duplikat, sepertinya saya tidak dapat menemukan kata kunci yang tepat untuk menemukan duplikat fitur ini.

Sunting: Posting ini diperbarui untuk menggunakan proposal sintaksis pilihan yang disebutkan di https://github.com/Microsoft/TypeScript/issues/12936#issuecomment -267272371, yang mencakup penggunaan sintaks yang lebih sederhana dengan tipe generik untuk mengaktifkan penggunaan dalam ekspresi.

Awaiting More Feedback Suggestion

Komentar yang paling membantu

Kami membicarakan hal ini cukup lama. Saya akan mencoba meringkas diskusi.

Pemeriksaan Kelebihan Properti

Jenis yang tepat hanyalah cara untuk mendeteksi properti tambahan. Permintaan untuk tipe yang tepat turun banyak ketika kami awalnya menerapkan pemeriksaan properti berlebih (EPC). EPC mungkin merupakan perubahan terbesar yang pernah kami lakukan, tetapi telah membuahkan hasil; segera kami mendapat bug ketika EPC tidak mendeteksi kelebihan properti.

Untuk sebagian besar di mana orang menginginkan tipe yang tepat, kami lebih suka memperbaikinya dengan membuat EPC lebih pintar. Area utama di sini adalah ketika tipe target adalah tipe serikat pekerja - kami hanya ingin menganggap ini sebagai perbaikan bug (EPC seharusnya berfungsi di sini tetapi belum diterapkan).

Jenis semua-opsional

Terkait dengan EPC adalah masalah semua tipe opsional (yang saya sebut tipe "lemah"). Kemungkinan besar, semua tipe lemah ingin tepatnya. Kita hanya perlu menerapkan deteksi tipe lemah (#7485 / #3842); satu-satunya pemblokir di sini adalah jenis persimpangan yang memerlukan beberapa kerumitan ekstra dalam implementasi.

Tipe siapa yang tepat?

Masalah utama pertama yang kita lihat dengan tipe yang tepat adalah sangat tidak jelas tipe mana yang harus ditandai dengan tepat.

Di salah satu ujung spektrum, Anda memiliki fungsi yang secara harfiah akan mengeluarkan pengecualian (atau melakukan hal-hal buruk) jika diberikan objek dengan kunci sendiri di luar beberapa domain tetap. Ini sedikit dan jarang (saya tidak bisa menyebutkan contoh dari memori). Di tengah, ada fungsi yang diam-diam diabaikan
properti yang tidak diketahui (hampir semuanya). Dan di sisi lain Anda memiliki fungsi yang secara umum beroperasi di semua properti (misalnya Object.keys ).

Jelas fungsi "akan dibuang jika diberikan data tambahan" harus ditandai sebagai menerima tipe yang tepat. Tapi bagaimana dengan tengah? Orang mungkin akan tidak setuju. Point2D / Point3D adalah contoh yang baik - Anda mungkin mengatakan bahwa fungsi magnitude harus memiliki tipe (p: exact Point2D) => number untuk mencegah melewatkan Point3D . Tetapi mengapa saya tidak dapat meneruskan objek { x: 3, y: 14, units: 'meters' } ke fungsi itu? Di sinilah EPC masuk - Anda ingin mendeteksi properti "ekstra" units di lokasi yang pasti akan dibuang, tetapi tidak benar-benar memblokir panggilan yang melibatkan aliasing.

Pelanggaran Asumsi / Masalah Instansiasi

Kami memiliki beberapa prinsip dasar yang akan dibatalkan oleh tipe yang tepat. Misalnya, diasumsikan bahwa tipe T & U selalu dapat ditetapkan ke T , tetapi ini gagal jika T adalah tipe yang tepat. Ini bermasalah karena Anda mungkin memiliki beberapa fungsi umum yang menggunakan prinsip T & U -> T , tetapi menjalankan fungsi dengan T dipakai dengan tipe yang tepat. Jadi tidak mungkin kita bisa membuat suara ini (benar-benar tidak OK untuk kesalahan pada instantiasi) - belum tentu pemblokir, tetapi membingungkan untuk memiliki fungsi generik lebih permisif daripada versi yang dibuat secara manual itu sendiri!

Diasumsikan juga bahwa T selalu dapat ditetapkan ke T | U , tetapi tidak jelas bagaimana menerapkan aturan ini jika U adalah tipe yang tepat. Apakah { s: "hello", n: 3 } dapat ditetapkan ke { s: string } | Exact<{ n: number }> ? "Ya" sepertinya jawaban yang salah karena siapa pun yang mencari n dan menemukannya tidak akan senang melihat s , tetapi "Tidak" juga tampaknya salah karena kita telah melanggar T -> T | U dasar

Varia

Apa arti dari function f<T extends Exact<{ n: number }>(p: T) ? :bingung:

Seringkali tipe yang tepat diinginkan di mana yang sebenarnya Anda inginkan adalah persatuan "terputus-otomatis". Dengan kata lain, Anda mungkin memiliki API yang dapat menerima { type: "name", firstName: "bob", lastName: "bobson" } atau { type: "age", years: 32 } tetapi tidak ingin menerima { type: "age", years: 32, firstName: 'bob" } karena sesuatu yang tidak terduga akan terjadi. Jenis "benar" bisa dibilang { type: "name", firstName: string, lastName: string, age: undefined } | { type: "age", years: number, firstName: undefined, lastName: undefined } tapi astaga bagus yang menjengkelkan untuk mengetik. Kami berpotensi memikirkan gula untuk membuat jenis seperti ini.

Ringkasan: Kasus Penggunaan Diperlukan

Diagnosis harapan kami adalah bahwa ini, di luar sedikit API yang benar-benar tertutup, merupakan solusi Masalah XY . Sedapat mungkin kita harus menggunakan EPC untuk mendeteksi properti "buruk". Jadi, jika Anda memiliki masalah dan menurut Anda tipe yang tepat adalah solusi yang tepat, jelaskan masalah aslinya di sini sehingga kami dapat menyusun katalog pola dan melihat apakah ada solusi lain yang tidak terlalu mengganggu/membingungkan.

Semua 171 komentar

Saya akan menyarankan sintaks dapat diperdebatkan di sini. Sejak TypeScript sekarang memungkinkan pipa terkemuka untuk tipe serikat pekerja.

class B {}

type A = | number | 
B

Kompilasi sekarang dan setara dengan type A = number | B , berkat penyisipan titik koma otomatis.

Saya pikir ini mungkin tidak saya harapkan jika tipe yang tepat diperkenalkan.

Tidak yakin apakah realted tetapi FYI https://github.com/Microsoft/TypeScript/issues/7481

Jika sintaks {| ... |} diadopsi, kami dapat membangun tipe yang dipetakan sehingga Anda dapat menulis

type Exact<T> = {|
    [P in keyof T]: P[T]
|}

dan kemudian Anda bisa menulis Exact<User> .

Ini mungkin hal terakhir yang saya lewatkan dari Flow, dibandingkan dengan TypeScript.

Contoh Object.assign sangat bagus. Saya mengerti mengapa TypeScript berperilaku seperti hari ini, tetapi sebagian besar waktu saya lebih suka memiliki tipe yang tepat.

@HeringtonDarkholme Terima kasih. Masalah awal saya telah menyebutkan itu, tetapi saya menghilangkannya pada akhirnya karena seseorang akan memiliki sintaks yang lebih baik, ternyata mereka melakukannya

@DanielRosenwasser Itu terlihat jauh lebih masuk akal, terima kasih!

@wallverb Saya rasa tidak, meskipun saya juga ingin melihat fitur itu ada

Bagaimana jika saya ingin mengekspresikan gabungan tipe, di mana beberapa di antaranya tepat, dan beberapa tidak? Sintaks yang disarankan akan membuatnya rawan kesalahan dan sulit dibaca, bahkan Jika perhatian ekstra diberikan untuk spasi:

|Type1| | |Type2| | Type3 | |Type4| | Type5 | |Type6|

Bisakah Anda dengan cepat memberi tahu anggota serikat mana yang tidak tepat?

Dan tanpa jarak hati-hati?

|Type1|||Type2||Type3||Type4||Type5||Type6|

(jawaban: Type3 , Type5 )

@rotemdan Lihat jawaban di atas, ada tipe generik Extact sebagai gantinya yang merupakan proposal yang lebih solid daripada milik saya. Saya pikir ini adalah pendekatan yang disukai.

Ada juga kekhawatiran tentang bagaimana tampilannya di petunjuk editor, popup pratinjau, dan pesan kompiler. Ketik alias saat ini hanya "meratakan" ke ekspresi tipe mentah. Alias ​​​​tidak dipertahankan sehingga ekspresi yang tidak dapat dipahami masih akan muncul di editor, kecuali beberapa tindakan khusus diterapkan untuk mengatasi hal itu.

Saya merasa sulit untuk percaya bahwa sintaks ini diterima ke dalam bahasa pemrograman seperti Flow, yang memang memiliki gabungan dengan sintaks yang sama dengan TypeScript. Bagi saya tampaknya tidak bijaksana untuk memperkenalkan sintaks yang cacat yang secara fundamental bertentangan dengan sintaks yang ada dan kemudian berusaha sangat keras untuk "menutupi" itu.

Salah satu alternatif yang menarik (lucu?) adalah menggunakan pengubah seperti only . Saya memiliki draf proposal untuk ini beberapa bulan yang lalu, saya pikir, tetapi saya tidak pernah mengirimkannya:

function test(a: only string, b: only User) {};

Itu adalah sintaks terbaik yang bisa saya temukan saat itu.

_Edit_: just mungkin juga berfungsi?

function test(a: just string, b: just User) {};

_(Sunting: sekarang saya ingat bahwa sintaks awalnya untuk pengubah untuk tipe nominal, tapi saya kira itu tidak terlalu penting.. Kedua konsep tersebut cukup dekat sehingga kata kunci ini mungkin juga berfungsi di sini)_

Saya bertanya-tanya, mungkin kedua kata kunci dapat diperkenalkan untuk menjelaskan dua jenis pencocokan yang sedikit berbeda:

  • just T (artinya: "tepatnya T ") untuk pencocokan struktural yang tepat, seperti yang dijelaskan di sini.
  • only T (artinya: "uniknya T ") untuk pencocokan nominal.

Pencocokan nominal dapat dilihat sebagai versi yang lebih "ketat" dari pencocokan struktural yang tepat. Ini berarti bahwa tidak hanya tipe yang harus identik secara struktural, nilai itu sendiri harus dikaitkan dengan pengidentifikasi tipe yang sama persis seperti yang ditentukan. Ini mungkin atau mungkin tidak mendukung alias tipe, selain antarmuka dan kelas.

Saya pribadi tidak percaya perbedaan halus akan menciptakan banyak kebingungan, meskipun saya merasa terserah pada tim TypeScript untuk memutuskan apakah konsep pengubah nominal seperti only tampaknya sesuai untuk mereka. Saya hanya menyarankan ini sebagai opsi.

_(Sunting: hanya catatan tentang only ketika digunakan dengan kelas: ada ambiguitas di sini tentang apakah itu akan memungkinkan untuk subkelas nominal ketika kelas dasar direferensikan - yang perlu dibahas secara terpisah, saya kira. tingkat yang lebih rendah - hal yang sama dapat dipertimbangkan untuk antarmuka - meskipun saat ini saya tidak merasa itu akan berguna)_

Ini sepertinya semacam jenis pengurangan yang menyamar. Masalah ini mungkin relevan: https://github.com/Microsoft/TypeScript/issues/4183 https://github.com/Microsoft/TypeScript/issues/7993

@ethanresnick Mengapa Anda percaya itu?

Ini akan sangat berguna dalam basis kode yang sedang saya kerjakan sekarang. Jika ini sudah menjadi bagian dari bahasa maka saya tidak akan menghabiskan hari ini untuk melacak kesalahan.

(Mungkin kesalahan lain tetapi bukan kesalahan khusus ini )

Saya tidak suka sintaks pipa yang terinspirasi oleh Flow. Sesuatu seperti kata kunci exact belakang antarmuka akan lebih mudah dibaca.

exact interface Foo {}

@mohsen1 Saya yakin kebanyakan orang akan menggunakan tipe generik Exact dalam posisi ekspresi, jadi seharusnya tidak terlalu menjadi masalah. Namun, saya khawatir dengan proposal seperti itu karena Anda mungkin terlalu cepat membebani bagian kiri kata kunci antarmuka yang sebelumnya hanya diperuntukkan bagi ekspor (konsisten dengan nilai JavaScript - misalnya export const foo = {} ). Ini juga menunjukkan bahwa mungkin kata kunci tersebut juga tersedia untuk tipe (misalnya exact type Foo = {} dan sekarang menjadi export exact interface Foo {} ).

Dengan sintaks {| |} bagaimana cara kerja extends ? apakah interface Bar extends Foo {| |} akan tepat jika Foo tidak tepat?

Saya pikir kata kunci exact memudahkan untuk mengetahui apakah sebuah antarmuka tepat. Itu bisa (harus?) bekerja untuk type juga.

interface Foo {}
type Bar = exact Foo

Sangat membantu untuk hal-hal yang bekerja melalui database atau panggilan jaringan ke database atau SDK seperti AWS SDK yang mengambil objek dengan semua properti opsional karena data tambahan diabaikan secara diam-diam dan dapat menyebabkan bug yang sulit ditemukan :rose:

@mohsen1 Pertanyaan itu tampaknya tidak relevan dengan sintaks, karena pertanyaan yang sama masih ada menggunakan pendekatan kata kunci. Secara pribadi, saya tidak memiliki jawaban yang disukai dan harus bermain dengan harapan yang ada untuk menjawabnya - tetapi reaksi awal saya adalah bahwa tidak masalah apakah Foo tepat atau tidak.

Penggunaan kata kunci exact tampaknya ambigu - Anda mengatakan itu dapat digunakan seperti exact interface Foo {} atau type Foo = exact {} ? Apa artinya exact Foo | Bar ? Menggunakan pendekatan generik dan bekerja dengan pola yang ada berarti tidak diperlukan penemuan ulang atau pembelajaran. Ini hanya interface Foo {||} (ini adalah satu-satunya hal baru di sini), kemudian type Foo = Exact<{}> dan Exact<Foo> | Bar .

Kami membicarakan hal ini cukup lama. Saya akan mencoba meringkas diskusi.

Pemeriksaan Kelebihan Properti

Jenis yang tepat hanyalah cara untuk mendeteksi properti tambahan. Permintaan untuk tipe yang tepat turun banyak ketika kami awalnya menerapkan pemeriksaan properti berlebih (EPC). EPC mungkin merupakan perubahan terbesar yang pernah kami lakukan, tetapi telah membuahkan hasil; segera kami mendapat bug ketika EPC tidak mendeteksi kelebihan properti.

Untuk sebagian besar di mana orang menginginkan tipe yang tepat, kami lebih suka memperbaikinya dengan membuat EPC lebih pintar. Area utama di sini adalah ketika tipe target adalah tipe serikat pekerja - kami hanya ingin menganggap ini sebagai perbaikan bug (EPC seharusnya berfungsi di sini tetapi belum diterapkan).

Jenis semua-opsional

Terkait dengan EPC adalah masalah semua tipe opsional (yang saya sebut tipe "lemah"). Kemungkinan besar, semua tipe lemah ingin tepatnya. Kita hanya perlu menerapkan deteksi tipe lemah (#7485 / #3842); satu-satunya pemblokir di sini adalah jenis persimpangan yang memerlukan beberapa kerumitan ekstra dalam implementasi.

Tipe siapa yang tepat?

Masalah utama pertama yang kita lihat dengan tipe yang tepat adalah sangat tidak jelas tipe mana yang harus ditandai dengan tepat.

Di salah satu ujung spektrum, Anda memiliki fungsi yang secara harfiah akan mengeluarkan pengecualian (atau melakukan hal-hal buruk) jika diberikan objek dengan kunci sendiri di luar beberapa domain tetap. Ini sedikit dan jarang (saya tidak bisa menyebutkan contoh dari memori). Di tengah, ada fungsi yang diam-diam diabaikan
properti yang tidak diketahui (hampir semuanya). Dan di sisi lain Anda memiliki fungsi yang secara umum beroperasi di semua properti (misalnya Object.keys ).

Jelas fungsi "akan dibuang jika diberikan data tambahan" harus ditandai sebagai menerima tipe yang tepat. Tapi bagaimana dengan tengah? Orang mungkin akan tidak setuju. Point2D / Point3D adalah contoh yang baik - Anda mungkin mengatakan bahwa fungsi magnitude harus memiliki tipe (p: exact Point2D) => number untuk mencegah melewatkan Point3D . Tetapi mengapa saya tidak dapat meneruskan objek { x: 3, y: 14, units: 'meters' } ke fungsi itu? Di sinilah EPC masuk - Anda ingin mendeteksi properti "ekstra" units di lokasi yang pasti akan dibuang, tetapi tidak benar-benar memblokir panggilan yang melibatkan aliasing.

Pelanggaran Asumsi / Masalah Instansiasi

Kami memiliki beberapa prinsip dasar yang akan dibatalkan oleh tipe yang tepat. Misalnya, diasumsikan bahwa tipe T & U selalu dapat ditetapkan ke T , tetapi ini gagal jika T adalah tipe yang tepat. Ini bermasalah karena Anda mungkin memiliki beberapa fungsi umum yang menggunakan prinsip T & U -> T , tetapi menjalankan fungsi dengan T dipakai dengan tipe yang tepat. Jadi tidak mungkin kita bisa membuat suara ini (benar-benar tidak OK untuk kesalahan pada instantiasi) - belum tentu pemblokir, tetapi membingungkan untuk memiliki fungsi generik lebih permisif daripada versi yang dibuat secara manual itu sendiri!

Diasumsikan juga bahwa T selalu dapat ditetapkan ke T | U , tetapi tidak jelas bagaimana menerapkan aturan ini jika U adalah tipe yang tepat. Apakah { s: "hello", n: 3 } dapat ditetapkan ke { s: string } | Exact<{ n: number }> ? "Ya" sepertinya jawaban yang salah karena siapa pun yang mencari n dan menemukannya tidak akan senang melihat s , tetapi "Tidak" juga tampaknya salah karena kita telah melanggar T -> T | U dasar

Varia

Apa arti dari function f<T extends Exact<{ n: number }>(p: T) ? :bingung:

Seringkali tipe yang tepat diinginkan di mana yang sebenarnya Anda inginkan adalah persatuan "terputus-otomatis". Dengan kata lain, Anda mungkin memiliki API yang dapat menerima { type: "name", firstName: "bob", lastName: "bobson" } atau { type: "age", years: 32 } tetapi tidak ingin menerima { type: "age", years: 32, firstName: 'bob" } karena sesuatu yang tidak terduga akan terjadi. Jenis "benar" bisa dibilang { type: "name", firstName: string, lastName: string, age: undefined } | { type: "age", years: number, firstName: undefined, lastName: undefined } tapi astaga bagus yang menjengkelkan untuk mengetik. Kami berpotensi memikirkan gula untuk membuat jenis seperti ini.

Ringkasan: Kasus Penggunaan Diperlukan

Diagnosis harapan kami adalah bahwa ini, di luar sedikit API yang benar-benar tertutup, merupakan solusi Masalah XY . Sedapat mungkin kita harus menggunakan EPC untuk mendeteksi properti "buruk". Jadi, jika Anda memiliki masalah dan menurut Anda tipe yang tepat adalah solusi yang tepat, jelaskan masalah aslinya di sini sehingga kami dapat menyusun katalog pola dan melihat apakah ada solusi lain yang tidak terlalu mengganggu/membingungkan.

Tempat utama saya melihat orang terkejut karena tidak memiliki tipe objek yang tepat adalah dalam perilaku Object.keys dan for..in -- mereka selalu menghasilkan tipe string alih-alih 'a'|'b' untuk sesuatu yang diketik { a: any, b: any } .

Seperti yang saya sebutkan di https://github.com/Microsoft/TypeScript/issues/14094 dan Anda jelaskan di bagian Lain-lain, menjengkelkan bahwa {first: string, last: string, fullName: string} sesuai dengan {first: string; last: string} | {fullName: string} .

Misalnya, diasumsikan bahwa tipe T & U selalu dapat ditetapkan ke T, tetapi ini gagal jika T adalah tipe yang tepat

Jika T adalah tipe yang tepat, maka mungkin T & U adalah never (atau T === U ). Benar?

Atau U adalah subset yang tidak tepat dari T

Kasus penggunaan saya yang mengarahkan saya ke saran ini adalah reduksi redux.

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): State {
   return {
       ...state,
       fullName: action.payload // compiles, but it's an programming mistake
   }
}

Seperti yang Anda tunjukkan dalam ringkasan, masalah saya tidak secara langsung bahwa saya memerlukan antarmuka yang tepat, saya membutuhkan operator spread untuk bekerja dengan tepat. Tetapi karena perilaku operator spread diberikan oleh JS, satu-satunya solusi yang muncul di benak saya adalah mendefinisikan tipe pengembalian atau antarmuka tepatnya.

Apakah saya memahami dengan benar bahwa menetapkan nilai T ke Exact<T> akan menjadi kesalahan?

interface Dog {
    name: string;
    isGoodBoy: boolean;
}
let a: Dog = { name: 'Waldo', isGoodBoy: true };
let b: Exact<Dog> = a;

Dalam contoh ini, menyempitkan Dog menjadi Exact<Dog> tidak akan aman, bukan?
Pertimbangkan contoh ini:

interface PossiblyFlyingDog extends Dog {
    canFly: boolean;
}
let c: PossiblyFlyingDog = { ...a, canFly: true };
let d: Dog = c; // this is okay
let e: Exact<Dog> = d; // but this is not

@leonadler Ya, itu idenya. Anda hanya dapat menetapkan Exact<T> ke Exact<T> . Kasus penggunaan langsung saya adalah bahwa fungsi validasi akan menangani tipe Exact (misalnya mengambil muatan permintaan sebagai any dan menghasilkan Exact<T> valid). Exact<T> , bagaimanapun, akan ditugaskan ke T .

@nerumo

Seperti yang Anda tunjukkan dalam ringkasan, masalah saya tidak secara langsung bahwa saya memerlukan antarmuka yang tepat, saya membutuhkan operator spread untuk bekerja dengan tepat. Tetapi karena perilaku operator spread diberikan oleh JS, satu-satunya solusi yang muncul di benak saya adalah mendefinisikan tipe pengembalian atau antarmuka tepatnya.

Saya telah menemukan masalah yang sama dan menemukan solusi ini yang bagi saya merupakan solusi yang cukup elegan :)

export type State = {
  readonly counter: number,
  readonly baseCurrency: string,
};

// BAD
export function badReducer(state: State = initialState, action: Action): State {
  if (action.type === INCREASE_COUNTER) {
    return {
      ...state,
      counterTypoError: state.counter + 1, // OK
    }; // it's a bug! but the compiler will not find it 
  }
}

// GOOD
export function goodReducer(state: State = initialState, action: Action): State {
  let partialState: Partial<State> | undefined;

  if (action.type === INCREASE_COUNTER) {
    partialState = {
      counterTypoError: state.counter + 1, // Error: Object literal may only specify known properties, and 'counterTypoError' does not exist in type 'Partial<State>'. 
    }; // now it's showing a typo error correctly 
  }
  if (action.type === CHANGE_BASE_CURRENCY) {
    partialState = { // Error: Types of property 'baseCurrency' are incompatible. Type 'number' is not assignable to type 'string'.
      baseCurrency: 5,
    }; // type errors also works fine 
  }

  return partialState != null ? { ...state, ...partialState } : state;
}

Anda dapat menemukan lebih banyak di bagian panduan redux saya ini:

Perhatikan bahwa ini dapat diselesaikan di userland menggunakan proposal jenis kendala saya (#13257):

type Exact<T> = [
    case U in U extends T && T extends U: T,
];

Sunting: Sintaks yang diperbarui relatif terhadap proposal

@piotrwitek terima kasih, trik Partial berfungsi dengan baik dan sudah menemukan bug di basis kode saya;) itu sepadan dengan kode boilerplate kecil. Tapi tetap saya setuju dengan @isiahmeadows bahwa itu Tepatakan lebih baik

@piotrwitek menggunakan Parsial seperti itu _hampir_ memecahkan masalah saya, tetapi itu masih memungkinkan properti menjadi tidak terdefinisi bahkan jika antarmuka Negara mengklaim tidak (saya berasumsi strictNullChecks).

Saya berakhir dengan sesuatu yang sedikit lebih kompleks untuk mempertahankan jenis antarmuka:

export function updateWithPartial<S extends object>(current: S, update: Partial<S>): S {
    return Object.assign({}, current, update);
}

export function updateWith<S extends object, K extends keyof S>(current: S, update: {[key in K]: S[key]}): S {
    return Object.assign({}, current, update);
}

interface I {
    foo: string;
    bar: string;
}

const f: I = {foo: "a", bar: "b"}
updateWithPartial(f, {"foo": undefined}).foo.replace("a", "x"); // Compiles, but fails at runtime
updateWith(f, {foo: undefined}).foo.replace("a", "x"); // Does not compile
updateWith(f, {foo: "c"}).foo.replace("a", "x"); // Compiles and works

@asmundg itu benar, solusinya akan menerima undefined, tetapi dari sudut pandang saya ini dapat diterima, karena dalam solusi saya, saya hanya menggunakan pembuat tindakan dengan parameter yang diperlukan untuk muatan, dan ini akan memastikan bahwa tidak ada nilai yang tidak ditentukan yang seharusnya ditugaskan ke properti non-nullable.
Secara praktis saya menggunakan solusi ini untuk beberapa waktu dalam produksi dan masalah ini tidak pernah terjadi, tetapi beri tahu saya kekhawatiran Anda.

export const CHANGE_BASE_CURRENCY = 'CHANGE_BASE_CURRENCY';

export const actionCreators = {
  changeBaseCurrency: (payload: string) => ({
    type: CHANGE_BASE_CURRENCY as typeof CHANGE_BASE_CURRENCY, payload,
  }),
}

store.dispatch(actionCreators.changeBaseCurrency()); // Error: Supplied parameters do not match any signature of call target.
store.dispatch(actionCreators.changeBaseCurrency(undefined)); // Argument of type 'undefined' is not assignable to parameter of type 'string'.
store.dispatch(actionCreators.changeBaseCurrency('USD')); // OK => { type: "CHANGE_BASE_CURRENCY", payload: 'USD' }

DEMO - aktifkan strictNullChecks di opsi

Anda juga dapat membuat muatan yang dapat dibatalkan juga jika perlu, Anda dapat membaca lebih lanjut di panduan saya: https://github.com/piotrwitek/react-redux-typescript-guide#actions

Saat Tipe Istirahat digabungkan, fitur ini dapat dengan mudah dijadikan gula sintaksis di atasnya.

Usul

Logika kesetaraan tipe harus dibuat ketat - hanya tipe dengan properti yang sama atau tipe yang memiliki properti lainnya yang dapat dipakai sedemikian rupa sehingga tipe induknya memiliki properti yang sama yang dianggap cocok. Untuk mempertahankan kompatibilitas mundur, jenis istirahat sintetis ditambahkan ke semua jenis kecuali yang sudah ada. Bendera baru --strictTypes juga ditambahkan, yang menekan penambahan parameter istirahat sintetis.

Persamaan di bawah --strictTypes :

type A = { x: number, y: string };
type B = { x: number, y: string, ...restB: <T>T };
type C = { x: number, y: string, z: boolean, ...restC: <T>T };

declare const a: A;
declare const b: B;
declare const c: C;

a = b; // Error, type B has extra property: "restB"
a = c; // Error, type C has extra properties: "z", "restC"
b = a; // OK, restB inferred as {}
b = c; // OK, restB inferred as { z: boolean, ...restC: <T>T }

c = a; // Error, type A is missing property: "z"
       // restC inferred as {}

c = b; // Error, type B is missing property: "z"
       // restC inferred as restB 

Jika --strictTypes tidak diaktifkan, properti ...rest: <T>T secara otomatis ditambahkan pada tipe A . Dengan cara ini baris a = b; dan a = c; tidak akan error lagi, seperti halnya dengan variabel b pada dua baris berikutnya.

Sepatah Kata tentang Pelanggaran Asumsi

diasumsikan bahwa tipe T & U selalu dapat ditetapkan ke T, tetapi ini gagal jika T adalah tipe yang tepat.

Ya, & memungkinkan logika palsu tetapi begitu juga dengan string & number . Baik string dan number adalah tipe kaku yang berbeda yang tidak dapat berpotongan, namun sistem tipe mengizinkannya. Jenis yang tepat juga kaku, sehingga inkonsistensi masih konsisten. Masalahnya terletak pada operator & - tidak sehat.

Apakah { s: "halo", n: 3 } dapat ditetapkan ke { s: string } | Tepat<{ n: bilangan }>.

Ini dapat diterjemahkan menjadi:

type Test = { s: string, ...rest: <T>T } | { n: number }
const x: Test = { s: "hello", n: 3 }; // OK, s: string; rest inferred as { n: number }

Jadi jawabannya harus "ya". Tidak aman untuk menggabungkan eksak dengan tipe yang tidak eksak, karena tipe yang tidak eksak menggolongkan semua tipe eksak kecuali ada properti diskriminator.

Re: fungsi f<T extends Exact<{ n: number }>(p: T) dalam komentar @RyanCavanaugh di atas, di salah satu perpustakaan saya, saya sangat ingin mengimplementasikan fungsi berikut:

const checkType = <T>() => <U extends Exact<T>>(value: U) => value;

Yaitu fungsi yang mengembalikan parameternya dengan tipe yang sama persis, tetapi pada saat yang sama juga memeriksa apakah tipenya juga sama persis dengan tipe lainnya (T).

Berikut adalah contoh yang sedikit dibuat-buat dengan tiga percobaan saya yang gagal untuk memenuhi kedua persyaratan:

  1. Tidak ada kelebihan properti sehubungan dengan CorrectObject
  2. Ditetapkan ke HasX tanpa menentukan HasX sebagai tipe objek
type AllowedFields = "x" | "y";
type CorrectObject = {[field in AllowedFields]?: number | string};
type HasX = { x: number };

function objectLiteralAssignment() {
  const o: CorrectObject = {
    x: 1,
    y: "y",
    // z: "z" // z is correctly prevented to be defined for o by Excess Properties rules
  };

  const oAsHasX: HasX = o; // error: Types of property 'x' are incompatible.
}

function objectMultipleAssignment() {
  const o = {
    x: 1,
    y: "y",
    z: "z",
  };
  const o2 = o as CorrectObject; // succeeds, but undesirable property z is allowed

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

function genericExtends() {
  const checkType = <T>() => <U extends T>(value: U) => value;
  const o = checkType<CorrectObject>()({
    x: 1,
    y: "y",
    z: "z", // undesirable property z is allowed
  }); // o is inferred to be { x: number; y: string; z: string; }

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

Di sini HasX adalah tipe yang sangat disederhanakan (tipe aktual memetakan o terhadap tipe skema) yang didefinisikan dalam lapisan yang berbeda dari konstanta itu sendiri, jadi saya tidak dapat membuat tipe o menjadi ( CorrectObject & HasX ).

Dengan Jenis Tepat, solusinya adalah:

function exactTypes() {
  const checkType = <T>() => <U extends Exact<T>>(value: U) => value;
  const o = checkType<CorrectObject>()({
    x: 1,
    y: "y",
    // z: "z", // undesirable property z is *not* allowed
  }); // o is inferred to be { x: number; y: string; }

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

@andy-ms

Jika T adalah tipe eksak, maka mungkin T & U tidak pernah (atau T === U). Benar?

Saya pikir T & U seharusnya never hanya jika U terbukti tidak kompatibel dengan T , misalnya jika T adalah Exact<{x: number | string}> dan U adalah {[field: string]: number} , maka T & U seharusnya Exact<{x: number}>

Lihat tanggapan pertama untuk itu:

Atau U adalah subset tak eksak dari T

Saya akan mengatakan, jika U dapat dialihkan ke T, maka T & U === T . Tetapi jika T dan U adalah tipe persis yang berbeda, maka T & U === never .

Dalam contoh Anda, mengapa perlu memiliki fungsi checkType yang tidak melakukan apa-apa? Mengapa tidak memiliki const o: Exact<CorrectObject> = { ... } ?

Karena kehilangan informasi bahwa x pasti ada (opsional di CorrectObject) dan adalah angka (number | string di CorrectObject). Atau mungkin saya salah memahami apa artinya Persis, saya pikir itu hanya akan mencegah properti asing, bukan berarti secara rekursif berarti semua jenis harus persis sama.

Satu lagi pertimbangan dalam mendukung Exact Types dan melawan EPC saat ini adalah refactoring - jika refactoring Extract Variable tersedia, seseorang akan kehilangan EPC kecuali variabel yang diekstrak memperkenalkan anotasi tipe, yang bisa menjadi sangat verbose.

Untuk memperjelas mengapa saya mendukung Exact Types - ini bukan untuk serikat yang didiskriminasi tetapi kesalahan ejaan dan properti asing yang keliru jika jenis costraint tidak dapat ditentukan pada saat yang sama dengan objek literal.

@andy-ms

Saya akan mengatakan, jika U dapat dialihkan ke T, maka T & U === T. Tetapi jika T dan U adalah tipe persis yang berbeda, maka T & U === tidak pernah.

Operator tipe & adalah operator persimpangan, hasilnya adalah himpunan bagian umum dari kedua sisi, yang juga tidak selalu sama. Contoh paling sederhana yang dapat saya pikirkan:

type T = Exact<{ x?: any, y: any }>;
type U = { x: any, y? any };

di sini T & U seharusnya Exact<{ x: any, y: any }> , yang merupakan subset dari T dan U , tetapi T bukan merupakan subset dari U (x hilang) atau U adalah subset dari T (y hilang).

Ini harus bekerja terlepas dari apakah T , U , atau T & U adalah tipe yang tepat.

@magnushiie Anda punya poin bagus -- tipe yang tepat dapat membatasi penugasan dari tipe dengan lebar yang lebih besar, tetapi masih memungkinkan penugasan dari tipe dengan kedalaman yang lebih besar. Jadi Anda bisa memotong Exact<{ x: number | string }> dengan Exact<{ x: string | boolean }> untuk mendapatkan Exact<{ x: string }> . Satu masalah adalah bahwa ini sebenarnya bukan typesafe jika x tidak hanya dapat dibaca -- kami mungkin ingin memperbaiki kesalahan itu untuk jenis yang tepat, karena itu berarti memilih untuk mengikuti perilaku yang lebih ketat.

Tipe yang tepat juga dapat digunakan untuk masalah hubungan argumen tipe ke tanda tangan indeks.

interface T {
    [index: string]: string;
}

interface S {
    a: string;
    b: string;
}

interface P extends S {
    c: number;
}

declare function f(t: T);
declare function f2(): P;
const s: S = f2();

f(s); // Error because an interface can have more fields that is not conforming to an index signature
f({ a: '', b: '' }); // No error because literals is exact by default

Berikut cara hacky untuk memeriksa jenis yang tepat:

// type we'll be asserting as exact:
interface TextOptions {
  alignment: string;
  color?: string;
  padding?: number;
}

// when used as a return type:
function getDefaultOptions(): ExactReturn<typeof returnValue, TextOptions> {
  const returnValue = { colour: 'blue', alignment: 'right', padding: 1 };
  //             ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.
  return returnValue
}

// when used as a type:
function example(a: TextOptions) {}
const someInput = {padding: 2, colour: '', alignment: 'right'}
example(someInput as Exact<typeof someInput, TextOptions>)
  //          ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.

Sayangnya saat ini tidak memungkinkan untuk membuat pernyataan Exact sebagai parameter tipe, jadi itu harus dibuat selama waktu panggilan (yaitu Anda harus mengingatnya).

Berikut adalah utilitas pembantu yang diperlukan untuk membuatnya berfungsi (terima kasih kepada @tycho01 untuk beberapa di antaranya):

type Exact<A, B extends Difference<A, B>> = AssertPassThrough<Difference<A, B>, A, B>
type ExactReturn<A, B extends Difference<A, B>> = B & Exact<A, B>

type AssertPassThrough<Actual, Passthrough, Expected extends Actual> = Passthrough;
type Difference<A, Without> = {
  [P in DiffUnion<keyof A, keyof Without>]: A[P];
}
type DiffUnion<T extends string, U extends string> =
  ({[P in T]: P } &
  { [P in U]: never } &
  { [k: string]: never })[T];

Lihat: Taman bermain .

Bagus! @gcanti ( typelevel-ts ) dan @pelotom ( type-zoo ) mungkin tertarik juga. :)

Bagi siapa pun yang tertarik, saya menemukan cara sederhana untuk menerapkan tipe yang tepat pada parameter fungsi. Bekerja pada TS 2.7, setidaknya.

function myFn<T extends {[K in keyof U]: any}, U extends DesiredType>(arg: T & U): void;

EDIT: Saya kira agar ini berfungsi, Anda harus menentukan objek literal langsung ke dalam argumen; ini tidak berfungsi jika Anda mendeklarasikan const terpisah di atas dan meneruskannya sebagai gantinya. :/ Tapi satu solusi adalah dengan hanya menggunakan penyebaran objek di situs panggilan, yaitu myFn({...arg}) .

EDIT: maaf, saya tidak membaca bahwa Anda hanya menyebutkan TS 2.7. Saya akan mengujinya di sana!

@vaskevich Sepertinya saya tidak bisa membuatnya berfungsi, yaitu tidak mendeteksi colour sebagai properti berlebih :

Saat tipe bersyarat mendarat (#21316), Anda dapat melakukan hal berikut untuk meminta tipe yang tepat sebagai parameter fungsi, bahkan untuk literal objek "tidak segar":

type Exactify<T, X extends T> = T & {
    [K in keyof X]: K extends keyof T ? X[K] : never
}

type Foo = {a?: string, b: number}

declare function requireExact<X extends Exactify<Foo, X>>(x: X): void;

const exact = {b: 1}; 
requireExact(exact); // okay

const inexact = {a: "hey", b: 3, c: 123}; 
requireExact(inexact);  // error
// Types of property 'c' are incompatible.
// Type 'number' is not assignable to type 'never'.

Tentu saja jika Anda memperluas jenisnya, itu tidak akan berhasil, tetapi saya rasa tidak ada yang dapat Anda lakukan tentang itu:

const inexact = {a: "hey", b: 3, c: 123} as Foo;
requireExact(inexact);  // okay

Pikiran?

Sepertinya kemajuan sedang dibuat pada parameter fungsi. Adakah yang menemukan cara untuk menegakkan tipe yang tepat untuk nilai pengembalian fungsi?

@jezzgoodwin tidak juga. Lihat #241 yang merupakan akar penyebab pengembalian fungsi tidak diperiksa dengan benar untuk properti tambahan

Satu lagi kasus penggunaan. Saya hampir mengalami bug karena situasi berikut yang tidak dilaporkan sebagai kesalahan:

interface A {
    field: string;
}

interface B {
    field2: string;
    field3?: string;
}

type AorB = A | B;

const fixture: AorB[] = [
    {
        field: 'sfasdf',
        field3: 'asd' // ok?!
    },
];

( Taman bermain )

Solusi yang jelas untuk ini dapat berupa:

type AorB = Exact<A> | Exact<B>;

Saya melihat solusi yang diusulkan di #16679 tetapi dalam kasus saya, tipenya adalah AorBorC (dapat tumbuh) dan setiap objek memiliki beberapa properti, jadi saya agak sulit untuk menghitung secara manual kumpulan properti fieldX?:never untuk setiap jenis.

@michalstocki Bukankah itu #20863? Anda ingin pemeriksaan properti berlebih pada serikat pekerja menjadi lebih ketat.

Bagaimanapun, dengan tidak adanya tipe yang tepat dan pemeriksaan properti berlebih yang ketat pada serikat pekerja, Anda dapat melakukan properti fieldX?:never terprogram alih-alih secara manual dengan menggunakan tipe kondisional :

type AllKeys<U> = U extends any ? keyof U : never
type ExclusifyUnion<U> = [U] extends [infer V] ?
 V extends any ? 
 (V & {[P in Exclude<AllKeys<U>, keyof V>]?: never}) 
 : never : never

Dan kemudian definisikan persatuan Anda sebagai

type AorB = ExclusifyUnion<A | B>;

yang berkembang menjadi

type AorB = (A & {
    field2?: undefined;
    field3?: undefined;
}) | (B & {
    field?: undefined;
})

secara otomatis. Ini berfungsi untuk AorBorC juga.

Lihat juga https://github.com/Microsoft/TypeScript/issues/14094#issuecomment -373780463 untuk eksklusif atau implementasi

@jcalz Jenis lanjutan ExclusifyUnion tidak terlalu aman:

const { ...fields } = o as AorB;

fields.field3.toUpperCase(); // it shouldn't be passed

Bidang fields semuanya non-opsional.

Saya tidak berpikir itu ada hubungannya dengan tipe Exact, tetapi dengan apa yang terjadi ketika Anda menyebar dan kemudian merusak objek bertipe union . Persatuan apa pun pada akhirnya akan diratakan menjadi satu tipe seperti persimpangan, karena itu memisahkan objek ke dalam properti individu dan kemudian menggabungkannya kembali; korelasi atau batasan apa pun antara konstituen dari setiap serikat pekerja akan hilang. Tidak yakin bagaimana cara menghindarinya... jika itu bug, itu mungkin masalah yang terpisah.

Jelas semuanya akan berperilaku lebih baik jika Anda mengetikkan penjagaan sebelum perusakan:

declare function isA(x: any): x is A;
declare function isB(x: any): x is B;

declare const o: AorB;
if (isA(o)) {
  const { ...fields } = o;
  fields.field3.toUpperCase(); // error
} else {
  const { ...fields } = o;
  fields.field3.toUpperCase(); // error
  if (fields.field3) {
    fields.field3.toUpperCase(); // okay
  }
}

Bukannya ini "memperbaiki" masalah yang Anda lihat, tetapi begitulah yang saya harapkan seseorang bertindak dengan serikat pekerja yang dibatasi.

Mungkin https://github.com/Microsoft/TypeScript/pull/24897 memperbaiki masalah penyebaran

saya mungkin terlambat ke pesta, tetapi inilah cara Anda setidaknya dapat memastikan tipe Anda sama persis:

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
const sureIsTrue: (fact: true) => void = () => {};
const sureIsFalse: (fact: false) => void = () => {};



declare const x: string;
declare const y: number;
declare const xAndYAreOfTheSameType: AreSame<typeof x, typeof y>;
sureIsFalse(xAndYAreOfTheSameType);  // <-- no problem, as expected
sureIsTrue(xAndYAreOfTheSameType);  // <-- problem, as expected

berharap saya bisa melakukan ini:

type Exact<A, B> = A extends B ? B extends A ? B : never : never;
declare function needExactA<X extends Exact<A, X>>(value: X): void;

Apakah fitur yang dijelaskan dalam masalah ini membantu kasus di mana antarmuka kosong/terindeks cocok dengan tipe seperti objek, seperti fungsi atau kelas?

interface MyType
{
    [propName: string]: any;
}

function test(value: MyType) {}

test({});           // OK
test(1);            // Fails, OK!
test('');           // Fails, OK!
test(() => {});     // Does not fail, not OK!
test(console.log);  // Does not fail, not OK!
test(console);      // Does not fail, not OK!

Antarmuka MyType hanya mendefinisikan tanda tangan indeks dan digunakan sebagai jenis satu-satunya parameter fungsi test . Parameter diteruskan ke fungsi tipe:

  • Objek literal {} , lewat. Perilaku yang diharapkan.
  • Konstanta numerik 1 tidak lulus. Perilaku yang diharapkan (_Argumen tipe '1' tidak dapat ditetapkan ke parameter tipe 'MyType'._)
  • String literal '' tidak lulus. Perilaku yang diharapkan (_`Argumen tipe '""' tidak dapat ditetapkan ke parameter tipe 'MyType'._)
  • Deklarasi fungsi panah () => {} : Lulus. Perilaku yang tidak diharapkan. Mungkin lolos karena fungsi adalah objek?
  • Metode kelas console.log Lulus. Perilaku yang tidak diharapkan. Mirip dengan fungsi panah.
  • Kelas console lulus. Perilaku yang tidak diharapkan. Mungkin karena kelas adalah objek?

Intinya adalah hanya mengizinkan variabel yang sama persis dengan antarmuka MyType dengan menjadi tipe itu (dan tidak secara implisit dikonversi ke dalamnya). TypeScript tampaknya melakukan banyak konversi implisit berdasarkan tanda tangan jadi ini mungkin sesuatu yang tidak dapat didukung.

Maaf jika ini di luar topik. Sejauh ini masalah ini paling cocok dengan masalah yang saya jelaskan di atas.

@ Janne252 Proposal ini dapat membantu Anda secara tidak langsung. Dengan asumsi Anda mencoba Exact<{[key: string]: any}> , inilah mengapa ini akan berhasil:

  • Literal objek lulus seperti yang diharapkan, seperti yang sudah mereka lakukan dengan {[key: string]: any} .
  • Konstanta numerik gagal seperti yang diharapkan, karena literal tidak dapat ditetapkan ke {[key: string]: any} .
  • Literal string gagal seperti yang diharapkan, karena tidak dapat ditetapkan ke {[key: string]: any} .
  • Fungsi dan konstruktor kelas gagal karena tanda tangan call (ini bukan properti string).
  • Objek console lewat karena hanya itu, sebuah objek (bukan kelas). JS tidak membuat pemisahan antara objek dan kamus kunci/nilai, dan TS tidak berbeda di sini selain dari pengetikan polimorfik baris yang ditambahkan. Selain itu, TS tidak mendukung tipe yang bergantung pada nilai, dan typeof hanyalah gula untuk menambahkan beberapa parameter tambahan dan/atau alias tipe - ini hampir tidak ajaib seperti yang terlihat.

@blakeembrey @michalstocki @aleksey-bykov
Ini adalah cara saya melakukan tipe yang tepat:

type Exact<A extends object> = A & {__kind: keyof A};

type Foo = Exact<{foo: number}>;
type FooGoo = Exact<{foo: number, goo: number}>;

const takeFoo = (foo: Foo): Foo => foo;

const foo = {foo: 1} as Foo;
const fooGoo = {foo: 1, goo: 2} as FooGoo;

takeFoo(foo)
takeFoo(fooGoo) // error "[ts]
//Argument of type 'Exact<{ foo: number; goo: number; }>' is not assignable to parameter of type 'Exact<{ //foo: number; }>'.
//  Type 'Exact<{ foo: number; goo: number; }>' is not assignable to type '{ __kind: "foo"; }'.
//    Types of property '__kind' are incompatible.
//      Type '"foo" | "goo"' is not assignable to type '"foo"'.
//        Type '"goo"' is not assignable to type '"foo"'."

const takeFooGoo = (fooGoo: FooGoo): FooGoo => fooGoo;

takeFooGoo(fooGoo);
takeFooGoo(foo); // error "[ts]
// Argument of type 'Exact<{ foo: number; }>' is not assignable to parameter of type 'Exact<{ foo: number; // goo: number; }>'.
//  Type 'Exact<{ foo: number; }>' is not assignable to type '{ foo: number; goo: number; }'.
//    Property 'goo' is missing in type 'Exact<{ foo: number; }>'.

Ini berfungsi untuk parameter fungsi, pengembalian, dan bahkan untuk penetapan.
const foo: Foo = fooGoo; // kesalahan
Tidak ada overhead runtime. Satu-satunya masalah adalah bahwa setiap kali Anda membuat objek baru yang tepat, Anda harus melemparkannya ke tipenya, tetapi sebenarnya itu bukan masalah besar.

Saya percaya contoh asli memiliki perilaku yang benar: Saya berharap interface s terbuka. Sebaliknya, saya berharap type s ditutup (dan mereka hanya kadang-kadang ditutup). Berikut adalah contoh perilaku mengejutkan saat menulis tipe MappedOmit :
https://Gist.github.com/donabrams/b849927f5a0160081db913e3d52cc7b3

Jenis MappedOmit dalam contoh hanya berfungsi sebagaimana dimaksudkan untuk serikat pekerja yang didiskriminasi. Untuk serikat pekerja yang tidak terdiskriminasi, TypeScript 3.2 akan diteruskan ketika persimpangan jenis apa pun dalam serikat tersebut dilewati.

Solusi di atas menggunakan as TypeX atau as any untuk mentransmisi memiliki efek samping menyembunyikan kesalahan dalam konstruksi!. Kami ingin pemeriksa tipe kami membantu kami menangkap kesalahan dalam konstruksi juga! Selain itu, ada beberapa hal yang dapat kita hasilkan secara statis dari tipe yang terdefinisi dengan baik. Solusi seperti di atas (atau solusi tipe nominal yang dijelaskan di sini: https://Gist.github.com/donabrams/74075e89d10db446005abe7b1e7d9481) menghentikan generator tersebut agar tidak berfungsi (meskipun kami dapat memfilter bidang utama _ , ini adalah konvensi yang menyakitkan itu benar-benar dapat dihindari).

@aleksey-bykov fyi saya pikir implementasi Anda 99% dari jalan ke sana, ini berhasil untuk saya:

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
type Exact<A, B> = AreSame<A, B> extends true ? B : never;

const value1 = {};
const value2 = {a:1};

// works
const exactValue1: Exact<{}, typeof value1> = value1;
const exactValue1WithTypeof: Exact<typeof value1, typeof value1> = value1;

// cannot assign {a:number} to never
const exactValue1Fail: Exact<{}, typeof value2> = value2;
const exactValue1FailWithTypeof: Exact<typeof value1, typeof value2> = value2;

// cannot assign {} to never
const exactValue2Fail: Exact<{a: number}, typeof value1> = value1;
const exactValue2FailWithTypeof: Exact<typeof value2, typeof value1> = value1;

// works
const exactValue2: Exact<{a: number}, typeof value2> = value2;
const exactValue2WithTypeof: Exact<typeof value2, typeof value2> = value2;

wow, tolong tinggalkan bunga di sini, hadiah masuk ke tempat sampah itu

Satu perbaikan kecil yang dapat dilakukan di sini:
Dengan menggunakan definisi Exact secara efektif membuat pengurangan B dari A sebagai A & never tipe pada semua B kunci unik

type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
type Exact<A, B = {}> = A & Record<keyof Omit<B, A>, never>;

Terakhir, saya ingin dapat melakukan ini tanpa harus menambahkan penggunaan template eksplisit dari argumen template B . Saya dapat membuat ini berfungsi dengan membungkus dengan metode - tidak ideal karena mempengaruhi runtime tetapi berguna jika Anda benar-benar membutuhkannya:

function makeExactVerifyFn<T>() {
  return <C>(x: C & Exact<T, C>): C => x;
}

Contoh penggunaan:

interface Task {
  title: string;
  due?: Date;
}

const isOnlyTask = makeExactVerifyFn<Task>();

const validTask_1 = isOnlyTask({
    title: 'Get milk',
    due: new Date()  
});

const validTask_2 = isOnlyTask({
    title: 'Get milk'
});

const invalidTask_1 = isOnlyTask({
    title: 5 // [ts] Type 'number' is not assignable to type 'string'.
});

const invalidTask_2 = isOnlyTask({
    title: 'Get milk',
    procrastinate: true // [ts] Type 'true' is not assignable to type 'never'.
});

@danielnmsft Tampaknya aneh untuk meninggalkan B di Exact<A, B> opsional dalam contoh Anda, terutama jika diperlukan untuk validasi yang tepat. Kalau tidak, itu terlihat cukup bagus bagi saya. Tampaknya lebih baik bernama Equal .

@drabinowitz Jenis Anda Exact sebenarnya tidak mewakili apa yang telah diusulkan di sini dan mungkin harus diganti namanya menjadi AreExact . Maksud saya, Anda tidak dapat melakukan ini dengan tipe Anda:

function takesExactFoo<T extends Exact<Foo>>(foo: T) {}

Namun, tipe Anda berguna untuk menerapkan tipe parameter yang tepat!

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
type Exact<A, B> = AreSame<A, B> extends true ? B : never;

interface Foo {
    bar: any
}

function takesExactFoo <T>(foo: T & Exact<Foo, T>) {
                    //  ^ or `T extends Foo` to type-check `foo` inside the function
}

let foo = {bar: 123}
let foo2 = {bar: 123, baz: 123}

takesExactFoo(foo) // ok
takesExactFoo(foo2) // error

UPD1 Ini tidak akan membuat fungsi runtime +1 seperti pada solusi @danielnmsft dan tentu saja jauh lebih fleksibel.

UPD2 Saya baru menyadari bahwa Daniel pada dasarnya membuat tipe yang sama Exact seperti yang dilakukan @drabinowitz , tetapi yang lebih ringkas dan mungkin lebih baik. Saya juga menyadari bahwa saya melakukan hal yang sama seperti yang dilakukan Daniel. Tetapi saya akan meninggalkan komentar saya jika ada yang menganggapnya berguna.

Definisi AreSame / Exact itu tampaknya tidak berfungsi untuk tipe serikat pekerja.
Contoh: Exact<'a' | 'b', 'a' | 'b'> menghasilkan never .
Ini tampaknya dapat diperbaiki dengan mendefinisikan type AreSame<A, B> = A|B extends A&B ? true : false;

@nerumo pasti menemukan ini untuk jenis fungsi peredam yang sama dengan yang Anda tunjukkan.

Pasangkan opsi tambahan dari apa yang Anda miliki:

1 Anda dapat mengatur tipe pengembalian agar sama dengan tipe input dengan typeof . Lebih berguna jika itu tipe yang sangat rumit. Bagi saya ketika saya melihat ini, lebih jelas tujuannya adalah mencegah properti tambahan.

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): typeof state {
   return {
       ...state,
       fullName: action.payload        // THIS IS REPORTED AS AN ERROR
   };
}

2 Untuk reduksi, alih-alih variabel sementara, tetapkan kembali ke dirinya sendiri sebelum kembali:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {
   return (state = {
       ...state,
       fullName: action.payload        // THIS IS REPORTED AS AN ERROR
   });
}

3 Jika Anda benar-benar menginginkan variabel sementara, jangan berikan tipe eksplisit, gunakan typeof state lagi

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {

   const newState: typeof state = {
       ...state,
       fullName: action.payload         // THIS IS REPORTED AS AN ERROR
   };

   return newState;
}

3b Jika peredam Anda tidak mengandung ...state Anda dapat menggunakan Partial<typeof state> untuk tipe:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {

   const newState: Partial<typeof state> = {
       name: 'Simon',
       fullName: action.payload         // THIS IS REPORTED AS AN ERROR
   };

   return newState;
}

Saya merasa seluruh percakapan ini (dan saya baru saja membaca seluruh utasnya) melewatkan inti masalah bagi kebanyakan orang dan itu adalah untuk mencegah kesalahan yang kami inginkan hanyalah pernyataan tipe untuk mencegah pelarangan tipe 'lebih luas':

Inilah yang mungkin dicoba orang terlebih dahulu, yang tidak melarang 'nama lengkap':

 return <State> {
       ...state,
       fullName: action.payload         // compiles ok :-(
   };

Ini karena <Dog> cat Anda memberi tahu kompiler - ya saya tahu apa yang saya lakukan, ini adalah Dog ! Anda tidak meminta izin.

Jadi yang paling berguna bagi saya adalah versi <Dog> cat yang lebih ketat yang akan mencegah properti asing:

 return <strict State> {
       ...state,
       fullName: action.payload     // compiles ok :-(
   };

Seluruh jenis Exact<T> memiliki banyak konsekuensi melalui konsekuensi (ini adalah utas yang panjang!). Itu mengingatkan saya pada seluruh perdebatan 'pengecualian yang diperiksa' di mana itu adalah sesuatu yang Anda pikir Anda inginkan tetapi ternyata memiliki banyak masalah (seperti tiba-tiba lima menit kemudian menginginkan Unexact<T> ).

Di sisi lain <strict T> akan bertindak lebih seperti penghalang untuk mencegah jenis 'mustahil' mendapatkan 'melewati'. Ini pada dasarnya adalah filter tipe yang melewati tipe (seperti yang telah dilakukan di atas dengan fungsi runtime).

Namun akan mudah bagi pendatang baru untuk menganggap itu mencegah 'data buruk' masuk dalam kasus di mana tidak mungkin untuk melakukannya.

Jadi jika saya harus membuat sintaks proposal, ini akan menjadi ini:

/// This syntax is ONLY permitted directly in front of an object declaration:
return <strict State> { ...state, foooooooooooooo: 'who' };

Kembali ke OP: secara teori [1] dengan tipe yang dinegasikan, Anda dapat menulis type Exact<T> = T & not Record<not keyof T, any> . Kemudian Exact<{x: string}> akan melarang jenis apa pun dengan kunci selain x untuk ditugaskan padanya. Tidak yakin apakah itu cukup untuk memenuhi apa yang diminta oleh semua orang di sini, tetapi tampaknya sangat cocok dengan OP.

[1] Saya katakan secara teori karena itu didasarkan pada tanda tangan indeks yang lebih baik juga

Ingin tahu apakah saya memiliki masalah yang dijelaskan di sini. Saya memiliki kode seperti:

const Layers = {
  foo: 'foo'
  bar: 'bar'
  baz: 'baz'
}

type Groups = {
  [key in keyof Pick<Layers, 'foo' | 'bar'>]: number
}

const groups = {} as Groups

maka itu memungkinkan saya untuk mengatur properti yang tidak dikenal, yang tidak saya inginkan:

groups.foo = 1
groups.bar = 2
groups.anything = 2 // NO ERROR :(

Pengaturan anything masih berfungsi, dan tipe nilai kuncinya adalah any . Saya berharap itu akan menjadi kesalahan.

Apakah ini yang akan diselesaikan dengan masalah ini?

Ternyata, saya seharusnya melakukan

type Groups = {
  [key in keyof Pick<typeof Layers, 'foo' | 'bar'>]: number
}

Perhatikan penggunaan tambahan typeof .

Plugin Atom atom-typescript berusaha keras untuk tidak gagal, dan akhirnya macet. Ketika saya menambahkan typeof , semuanya kembali normal, dan alat peraga yang tidak dikenal tidak lagi diizinkan yang saya harapkan.

Dengan kata lain, ketika saya tidak menggunakan typeof , atom-typescript mencoba mencari tipe di tempat lain dari kode tempat saya menggunakan objek bertipe Groups , dan itu memungkinkan saya untuk menambahkan alat peraga yang tidak dikenal dan menunjukkan kepada saya petunjuk jenis any untuk mereka.

Jadi saya rasa saya tidak memiliki masalah dengan utas ini.

Komplikasi lain mungkin bagaimana menangani properti opsional.

Jika Anda memiliki tipe yang memiliki properti opsional, apa arti Exact<T> untuk properti tersebut:

export type PlaceOrderResponse = { 
   status: 'success' | 'paymentFailed', 
   orderNumber: string
   amountCharged?: number
};

Apakah Exact<T> berarti setiap properti opsional harus didefinisikan? Apa yang akan Anda tentukan sebagai? Bukan 'tidak terdefinisi' atau 'null' karena itu memiliki efek runtime.

Apakah ini sekarang memerlukan cara baru untuk menentukan 'parameter opsional wajib'?

Misalnya dengan apa kita harus menetapkan amountCharged dalam contoh kode berikut agar memenuhi 'ketepatan' dari jenisnya? Kami tidak terlalu 'tepat' jika kami tidak memaksakan properti ini setidaknya 'diakui'. Apakah <never> ? Tidak boleh undefined atau null .

const exactOrderResponse: Exact<PlaceOrderResponse> = 
{
   status: 'paymentFailed',
   orderNumber: '1001',
   amountCharged: ????      
};

Jadi, Anda mungkin berpikir - ini masih opsional, dan sekarang persis opsional yang hanya diterjemahkan menjadi optional . Dan tentu saja pada saat runtime itu tidak perlu disetel, tetapi bagi saya sepertinya kita baru saja 'memecahkan' Exact<T> dengan menempelkan tanda tanya.

Mungkin hanya ketika menetapkan nilai di antara dua jenis pemeriksaan ini perlu dilakukan? (Untuk menegakkan bahwa keduanya menyertakan amountCharged?: number )

Mari kita perkenalkan tipe baru di sini untuk data input kotak dialog:

export type OrderDialogBoxData = { 
   status: 'success' | 'paymentFailed', 
   orderNumber: string
   amountCharge?: number      // note the typo here!
};

Jadi mari kita coba ini:

// run the API call and then assign it to a dialog box.
const serverResponse: Exact<PlaceOrderResponse> = await placeOrder();
const dialogBoxData: Exact<OrderDialogBoxData> = serverResponse;    // SHOULD FAIL

Saya berharap ini gagal tentu saja karena salah ketik - meskipun properti ini opsional di keduanya.

Jadi saya kembali ke 'Mengapa kita menginginkan ini sejak awal?' .
Saya pikir itu karena alasan ini (atau sebagian tergantung pada situasinya):

  • Hindari kesalahan ketik nama properti
  • Jika kami menambahkan properti ke beberapa 'komponen', kami ingin memastikan semua yang menggunakannya harus menambahkan properti itu juga
  • Jika kami menghapus properti dari beberapa 'komponen', kami perlu menghapusnya di mana-mana.
  • Pastikan kami tidak memberikan properti tambahan yang tidak perlu (mungkin kami mengirimkannya ke API dan kami ingin menjaga payload tetap ramping)

Jika 'properti opsional yang tepat' tidak ditangani dengan benar maka beberapa manfaat ini rusak atau sangat membingungkan!

Juga dalam contoh di atas kita baru saja 'menyetujui' Exact untuk mencoba menghindari kesalahan ketik tetapi hanya berhasil membuat kekacauan besar! Dan sekarang bahkan lebih rapuh dari sebelumnya.

Saya pikir apa yang sering saya butuhkan sebenarnya bukan tipe Exact<T> sama sekali, ini adalah salah satu dari dua ini:

NothingMoreThan<T> atau
NothingLessThan<T>

Di mana 'wajib opsional' sekarang menjadi sesuatu. Yang pertama memungkinkan tidak ada tambahan untuk ditentukan oleh RHS tugas, dan yang kedua memastikan semuanya (termasuk properti opsional) ditentukan pada RHS tugas.

NothingMoreThan akan berguna untuk muatan yang dikirim melalui kabel, atau JSON.stringify() dan jika Anda mendapatkan kesalahan karena Anda memiliki terlalu banyak properti di RHS, Anda harus menulis kode runtime untuk memilih saja sifat-sifat yang dibutuhkan. Dan itulah solusi yang tepat - karena begitulah cara kerja Javascript.

NothingLessThan adalah jenis yang sudah kita miliki di TypeScript - untuk semua tugas normal - kecuali itu perlu mempertimbangkan properti (optional?: number) opsional.

Saya tidak berharap nama-nama ini membuat daya tarik, tetapi saya pikir konsepnya lebih jelas dan lebih terperinci daripada Exact<T> ...

Kemudian, mungkin (jika kita benar-benar membutuhkannya):

Exact<T> = NothingMoreThan<NothingLessThan<T>>;

atau apakah itu:

Exact<T> = NothingLessThan<NothingMoreThan<T>>;   // !!

Posting ini adalah hasil dari masalah nyata yang saya alami hari ini di mana saya memiliki 'tipe data kotak dialog' yang berisi beberapa properti opsional dan saya ingin memastikan apa yang berasal dari server dapat ditetapkan untuk itu.

Catatan akhir: NothingLessThan / NothingMoreThan memiliki 'rasa' yang mirip dengan beberapa komentar di atas di mana tipe A diperpanjang dari tipe B, atau B diperpanjang dari A. Batasannya adalah mereka tidak akan membahas properti opsional (setidaknya saya pikir mereka tidak bisa hari ini).

@simeyla Anda bisa lolos dengan varian "tidak lebih dari".

  • "Tidak kurang dari" hanyalah tipe normal. TS melakukan ini secara implisit, dan setiap jenis diperlakukan setara dengan for all T extends X: T .
  • "Tidak lebih dari" pada dasarnya adalah kebalikannya: ini adalah for all T super X: T implisit

Cara untuk memilih satu atau keduanya secara eksplisit sudah cukup. Sebagai efek samping, Anda bisa menentukan Jawa T super C sebagai Anda diusulkan T extends NothingMoreThan<C> . Jadi saya cukup yakin ini mungkin lebih baik daripada tipe standar yang tepat.

Saya merasa ini harus sintaks. Mungkin ini?

  • extends T - Gabungan semua tipe yang dapat ditetapkan ke T, yaitu setara dengan T .
  • super T - Gabungan semua tipe T dapat ditetapkan.
  • extends super T , super extends T - Gabungan semua tipe yang ekuivalen dengan T. Ini keluar dari grid, karena hanya tipe yang dapat ditetapkan dan ditetapkan untuk dirinya sendiri.
  • type Exact<T> = extends super T - Gula bawaan untuk kasus umum di atas, untuk membantu keterbacaan.
  • Karena ini hanya mengubah penetapan, Anda masih dapat memiliki hal-hal seperti serikat pekerja yang merupakan tipe tepat atau super.

Ini juga memungkinkan untuk mengimplementasikan #14094 di userland hanya dengan membuat setiap varian Exact<T> , seperti Exact<{a: number}> | Exact<{b: number}> .


Saya ingin tahu apakah ini juga memungkinkan tipe yang dinegasikan di userland. Saya percaya itu benar, tetapi saya harus melakukan beberapa aritmatika tipe yang rumit terlebih dahulu untuk mengonfirmasi itu, dan itu bukan hal yang jelas untuk dibuktikan.

Saya ingin tahu apakah ini juga memungkinkan tipe yang dinegasikan di tanah pengguna, karena (super T) | (memperpanjang T) sama dengan tidak diketahui. Saya percaya itu, tetapi saya harus melakukan beberapa aritmatika tipe yang rumit terlebih dahulu untuk mengonfirmasi itu, dan itu bukan hal yang jelas untuk dibuktikan.

Untuk (super T) | (extends T) === unknown untuk menahan penetapan harus menjadi pesanan total.

@jack-williams Tangkapan yang bagus dan diperbaiki (dengan menghapus klaim). Saya bertanya-tanya mengapa hal-hal tidak berjalan pada awalnya ketika saya bermain-main sedikit.

@jack-williams

"Tidak kurang dari" hanyalah tipe normal. TS melakukan ini secara implisit, dan setiap tipe diperlakukan setara

Iya dan tidak. Tapi kebanyakan ya... ...tapi hanya jika Anda dalam mode strict !

Jadi saya memiliki banyak situasi di mana saya membutuhkan properti untuk menjadi 'opsional' secara logis tetapi saya ingin kompiler memberi tahu saya jika saya telah 'melupakannya' atau salah mengejanya.

Nah itulah yang Anda dapatkan dengan lastName: string | undefined sedangkan saya kebanyakan mendapatkan lastName?: string , dan tentu saja tanpa mode strict Anda tidak akan diperingatkan tentang semua perbedaan.

Saya selalu tahu tentang mode ketat, dan saya tidak bisa seumur hidup saya menemukan alasan bagus mengapa saya tidak menyalakannya sampai kemarin - tetapi sekarang saya sudah (dan saya masih mengarungi ratusan perbaikan ) jauh lebih mudah untuk mendapatkan perilaku yang saya inginkan 'di luar kotak'.

Saya telah mencoba segala macam hal untuk mendapatkan apa yang saya inginkan - termasuk bermain dengan Required<A> extends Required<B> , dan mencoba menghapus flag properti ? opsional. Itu mengirim saya ke lubang kelinci yang sama sekali berbeda - (dan ini semua sebelum saya mengaktifkan mode strict ).

Intinya adalah bahwa jika Anda mencoba untuk mendapatkan sesuatu yang mendekati tipe 'tepat' hari ini, maka Anda harus mulai dengan mengaktifkan mode strict (atau kombinasi flag apa pun yang memberikan pemeriksaan yang tepat). Dan jika saya perlu menambahkan middleName: string | undefined kemudian boom - saya tiba-tiba menemukan di mana pun saya perlu 'mempertimbangkannya' :-)

PS. terima kasih atas komentar Anda - sangat membantu. Saya menyadari bahwa saya telah melihat BANYAK kode yang jelas-jelas tidak menggunakan mode strict - dan kemudian orang-orang menabrak tembok seperti yang saya lakukan. Saya ingin tahu apa yang bisa dilakukan untuk mendorong penggunaannya lebih banyak?

@simeyla Saya pikir umpan balik dan terima kasih Anda harus diarahkan ke @isiahmeadows!

Saya pikir saya akan menulis pengalaman saya dengan tipe Exact setelah mengimplementasikan prototipe dasar. Pikiran umum saya adalah bahwa tim tepat dengan penilaian mereka:

Diagnosis harapan kami adalah bahwa ini, di luar sedikit API yang benar-benar tertutup, merupakan solusi Masalah XY.

Saya tidak merasa bahwa biaya memperkenalkan tipe objek lain dibayar dengan menangkap lebih banyak kesalahan, atau dengan mengaktifkan hubungan tipe baru. Pada akhirnya, tipe yang tepat memungkinkan saya _mengatakan_ lebih banyak, tetapi mereka tidak membiarkan saya _melakukan_ lebih banyak.

Meneliti beberapa kasus penggunaan potensial dari jenis yang tepat:

Pengetikan yang kuat untuk keys dan for ... in .

Memiliki tipe yang lebih tepat saat menghitung kunci tampaknya menarik, tetapi dalam praktiknya saya tidak pernah menemukan diri saya menyebutkan kunci untuk hal-hal yang secara konseptual tepat. Jika Anda tahu persis kuncinya, mengapa tidak langsung mengatasinya?

Pengerasan pelebaran properti opsional.

Aturan penetapan { ... } <: { ...; x?: T } tidak benar karena tipe kiri mungkin menyertakan properti x yang tidak kompatibel yang di-alias. Saat menetapkan dari jenis yang tepat, aturan ini menjadi suara. Dalam praktiknya saya tidak pernah menggunakan aturan ini; tampaknya lebih cocok untuk sistem warisan yang tidak memiliki tipe yang tepat untuk memulai.

Reaksi dan HOC

Saya telah menyematkan harapan terakhir saya pada tipe yang tepat meningkatkan passing props, dan penyederhanaan tipe spread. Kenyataannya adalah bahwa tipe eksak adalah antitesis dari polimorfisme terbatas, dan pada dasarnya non-komposisi.

Sebuah generik terbatas memungkinkan Anda menentukan alat peraga yang Anda pedulikan, dan meneruskan sisanya. Segera setelah batas menjadi tepat, Anda benar-benar kehilangan subtipe lebar dan generik menjadi kurang berguna secara signifikan. Masalah lain adalah bahwa salah satu alat komposisi utama dalam TypeScript adalah persimpangan, tetapi jenis persimpangan tidak kompatibel dengan jenis yang tepat. Setiap jenis persimpangan non-sepele dengan komponen yang tepat akan menjadi hampa: _jenis yang tepat tidak menulis_. Untuk reaksi dan props, Anda mungkin menginginkan tipe baris dan polimorfisme baris, tetapi itu untuk hari lain.

Hampir semua bug menarik yang mungkin diselesaikan dengan tipe yang tepat diselesaikan dengan pemeriksaan properti berlebih; Masalah terbesar adalah bahwa pemeriksaan properti yang berlebihan tidak akan berhasil bagi serikat pekerja tanpa properti yang diskriminan; selesaikan ini dan hampir semua masalah menarik yang relevan untuk tipe yang tepat hilang, IMO.

@ jack-williams Saya setuju umumnya tidak terlalu berguna untuk memiliki tipe yang tepat. Konsep pemeriksaan properti berlebih sebenarnya dicakup oleh proposal operator super T , hanya secara tidak langsung karena penyatuan semua tipe T dapat ditetapkan terutama tidak termasuk subtipe T yang tepat.

Saya tidak terlalu mendukung ini secara pribadi selain dari mungkin T super U *, karena tentang satu-satunya kasus penggunaan yang pernah saya temui untuk pemeriksaan properti berlebih adalah berurusan dengan server yang rusak, sesuatu yang biasanya dapat Anda atasi menggunakan fungsi pembungkus untuk menghasilkan permintaan secara manual dan membuang sampah berlebih. Setiap masalah lain yang saya temukan dilaporkan di utas ini sejauh ini dapat diselesaikan hanya dengan menggunakan serikat diskriminatif sederhana.

* Ini pada dasarnya akan menjadi T extends super U menggunakan proposal saya - batas bawah terkadang berguna untuk membatasi tipe generik yang kontravarian, dan solusi biasanya berakhir dengan memperkenalkan banyak tipe boilerplate tambahan dalam pengalaman saya.

@isiahmeadows Saya tentu setuju tipe batas bawah dapat berguna, dan jika Anda bisa mendapatkan tipe yang tepat dari itu, maka itu adalah kemenangan bagi mereka yang ingin menggunakannya. Saya kira saya harus menambahkan peringatan ke posting saya yaitu: Saya terutama membahas konsep menambahkan operator baru khusus untuk jenis objek yang tepat.

@ jack-williams Saya pikir Anda melewatkan nuansa saya yang saya maksudkan terutama pada jenis yang tepat dan bagian terkait dari pemeriksaan properti berlebih. Sedikit tentang tipe batas bawah adalah catatan kaki karena suatu alasan - itu adalah penyimpangan yang hanya terkait secara tangensial.

Saya berhasil menulis implementasi untuk ini yang akan berfungsi untuk argumen fungsi yang membutuhkan berbagai tingkat ketepatan:

// Checks that B is a subset of A (no extra properties)
type Subset<A extends {}, B extends {}> = {
   [P in keyof B]: P extends keyof A ? (B[P] extends A[P] | undefined ? A[P] : never) : never;
}

// This can be used to implement partially strict typing e.g.:
// ('b?:' is where the behaviour differs with optional b)
type BaseOptions = { a: string, b: number }

// Checks there are no extra properties (Not More, Less fine)
const noMore = <T extends Subset<BaseOptions, T>>(options: T) => { }
noMore({ a: "hi", b: 4 })        //Fine
noMore({ a: 5, b: 4 })           //Error 
noMore({ a: "o", b: "hello" })   //Error
noMore({ a: "o" })               //Fine
noMore({ b: 4 })                 //Fine
noMore({ a: "o", b: 4, c: 5 })   //Error

// Checks there are not less properties (More fine, Not Less)
const noLess = <T extends Subset<T, BaseOptions>>(options: T) => { }
noLess({ a: "hi", b: 4 })        //Fine
noLess({ a: 5, b: 4 })           //Error
noLess({ a: "o", b: "hello" })   //Error
noLess({ a: "o" })               //Error  |b?: Fine
noLess({ b: 4 })                 //Error
noLess({ a: "o", b: 4, c: 5 })   //Fine

// We can use these together to get a fully strict type (Not More, Not Less)
type Strict<A extends {}, B extends {}> = Subset<A, B> & Subset<B, A>;
const strict = <T extends Strict<BaseOptions, T>>(options: T) => { }
strict({ a: "hi", b: 4 })        //Fine
strict({ a: 5, b: 4 })           //Error
strict({ a: "o", b: "hello" })   //Error
strict({ a: "o" })               //Error  |b?: Fine
strict({ b: 4 })                 //Error
strict({ a: "o", b: 4, c: 5 })   //Error

// Or a fully permissive type (More Fine, Less Fine)
type Permissive<A extends {}, B extends {}> = Subset<A, B> | Subset<B, A>;
const permissive = <T extends Permissive<BaseOptions, T>>(options: T) => { }
permissive({ a: "hi", b: 4 })        //Fine
permissive({ a: 5, b: 4 })           //Error
permissive({ a: "o", b: "hello" })   //Error
permissive({ a: "o" })               //Fine
permissive({ b: 4 })                 //Fine
permissive({ a: "o", b: 4, c: 5 })   //Fine


Jenis yang tepat untuk penugasan variabel yang saya sadari sebenarnya tidak melakukan apa-apa ...

// This is a little unweildy, there's also a shortform that works in many cases:
type Exact<A extends {}> = Subset<A, A>
// The simpler Exact type works for variable typing
const options0: Exact<BaseOptions> = { a: "hi", b: 4 }        //Fine
const options1: Exact<BaseOptions> = { a: 5, b: 4 }           //Error
const options2: Exact<BaseOptions> = { a: "o", b: "hello" }   //Error
const options3: Exact<BaseOptions> = { a: "o" }               //Error |b?: Fine
const options4: Exact<BaseOptions> = { b: 4 }                 //Error
const options5: Exact<BaseOptions> = { a: "o", b: 4, c: 5 }   //Error

// It also works for function typing when using an inline value
const exact = (options: Exact<BaseOptions>) => { }
exact({ a: "hi", b: 4 })        //Fine
exact({ a: 5, b: 4 })           //Error
exact({ a: "o", b: "hello" })   //Error
exact({ a: "o" })               //Error  |b?: Fine
exact({ b: 4 })                 //Error
exact({ a: "o", b: 4, c: 5 })   //Error

// But not when using a variable as an argument even of the same type
const options6 = { a: "hi", b: 4 }
const options7 = { a: 5, b: 4 }
const options8 = { a: "o", b: "hello" }
const options9 = { a: "o" }
const options10 = { b: 4 }
const options11 = { a: "o", b: 4, c: 5 }
exact(options6)                 //Fine
exact(options7)                 //Error
exact(options8)                 //Error
exact(options9)                 //Error |b?: Fine
exact(options10)                //Error
exact(options11)                //Fine  -- Should not be Fine

// However using strict does work for that
// const strict = <T extends Strict<BaseOptions, T>>(options: T) => { }
strict(options6)                //Fine
strict(options7)                //Error
strict(options8)                //Error
strict(options9)                //Error |b?: Fine
strict(options10)               //Error
strict(options11)               //Error -- Is correctly Error

Lihat

https://www.npmjs.com/package/ts-strictargs
https://github.com/Kotarski/ts-strictargs

Saya merasa seperti saya memiliki kasus penggunaan untuk ini ketika membungkus komponen Bereaksi, di mana saya harus "melewati" alat peraga: https://github.com/Microsoft/TypeScript/issues/29883 . @jack-williams Adakah pemikiran tentang ini?

@OliverJAsh Terlihat relevan, tetapi saya harus mengakui bahwa saya tidak begitu tahu Bereaksi. Saya kira akan sangat membantu untuk mengetahui bagaimana tipe yang tepat dapat membantu dengan tepat di sini.

type MyComponentProps = { foo: 1 };
declare const MyComponent: ComponentType<MyComponentProps>;

type MyWrapperComponent = MyComponentProps & { myWrapperProp: 1 };
const MyWrapperComponent: ComponentType<MyWrapperComponent> = props => (
    <MyComponent
        // We're passing too many props here, but no error!
        {...props}
    />
);

Tolong perbaiki saya kapan saja saya mengatakan sesuatu yang salah.

Saya kira awalnya adalah menentukan MyComponent untuk menerima tipe yang tepat?

declare const MyComponent: ComponentType<Exact<MyComponentProps>>;

Dalam hal ini maka kita akan mendapatkan kesalahan, tetapi bagaimana Anda memperbaiki kesalahan itu? Saya berasumsi di sini bahwa komponen pembungkus tidak hanya memiliki tipe prop yang sama, dan pada titik tertentu Anda benar-benar perlu mengekstrak subset prop secara dinamis. Apakah ini asumsi yang masuk akal?

Jika MyWrapperComponent props juga tepat maka saya pikir itu akan cukup untuk melakukan destructuring bind. Dalam kasus umum ini akan membutuhkan tipe Omit atas tipe yang tepat, dan saya benar-benar tidak tahu semantik di sana. Saya kira itu bisa berfungsi seperti tipe yang dipetakan homomorfik dan mempertahankan ketepatannya, tetapi saya pikir ini akan membutuhkan lebih banyak pemikiran.

Jika MyWrapperComponent tidak tepat maka diperlukan beberapa pemeriksaan run-time untuk membuktikan ketepatan tipe baru, yang hanya dapat dilakukan dengan secara eksplisit memilih properti yang Anda inginkan (yang tidak berskala seperti yang Anda katakan di OP Anda). Saya tidak yakin berapa banyak yang Anda dapatkan dalam kasus ini.

Hal-hal yang belum saya bahas karena saya tidak tahu seberapa besar kemungkinannya adalah kasus umum, di mana props adalah beberapa tipe generik, dan di mana Anda perlu menggabungkan alat peraga seperti { ...props1, ...props2 } . Apakah ini umum?

@Kotarski Apakah Anda mempublikasikannya secara kebetulan di registri NPM?

@gitowiec

@Kotarski Apakah Anda mempublikasikannya secara kebetulan di registri NPM?

https://www.npmjs.com/package/ts-strictargs
https://github.com/Kotarski/ts-strictargs

Saya memiliki kasus penggunaan ini:

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD

// I want this to error, because the 'c' should mean it prevents either AB or ABCD from being satisfied.
const foo: AB | ABCD = { a, b, c };

// I presume that I would need to do this:
const foo: Exact<AB> | Exact<ABCD> = { a, b, c };

@ryami333 Itu tidak membutuhkan tipe yang tepat; yang hanya perlu diperbaiki untuk pemeriksaan properti berlebih: #13813.

@ryami333 Jika Anda bersedia menggunakan tipe tambahan, saya memiliki tipe yang akan melakukan apa yang Anda inginkan, yaitu memaksa versi serikat pekerja yang lebih ketat:

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD


type UnionKeys<T> = T extends any ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>

// Error now.
const foo: StrictUnion<AB | ABCD> = { a: "", b: "", c: "" };

@dragomirtitian Menarik. Ini membuatku penasaran kenapa

type KeyofV1<T extends object> = keyof T

menghasilkan hasil yang berbeda dari

type KeyofV2<T> = T extends object ? keyof T : never

Bisakah seseorang menjelaskan ini kepada saya?

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD

KeyofV1< AB | ABCD > // 'a' | 'b'
KeyofV2< AB | ABCD > // 'a' | 'b' | 'c' | 'e'

V1 mendapatkan kunci umum dari serikat, V2 mendapatkan kunci dari setiap anggota serikat dan serikat hasilnya.

@weswigham Apakah ada alasan mereka harus mengembalikan hasil yang berbeda?

Ya? Seperti yang saya katakan - V1 mendapatkan _kunci umum_ untuk setiap anggota serikat pekerja, karena argumen ke keyof akhirnya menjadi keyof (AB | ABCD) , yang hanya "A" | "B" , sementara versi dalam kondisional hanya menerima satu anggota serikat pada satu waktu, berkat distribusi kondisional melalui inputnya, jadi pada dasarnya keyof AB | keyof ABCD .

@weswigham Jadi kondisi mengevaluasinya lebih seperti ini, seperti melalui beberapa loop implisit?

type Union =
    (AB extends object ? keyof AB : never) |
    (ABCD extends object ? keyof ABCD : never)

Ketika saya membaca kode itu, saya biasanya mengharapkan cek (AB | ABCD) extends object untuk beroperasi sebagai satu unit, memeriksa bahwa (AB | ABCD) dapat ditetapkan ke object , lalu mengembalikan keyof (AB | ABCD) sebagai satu unit, 'a' | 'b' . Pemetaan implisit tampaknya sangat aneh bagi saya.

@isiahmeadows Anda dapat melihat tipe kondisional distributif sebagai foreach untuk serikat pekerja. Mereka menerapkan tipe kondisional untuk setiap anggota serikat secara bergantian dan hasilnya adalah gabungan dari setiap hasil parsial.

Jadi UnionKeys<A | B> = UnionKeys<A> | UnionKeys<B> =(keyof A) | (keyof B)

Tetapi hanya jika tipe kondisional mendistribusikan, dan mendistribusikan hanya jika tipe yang diuji adalah parameter tipe telanjang. Jadi:

type A<T> = T extends object ? keyof T : never // distributive
type B<T> = [T] extends [object] ? keyof T : never // non distributive the type parameter is not naked
type B<T> = object extends T ? keyof T : never // non distributive the type parameter is not the tested type

Terima kasih teman-teman, saya pikir saya mengerti. Saya mengatur ulang untuk pemahaman saya; Saya percaya bahwa NegativeUncommonKeys berguna sendiri juga. Ini dia kalau-kalau itu berguna untuk orang lain juga.

type UnionKeys<T> = T extends any ? keyof T : never;
type NegateUncommonKeys<T, TAll> = (
    Partial<
        Record<
            Exclude<
                UnionKeys<TAll>,
                keyof T
            >,
            never
        >
    >
) 

type StrictUnion<T, TAll = T> = T extends any 
  ? T & NegateUncommonKeys<T, TAll>
  : never;

Saya juga mengerti mengapa T dan TAll keduanya ada di sana. "Efek loop", di mana T diuji dan telanjang, berarti bahwa setiap item dalam gabungan untuk T diterapkan sedangkan TAll belum diuji berisi gabungan asli dan lengkap dari semua item.

@weswigham Ya .. kecuali saya merasa bagian itu berbunyi seperti ditulis oleh satu insinyur kompiler untuk insinyur kompiler lain.

Tipe kondisional di mana tipe yang dicentang adalah parameter tipe telanjang disebut tipe kondisional distributif.

Apa parameter tipe telanjang? (dan mengapa mereka tidak mengenakan pakaian )

yaitu T mengacu pada konstituen individu setelah tipe kondisional didistribusikan di atas tipe gabungan)

Baru kemarin saya berdiskusi tentang apa arti kalimat ini dan mengapa ada penekanan pada kata 'setelah'.

Saya pikir dokumentasi ditulis dengan asumsi pengetahuan dan terminologi sebelumnya yang mungkin tidak selalu dimiliki pengguna.

Bagian buku pegangan memang masuk akal bagi saya dan menjelaskannya jauh lebih baik, tetapi saya masih skeptis dengan pilihan desain di sana. Tidak masuk akal secara logis bagi saya bagaimana perilaku itu secara alami akan mengikuti dari perspektif teori himpunan dan teori tipe. Itu hanya terlihat sedikit terlalu retas.

secara alami mengikuti dari perspektif teori himpunan dan teori tipe

Ambil setiap item dalam satu set dan partisi sesuai dengan predikat.

Itu adalah operasi distributif!

Ambil setiap item dalam satu set dan partisi sesuai dengan predikat.

Meskipun itu hanya masuk akal ketika Anda berbicara tentang himpunan set (yaitu, tipe gabungan) yang mulai terdengar lebih seperti teori kategori.

@RyanCavanaugh Oke, jadi izinkan saya mengklarifikasi: Saya secara intuitif membaca T extends U ? F<T> : G<T> sebagai T <: U ⊢ F(T), (T <: U ⊢ ⊥) ⊢ G(T) , dengan perbandingan dilakukan tidak sepotong {if t ∈ U then F({t}) else G({t}) | t ∈ T} , yang saat ini menjadi semantik.

(Maaf jika sintaks saya sedikit salah - pengetahuan teori tipe saya sepenuhnya otodidak, jadi saya tahu saya tidak tahu semua formalisme sintaksis.)

Operasi mana yang lebih intuitif untuk debat tak terbatas, tetapi dengan aturan saat ini, mudah untuk membuat tipe distributif non-distributif dengan [T] extends [C] . Jika standarnya non-distributif, Anda akan memerlukan beberapa mantra baru pada tingkat yang berbeda untuk menyebabkan distributif. Itu juga merupakan pertanyaan terpisah dari perilaku mana yang lebih sering disukai; IME Saya hampir tidak pernah menginginkan tipe yang tidak mendistribusikan.

Anda tidak memiliki landasan teoretis yang kuat untuk distribusi karena ini adalah operasi sintaksis.

Kenyataannya adalah sangat berguna dan mencoba menyandikannya dengan cara lain akan menyakitkan.

Seperti berdiri, saya akan pergi ke depan dan trail off sebelum saya mengarahkan percakapan terlalu jauh dari topik.

sudah ada begitu banyak masalah tentang distrubutivness, mengapa kita tidak menghadapinya bahwa sintaks baru diperlukan?

30572

Berikut adalah contoh masalah:

Saya ingin menentukan bahwa titik akhir/seri API pengguna saya TIDAK boleh mengembalikan properti tambahan apa pun (seperti misalnya kata sandi) selain yang ditentukan dalam antarmuka layanan. Jika saya secara tidak sengaja mengembalikan objek dengan properti tambahan, saya ingin kesalahan waktu kompilasi, terlepas dari apakah objek hasil telah dihasilkan oleh objek literal atau sebaliknya.

Pemeriksaan run time dari setiap objek yang dikembalikan bisa mahal, terutama untuk array.

Pemeriksaan properti yang berlebihan tidak membantu dalam kasus ini. Sejujurnya, saya pikir ini solusi one-trick-pony yang miring. Secara teori itu seharusnya memberikan jenis pengalaman "itu hanya berfungsi" - dalam praktiknya juga merupakan sumber kebingungan Jenis objek yang tepat seharusnya diimplementasikan sebagai gantinya, mereka akan membahas kedua kasus penggunaan dengan baik.

@babakness Tipe Anda NoExcessiveProps adalah no-op. Saya pikir mereka berarti sesuatu seperti ini:

interface API {
    username: () => { username: string }
}

const api: API = {
    username: (): { username: string } => {
        return { username: 'foobar', password: 'secret'} // error, ok
    }
}

const api2: API = {
    username: (): { username: string } => {
        const id: <X>(x: X) => X = x => x;
        const value = id({ username: 'foobar', password: 'secret' });
        return value  // no error, bad?
    }
}

Sebagai penulis tipe API, Anda ingin menerapkan bahwa username hanya mengembalikan nama pengguna, tetapi pelaksana mana pun dapat menyiasatinya karena tipe objek tidak memiliki batasan lebar. Itu hanya dapat diterapkan pada inisialisasi literal, yang mungkin atau mungkin tidak dilakukan oleh pelaksana. Padahal, saya akan sangat menyarankan siapa pun untuk mencoba menggunakan tipe yang tepat sebagai keamanan berbasis bahasa.

@spion

Pemeriksaan properti yang berlebihan tidak membantu dalam kasus ini. Sejujurnya, saya pikir ini solusi one-trick-pony yang miring. Secara teori, mereka seharusnya memberikan jenis pengalaman "hanya berfungsi"

EPC adalah pilihan desain yang masuk akal dan ringan yang mencakup banyak masalah. Kenyataannya adalah bahwa tipe Exact tidak 'hanya berfungsi'. Untuk mengimplementasikan dengan cara yang baik yang mendukung ekstensibilitas membutuhkan sistem tipe yang sama sekali berbeda.

@ jack-williams Tentu saja akan ada cara lain untuk memverifikasi hadiah juga (pemeriksaan runtime di mana kinerja tidak menjadi masalah, tes, dll) tetapi waktu kompilasi tambahan sangat berharga untuk umpan balik yang cepat.

Juga, saya tidak bermaksud bahwa tipe yang tepat "hanya berfungsi". Maksud saya EPC dimaksudkan untuk "hanya berfungsi" tetapi dalam praktiknya hanya terbatas, membingungkan, dan tidak aman. Terutama karena jika mencoba untuk "sengaja" menggunakannya, Anda biasanya akan menembak kaki Anda sendiri.

edit: Ya, saya mengedit untuk mengganti "mereka" dengan "itu" karena saya menyadari itu membingungkan.

@spion

Juga, saya tidak bermaksud bahwa tipe yang tepat "hanya berfungsi". Maksud saya EPC dimaksudkan untuk "hanya berfungsi" tetapi dalam praktiknya hanya terbatas, membingungkan, dan tidak aman. Terutama karena jika mencoba untuk "sengaja" menggunakannya, Anda biasanya akan menembak kaki Anda sendiri.

Kesalahanku. Baca komentar asli sebagai

Secara teori, mereka seharusnya memberikan jenis pengalaman "itu hanya berfungsi" [yang akan menjadi tipe yang tepat, bukan EPC]

komentar di [] menjadi bacaan saya.

Pernyataan yang direvisi:

Secara teori, itu seharusnya memberikan semacam pengalaman "hanya berfungsi"

jauh lebih jelas. Maaf atas salah tafsir saya!

type NoExcessiveProps<O> = {
  [K in keyof O]: K extends keyof O ? O[K] : never 
}

// no error
const getUser1 = (): {username: string} => {
  const foo = {username: 'foo', password: 'bar' }
  return foo 
} 

// Compile-time error, OK
const foo: NoExcessiveProps<{username: string}>  = {username: 'a', password: 'b' }

// No error? 🤔
const getUser2 = (): NoExcessiveProps<{username: string}> => {
  const foo = {username: 'foo', password: 'bar' }
  return foo 
}


Hasil untuk getUser2 mengejutkan, rasanya tidak konsisten dan sepertinya akan menghasilkan kesalahan waktu kompilasi. Apa wawasan tentang mengapa tidak?

@babakness NoExcessiveProps baru saja mengevaluasi kembali ke T (jenis dengan kunci yang sama dengan T ). Dalam [K in keyof O]: K extends keyof O ? O[K] : never , K akan selalu menjadi kunci O karena Anda memetakan lebih dari keyof O . Contoh kesalahan const karena memicu EPC sama seperti jika Anda mengetiknya sebagai {username: string} .

Jika Anda tidak keberatan memanggil fungsi tambahan, kami dapat menangkap tipe sebenarnya dari objek yang diteruskan, dan melakukan bentuk kustom pemeriksaan properti berlebih. (Saya menyadari intinya adalah untuk secara otomatis menangkap jenis kesalahan ini, jadi ini mungkin bernilai terbatas):

function checked<T extends E, E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
    return o;
}

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checked(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checked(foo) //ok
}

@dragomirtitian Ah... benar... poin bagus! Jadi saya mencoba memahami fungsi checked . Saya sangat bingung

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    const bar = checked(foo) // error
    return checked(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    const bar = checked(foo) // error!?
    return checked(foo) //ok
}

Tugas bar di getUser3 gagal. Kesalahannya tampaknya berada di foo
image

Detail kesalahan

image

Jenis untuk bar sini adalah {} , yang sepertinya karena pada checked

function checked<T extends E, E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
    return o;
}

E tidak ditetapkan di mana pun. Namun jika kita mengganti typeof E dengan typeof {} , itu tidak berfungsi.

Tipe Enya apa? Apakah ada semacam hal yang sadar konteks terjadi?

@babakness Jika tidak ada tempat lain untuk menyimpulkan parameter tipe, TypeScript akan menyimpulkannya dari tipe yang dikembalikan. Jadi ketika kita menetapkan hasil dari checked untuk mengembalikan getUser* , E akan menjadi tipe pengembalian fungsi, dan T akan menjadi jenis sebenarnya dari nilai yang ingin Anda kembalikan. Jika tidak ada tempat untuk menyimpulkan E dari itu hanya akan default ke {} dan Anda akan selalu mendapatkan kesalahan.

Alasan saya melakukannya seperti ini adalah untuk menghindari parameter tipe eksplisit apa pun, Anda dapat membuat versi yang lebih eksplisit:

function checked<E>() {
    return function <T extends E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
        return o;
    }
}

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checked<{ username: string }>()(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checked<{ username: string }>()(foo) //ok
}

Catatan: Pendekatan fungsi kari diperlukan karena kami belum memiliki inferensi argumen parsial (https://github.com/Microsoft/TypeScript/pull/26349) sehingga kami tidak dapat menentukan beberapa parameter tipe dan membuat yang lain disimpulkan dalam panggilan yang sama. Untuk menyiasatinya, kita tentukan E di panggilan pertama dan biarkan T disimpulkan di panggilan kedua. Anda juga dapat meng-cache fungsi cache untuk tipe tertentu dan menggunakan versi yang di-cache

function checked<E>() {
    return function <T extends E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
        return o;
    }
}
const checkUser = checked<{ username: string }>()

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checkUser(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checkUser(foo) //ok
}

FWIW ini adalah aturan tslint WIP/sketsa yang memecahkan masalah khusus untuk tidak secara tidak sengaja mengembalikan properti tambahan dari metode "terbuka".

https://Gist.github.com/spion/b89d1d2958f3d3142b2fe64fea5e4c32

Untuk kasus penggunaan spread - lihat https://github.com/Microsoft/TypeScript/issues/12936#issuecomment -300382189 - dapatkah linter mendeteksi pola seperti ini dan memperingatkan bahwa itu tidak aman untuk tipe?

Menyalin contoh kode dari komentar di atas:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): State {
   return {
       ...state,
       fullName: action.payload // compiles, but it's an programming mistake
   }
}

cc @JamesHenry / @armano2

Sangat ingin melihat itu terjadi. Kami menggunakan definisi TypeScript yang dihasilkan untuk titik akhir GraphQL dan merupakan masalah bahwa TypeScript tidak memunculkan kesalahan ketika saya meneruskan objek dengan lebih banyak bidang daripada yang diperlukan ke kueri karena GraphQL akan gagal mengeksekusi kueri seperti itu saat runtime.

berapa banyak dari hal ini yang sekarang diatasi dengan pembaruan 3.5.1 dengan pemeriksaan yang lebih baik untuk properti tambahan selama penugasan? kami mendapat banyak area masalah yang diketahui ditandai sebagai kesalahan seperti yang kami inginkan setelah memutakhirkan ke 3.5.1

jika Anda memiliki masalah dan menurut Anda tipe yang tepat adalah solusi yang tepat, jelaskan masalah aslinya di sini

https://github.com/microsoft/TypeScript/issues/12936#issuecomment -284590083

Inilah yang melibatkan ref React: https://github.com/microsoft/TypeScript/issues/31798

/cc @RyanCavanaugh

Satu kasus penggunaan bagi saya adalah

export const mapValues =
  <T extends Exact<T>, V>(object: T, mapper: (value: T[keyof T], key: keyof T) => V) => {
    type TResult = Exact<{ [K in keyof T]: V }>;
    const result: Partial<TResult> = { };
    for (const [key, value] of Object.entries(object)) {
      result[key] = mapper(value, key);
    }
    return result as TResult;
  };

Ini tidak baik jika kita tidak menggunakan tipe yang tepat, karena jika object memiliki properti tambahan, tidak aman untuk memanggil mapper pada kunci dan nilai tambahan tersebut.

Motivasi sebenarnya di sini adalah saya ingin memiliki nilai untuk enum di suatu tempat yang dapat saya gunakan kembali dalam kode:

const choices = { choice0: true, choice1: true, choice2: true };
const callbacksForChoices = mapValues(choices, (_, choice) => () => this.props.callback(choice));

di mana this.props.callback memiliki tipe (keyof typeof choices) => void .

Jadi sebenarnya ini tentang sistem tipe yang dapat mewakili fakta bahwa saya memiliki daftar kunci dalam kode tanah yang sama persis dengan satu set (misalnya, gabungan) kunci dalam tipe tanah, sehingga kita dapat menulis fungsi yang beroperasi pada ini daftar kunci dan buat pernyataan tipe yang valid tentang hasilnya. Kami tidak dapat menggunakan objek ( choices dalam contoh saya sebelumnya) karena sejauh yang diketahui sistem tipe, objek kode-tanah dapat memiliki properti tambahan di luar tipe objek apa pun yang digunakan. Kita tidak dapat menggunakan array ( ['choice0', 'choice1', 'choice2'] as const , karena sejauh yang diketahui oleh sistem tipe, array mungkin tidak berisi semua kunci yang diizinkan oleh tipe array.

Mungkin exact seharusnya bukan tipe, tetapi hanya pengubah pada input dan/atau output fungsi? Sesuatu seperti pengubah varians aliran ( + / - )

Saya ingin menambahkan apa yang baru saja dikatakan Exact adalah agar kompiler menjamin bentuk fungsi. Ketika saya memiliki kerangka kerja, saya mungkin menginginkan salah satu dari ini: (T, S): AtMost<T> , (T, S): AtLeast<T> , atau (T, S): Exact<T> mana kompiler dapat memverifikasi bahwa fungsi yang didefinisikan pengguna akan cocok dengan tepat.

Beberapa contoh yang berguna:
AtMost berguna untuk konfigurasi (jadi kami tidak mengabaikan params/kesalahan ketik tambahan dan gagal lebih awal).
AtLeast sangat bagus untuk hal-hal seperti komponen reaksi dan middleware di mana pengguna dapat memasukkan tambahan apa pun yang mereka inginkan ke suatu objek.
Exact berguna untuk serialisasi/deserialisasi (kami dapat menjamin kami tidak menjatuhkan data dan ini isomorfik).

Apakah ini akan membantu mencegah hal ini terjadi?

interface IDate {
  year: number;
  month: number;
  day: number;
}

type TBasicField = string | number | boolean | IDate;

 // how to make this generic stricter?
function doThingWithOnlyCorrectValues<T extends TBasicField>(basic: T): void {
  // ... do things with basic field of only the exactly correct structures
}

const notADate = {
  year: 2019,
  month: 8,
  day: 30,
  name: "James",
};

doThingWithOnlyCorrectValues(notADate); // <- this should not work! I want stricter type checking

Kami benar-benar membutuhkan cara di TS untuk mengatakan T extends exactly { something: boolean; } ? xxx : yyy .

Atau sebaliknya, sesuatu seperti:

const notExact = {
  something: true,
  name: "fred",
};

Masih akan mengembalikan xxx sana.

Mungkin kata kunci const dapat digunakan? misalnya T extends const { something: boolean }

@pleerock mungkin sedikit ambigu, seperti dalam JavaScript / TypeScript kita dapat mendefinisikan variabel sebagai const tetapi masih menambahkan / menghapus properti objek. Saya pikir kata kunci exact cukup to the point.

Saya tidak yakin apakah itu benar-benar terkait, tetapi saya mengharapkan setidaknya dua kesalahan dalam kasus ini:
tempat bermain
Screen Shot 2019-08-08 at 10 15 34

@mityok saya pikir itu terkait. Saya kira Anda ingin melakukan sesuatu seperti:

class Animal {
  makeSound(): exact Foo {
     return { a: 5 };
  }
}

Jika exact membuat jenisnya lebih ketat - maka itu tidak boleh diperpanjang dengan properti tambahan, seperti yang Anda lakukan di Dog .

mengambil keuntungan dari const ( as const ) dan menggunakan antarmuka dan tipe sebelumnya, seperti

const type WillAcceptThisOnly = number

function f(accept: WillAcceptThisOnly) {
}

f(1) // error
f(1 as WillAcceptThisOnly) // ok, explicit typecast

const n: WillAcceptThisOnly = 1
f(n) // ok

akan benar-benar verbose harus menetapkan variabel const, tetapi akan menghindari banyak kasus Edge ketika Anda melewati typealias yang tidak persis seperti yang Anda harapkan

Saya telah menemukan solusi TypeScript murni untuk masalah Exact<T> yang, saya yakin, berperilaku persis seperti yang diminta di posting utama:

// (these two types MUST NOT be merged into a single declaration)
type ExactInner<T> = <D>() => (D extends T ? D : D);
type Exact<T> = ExactInner<T> & T;

function exact<T>(obj: Exact<T> | T): Exact<T> {
    return obj as Exact<T>;
};

Alasan ExactInner tidak boleh disertakan dalam Exact adalah karena perbaikan #32824 belum dirilis (tetapi sudah digabungkan dalam !32924 ).

Itu hanya mungkin untuk menetapkan nilai ke variabel atau argumen fungsi tipe Exact<T> , jika ekspresi tangan kanan juga Exact<T> , di mana T adalah tipe yang persis sama di kedua bagian tugas.

Saya belum mencapai promosi nilai secara otomatis ke dalam tipe Persis, jadi untuk itulah fungsi helper exact() . Nilai apa pun dapat dipromosikan menjadi tipe yang tepat, tetapi penugasan hanya akan berhasil jika TypeScript dapat membuktikan bahwa tipe dasar dari kedua bagian ekspresi tidak hanya dapat diperluas, tetapi persis sama.

Ia bekerja dengan memanfaatkan fakta bahwa TypeScript menggunakan pemeriksaan relasi extend untuk menentukan apakah tipe tangan kanan dapat ditetapkan ke tipe tangan kiri — itu hanya dapat dilakukan jika tipe tangan kanan (sumber) _memperluas_ tipe tangan kiri (tujuan) .

Mengutip checker.ts ,

// Dua tipe kondisional 'T1 meluas U1 ? X1 : Y1' dan 'T2 memanjang U2 ? X2 : Y2' berhubungan jika
// salah satu dari T1 dan T2 terkait dengan yang lain, U1 dan U2 adalah tipe yang identik, X1 terkait dengan X2,
// dan Y1 terkait dengan Y2.

ExactInner<T> generik menggunakan pendekatan yang dijelaskan, menggantikan U1 dan U2 dengan tipe dasar yang memerlukan pemeriksaan ketepatan. Exact<T> menambahkan persimpangan dengan tipe dasar biasa, yang memungkinkan TypeScript untuk melonggarkan tipe yang tepat ketika variabel target atau argumen fungsinya bukan tipe yang tepat.

Dari sudut pandang programmer, Exact<T> berperilaku seolah-olah ia menetapkan flag exact pada T , tanpa memeriksa T atau mengubahnya, dan tanpa membuat tipe independen.

Berikut adalah tautan taman bermain dan tautan inti .

Kemungkinan peningkatan di masa mendatang adalah mengizinkan promosi otomatis dari jenis yang tidak tepat menjadi jenis yang tepat, sepenuhnya menghilangkan kebutuhan dalam fungsi exact() .

Kerja luar biasa @toringen!

Jika ada yang bisa menemukan cara untuk membuat ini bekerja tanpa harus membungkus nilai Anda dalam panggilan ke exact itu akan menjadi sempurna.

Tidak yakin apakah ini masalah yang benar, tetapi ini adalah contoh sesuatu yang ingin saya kerjakan.

https://www.typescriptlang.org/play/#code/KYOwrgtgBAyg9gJwC4BECWDgGMlriKAbwCgooBBAZyygF4oByAQ2oYBpSoVhq7GATHlgbEAvsWIAzMCBx4CTfvwDyCQQgBCATwAU -DNlz4AXFABE5GAGEzUAD7mUAUWtmAlEQnjiilWuCauvDI6Jhy + AB0VFgRSHAAqgAOiQFWLMA6bm4A3EA

enum SortDirection {
  Asc = 'asc',
  Desc = 'desc'
}
function addOrderBy(direction: "ASC" | "DESC") {}
addOrderBy(SortDirection.Asc.toUpperCase());

@lookfirst Itu berbeda. Ini meminta fitur untuk tipe yang tidak menerima properti tambahan, seperti beberapa tipe exact {foo: number} mana {foo: 1, bar: 2} tidak dapat ditetapkan untuk itu. Itu hanya meminta transformasi teks untuk diterapkan ke nilai enum, yang kemungkinan tidak ada.

Tidak yakin apakah ini masalah yang benar, tetapi [...]

Dalam pengalaman saya sebagai pengelola di tempat lain, jika Anda ragu dan tidak dapat menemukan masalah yang ada, ajukan bug baru dan skenario terburuk, itu akan ditutup sebagai penipuan yang tidak Anda temukan. Ini cukup banyak terjadi di sebagian besar proyek JS open source utama. (Kebanyakan dari kita pengelola yang lebih besar di komunitas JS sebenarnya adalah orang-orang yang baik, hanya orang-orang yang bisa benar-benar terjebak karena laporan bug dan semacamnya sehingga sulit untuk tidak terlalu singkat.)

@isiahmeadows Terima kasih atas tanggapannya. Saya tidak mengajukan masalah baru karena saya mencari masalah duplikat terlebih dahulu, yang merupakan hal yang benar untuk dilakukan. Saya mencoba untuk menghindari menjebak orang karena saya tidak yakin apakah ini masalah yang benar atau tidak atau bahkan bagaimana mengkategorikan apa yang saya bicarakan.

DIEDIT: Periksa solusi @aigoncharov di bawah , karena menurut saya lebih cepat.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

Tidak tahu apakah ini bisa ditingkatkan lagi.

type Exact<T, Shape> =
    // Check if `T` is matching `Shape`
    T extends Shape
        // Does match
        // Check if `T` has same keys as `Shape`
        ? Exclude<keyof T, keyof Shape> extends never
            // `T` has same keys as `Shape`
            ? T
            // `T` has more keys than `Shape`
            : never
        // Does not match at all
        : never;

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)

Tanpa komentar

type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never
    ? T1
    : never

type Exact<T, Shape> = T extends Shape
    ? ExactKeys<T, Shape>
    : never;

Tidak tahu apakah ini bisa ditingkatkan lagi.

type Exact<T, Shape> =
    // Check if `T` is matching `Shape`
    T extends Shape
        // Does match
        // Check if `T` has same keys as `Shape`
        ? Exclude<keyof T, keyof Shape> extends never
            // `T` has same keys as `Shape`
            ? T
            // `T` has more keys than `Shape`
            : never
        // Does not match at all
        : never;

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)

Tanpa komentar

type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never
    ? T1
    : never

type Exact<T, Shape> = T extends Shape
    ? ExactKeys<T, Shape>
    : never;

Cintai ide itu!

Trik lain yang bisa melakukan pekerjaan itu adalah memeriksa penugasan di kedua arah.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type A = {
  prop1: string
}
type B = {
  prop1: string
  prop2: string
}
type C = {
  prop1: string
}

type ShouldBeNever = Exact<A, B>
type ShouldBeA = Exact<A, C>

http://www.typescriptlang.org/play/#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA + KAXinSgjmAgDsATAZ1wCgooB + XMi6 + k5lt3vygAuKFQgA3CACc + o8VNmNQkKAEEiUAN58w0gPZgAjKLrBpASyoBzRgF9l4aACFNOlnsMmoZyzd0GYABMpuZWtg4q0ADCbgFeoX4RjI6qAMoAFvoArgA2NM4QAHKSMprwyGhq2M54qdCZOfmFGsQVKKjVUNF1QkA

Lain bermain dari @iamandrewluca https://www.typescriptlang.org/play/?ssl=7&ssc=6&pln=7&pc=17#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA + KAXinSgjmAgDsATAZ1wCgooB + XMi6 + k5lt3vygAuKFQgA3CACc + o8VNmNQkKAElxiFOnDRiAbz4sAZgHtTousGkBLKgHNGAX0aMkpqlaimARgCsiKEMhMwsoAHJQ8MwjKB8EaVFw + Olw51djAFcqFBsPKEorAEYMPAAKYFF4ZDQsdU0anUg8AEoglyyc4DyqAogrACYK0Q1yRt02-RdlfuAist8-NoB6ZagAEnhIFBhpaVNZQuAhxZagA

Nuansa di sini adalah apakah Exact<{ prop1: 'a' }> harus dapat ditetapkan ke Exact<{ prop1: string }> . Dalam kasus penggunaan saya, seharusnya.

@jeremybparagon kasus Anda tercakup. Berikut adalah beberapa kasus lagi.

type InexactType = {
    foo: 'foo'
}

const obj = {
    // here foo is infered as `string`
    // and will error because `string` is not assignable to `"foo"`
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj) // $ExpectError
type InexactType = {
    foo: 'foo'
}

const obj = {
    // here we cast to `"foo"` type
    // and will not error
    foo: 'foo' as 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)
type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

Saya pikir siapa pun yang menggunakan trik ini (dan saya tidak mengatakan tidak ada kegunaan yang valid untuk itu) harus sangat menyadari bahwa sangat mudah untuk mendapatkan lebih banyak alat peraga dalam jenis "tepat". Karena InexactType dapat ditetapkan ke Exact<T, InexactType> jika Anda memiliki sesuatu seperti ini, Anda keluar dari ketepatan tanpa menyadarinya:

function test1<T>(t: Exact<T, InexactType>) {}

function test2(t: InexactType) {
  test1(t); // inexactType assigned to exact type
}
test2(obj) // but 

Tautan Taman Bermain

Ini adalah alasan (setidaknya salah satunya) bahwa TS tidak memiliki tipe yang tepat, karena akan memerlukan percabangan lengkap dari tipe objek dalam tipe eksak vs non-tepat di mana tipe yang tidak tepat tidak pernah dapat ditetapkan ke yang tepat, bahkan jika pada nilai nominal mereka kompatibel. Jenis yang tidak tepat mungkin selalu berisi lebih banyak properti. (Setidaknya ini adalah salah satu alasan @ahejlsberg disebut sebagai tsconf).

Jika asExact adalah cara sintaksis untuk menandai objek yang tepat, seperti inilah solusi yang mungkin terlihat:

declare const exactMarker: unique symbol 
type IsExact = { [exactMarker]: undefined }
type Exact<T extends IsExact & R, R> =
  Exclude<keyof T, typeof exactMarker> extends keyof R? T : never;

type InexactType = {
    foo: string
}
function asExact<T>(o: T): T & IsExact { 
  return o as T & IsExact;
}

const obj = asExact({
  foo: 'foo',
});


function test1<T extends IsExact & InexactType>(t: Exact<T, InexactType>) {

}

function test2(t: InexactType) {
  test1(t); // error now
}
test2(obj) 
test1(obj);  // ok 

const obj2 = asExact({
  foo: 'foo',
  bar: ""
});
test1(obj2);

const objOpt = asExact < { foo: string, bar?: string }>({
  foo: 'foo',
  bar: ""
});
test1(objOpt);

Tautan Taman Bermain

@dragomirtitian itu sebabnya saya datang dengan solusi sedikit lebih awal https://github.com/microsoft/TypeScript/issues/12936#issuecomment -524631270 yang tidak menderita ini.

@dragomirtitian ini masalah bagaimana Anda mengetikkan fungsi Anda.
Jika Anda melakukannya sedikit berbeda, itu berhasil.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}

function test2<T extends InexactType>(t: T) {
  test1(t); // fails
}
test2(obj)

https://www.typescriptlang.org/play/#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA + KAXinSgjmAgDsATAZ1wCgooB + XMi6 + k5lt3vygAuKFQgA3CACc + o8VNmNQkKAElxiFOnDRiAbz4sAZgHtTousGkBLKgHNGAX0aMkpqlaimARgCsiKEMhMwsoAHJQ8MwjKB8EaVFw + Olw51djAFcqFBsPKEorAEYMPAAKYFF4ZDQsdU0anUg8AEogl0YsnOA8qgKIKwAmDE5KWgYNckbdcsqSNuD + 4oqWgG4oAHoNqGMEGwAbOnTC4EGy3z82oA

@jeremybparagon kasus Anda tercakup.

@iamandrewluca Saya pikir solusi di sini dan di sini berbeda tentang bagaimana mereka memperlakukan contoh saya .

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type A = {
  prop1: 'a'
}
type C = {
  prop1: string
}

type ShouldBeA = Exact<A, C> // This evaluates to never.

const ob...

Tautan Taman Bermain

@aigoncharov Masalahnya adalah Anda harus menyadari hal itu sehingga orang tidak dapat dengan mudah melakukan ini dan test1 masih dapat dipanggil dengan properti tambahan. IMO solusi apa pun yang dapat dengan mudah memungkinkan penugasan tidak tepat yang tidak disengaja telah gagal karena intinya adalah untuk menegakkan ketepatan dalam sistem tipe.

@toringen ya solusi Anda tampaknya lebih baik, saya hanya mengacu pada solusi yang diposting terakhir. Solusi Anda memiliki fakta bahwa Anda tidak memerlukan parameter tipe fungsi tambahan, namun tampaknya tidak berfungsi dengan baik untuk properti opsional:

// (these two types MUST NOT be merged into a single declaration)
type ExactInner<T> = <D>() => (D extends T ? D : D);
type Exact<T> = ExactInner<T> & T;
type Unexact<T> = T extends Exact<infer R> ? R : T;

function exact<T>(obj: Exact<T> | T): Exact<T> {
    return obj as Exact<T>;
};

////////////////////////////////
// Fixtures:
type Wide = { foo: string, bar?: string };
type Narrow = { foo: string };
type ExactWide = Exact<Wide>;
type ExactNarrow = Exact<Narrow>;

const ew: ExactWide = exact<Wide>({ foo: "", bar: ""});
const assign_en_ew: ExactNarrow = ew; // Ok ? 

Tautan Taman Bermain

@jeremybparagon Saya tidak yakin solusi @aigoncharov melakukan pekerjaan dengan baik pada properti opsional. Solusi apa pun yang didasarkan pada T extends S dan S extends T akan mengalami fakta sederhana bahwa

type A = { prop1: string }
type C = { prop1: string,  prop2?: string }
type CextendsA = C extends A ? "Y" : "N" // Y 
type AextendsC = A extends C ? "Y" : "N" // also Y 

Tautan Taman Bermain

Saya pikir @iamandrewluca menggunakan Exclude<keyof T, keyof Shape> extends never baik, tipe saya sangat mirip (saya mengedit jawaban asli saya untuk menambahkan &R untuk memastikan T extends R tanpa pemeriksaan tambahan).

type Exact<T extends IsExact & R, R> =
  Exclude<keyof T, typeof exactMarker> extends keyof R? T : never;

Saya tidak akan mempertaruhkan reputasi saya bahwa solusi saya tidak memiliki lubang, saya belum mencarinya dengan susah payah tetapi menyambut setiap temuan seperti itu 😊

kita harus memiliki tanda di mana ini diaktifkan secara global. Dengan cara ini, siapa yang ingin tipe longgar dapat terus melakukan hal yang sama. Terlalu banyak bug yang disebabkan oleh masalah ini. Sekarang saya mencoba untuk menghindari spread operator dan menggunakan pickKeysFromObject(shipDataRequest, ['a', 'b','c'])

Inilah kasus penggunaan untuk tipe persis yang baru-baru ini saya temukan:

type PossibleKeys = 'x' | 'y' | 'z';
type ImmutableMap = Readonly<{ [K in PossibleKeys]?: string }>;

const getFriendlyNameForKey = (key: PossibleKeys) => {
    switch (key) {
        case 'x':
            return 'Ecks';
        case 'y':
            return 'Why';
        case 'z':
            return 'Zee';
    }
};

const myMap: ImmutableMap = { x: 'foo', y: 'bar' };

const renderMap = (map: ImmutableMap) =>
    Object.keys(map).map(key => {
        // Argument of type 'string' is not assignable to parameter of type 'PossibleKeys'
        const friendlyName = getFriendlyNameForKey(key);
        // No index signature with a parameter of type 'string' was found on type 'Readonly<{ x?: string | undefined; y?: string | undefined; z?: string | undefined; }>'.    
        return [friendlyName, map[key]];
    });
;

Karena tipe tidak tepat secara default, Object.keys harus mengembalikan string[] (lihat https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208), tetapi dalam kasus ini , jika ImmutableMap tepat, tidak ada alasan ia tidak dapat mengembalikan PossibleKeys[] .

@dallonf perhatikan bahwa contoh ini memerlukan fungsionalitas tambahan selain hanya tipe yang tepat -- Object.keys hanyalah sebuah fungsi dan perlu ada beberapa mekanisme untuk menjelaskan fungsi yang mengembalikan keyof T untuk tipe yang tepat dan string untuk jenis lainnya. Hanya memiliki opsi untuk mendeklarasikan tipe yang tepat tidak akan cukup.

@RyanCavanaugh Saya pikir itu implikasinya, tipe yang tepat + kemampuan untuk mendeteksinya.

Gunakan case untuk pengetikan reaksi:

forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string }) => ComponentType<P> .

Sangat menggoda untuk meneruskan komponen reguler ke forwardRef itulah sebabnya React mengeluarkan peringatan runtime jika mendeteksi propTypes atau defaultProps pada argumen render . Kami ingin mengekspresikan ini pada level tipe tetapi harus mundur ke never :

- forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string }) => ComponentType<P>
+ forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string, propTypes?: never, defaultProps?: never }) => ComponentType<P>

Pesan kesalahan dengan never tidak membantu ("{} is not assignable to undefined").

Adakah yang bisa membantu saya tentang bagaimana solusi @toringen akan terlihat dengan gabungan bentuk objek acara yang berbeda? Saya ingin membatasi bentuk acara saya dalam panggilan redux-dispatch, misalnya:

type StoreEvent =
  | { type: 'STORE_LOADING' }
  | { type: 'STORE_LOADED'; data: unknown[] }

Tidak jelas bagaimana saya bisa membuat fungsi dispatch() yang diketik yang hanya menerima bentuk yang tepat dari suatu peristiwa.

(PEMBARUAN: Saya menemukan jawabannya: https://Gist.github.com/sarimarton/d5d539f8029c01ca1c357aba27139010)

Kasus penggunaan:

Tidak adanya dukungan Exact<> menyebabkan masalah runtime dengan mutasi GraphQL. GraphQL menerima daftar persis properti yang diizinkan. Jika Anda memberikan alat peraga yang berlebihan, itu menimbulkan kesalahan.

Jadi ketika kami memperoleh beberapa data dari formulir, maka TypeScript tidak dapat memvalidasi properti kelebihan (ekstra). Dan kita akan mendapatkan kesalahan saat runtime.

Contoh berikut menggambarkan keamanan imajiner

  • dalam kasus pertama melacak semua parameter input
  • tetapi dalam kehidupan nyata (seperti kasus kedua ketika kami mendapatkan data dari formulir dan menyimpannya ke beberapa variabel) TypeScript

Coba di Taman Bermain

Screen Shot 2020-03-05 at 13 04 38

Menurut artikel https://fettblog.eu/typescript-match-the-exact-object-shape/ dan solusi serupa yang disediakan di atas, kita dapat menggunakan solusi jelek berikut:

Screen Shot 2020-03-05 at 12 26 57

Mengapa solusi savePerson<T>(person: ValidateShape<T, Person>) ini Jelek?

Asumsikan Anda memiliki tipe input yang sangat bersarang misalnya .:

// Assume we are in the ideal world where implemented Exact<>

type Person {
  name: string;
  address: Exact<Address>;
}

type Address {
   city: string
   location: Exact<Location>
}

type Location {
   lon: number;
   lat: number; 
}

savePerson(person: Exact<Person>)

Saya tidak dapat membayangkan spaghetti apa yang harus kita tulis untuk mendapatkan perilaku yang sama dengan solusi yang tersedia saat ini:

savePerson<T, TT, TTT>(person: 
  ValidateShape<T, Person keyof ...🤯...
     ValidateShape<TT, Address keyof ...💩... 
         ValidateShape<TTT, Location keyof ...🤬... 
> > >)

Jadi, untuk saat ini, kami memiliki lubang besar dalam analisis statis dalam kode kami, yang bekerja dengan data input bersarang yang kompleks.

Kasus yang dijelaskan pada gambar pertama, di mana TS tidak memvalidasi kelebihan properti karena "kesegaran" hilang, juga sedikit menyulitkan kami.

Menulis

doSomething({
  /* large object of options */
})

sering terasa jauh lebih tidak mudah dibaca daripada

const options = {
  /* large object of options */
}
doSomething(options)

Anotasi const options: DoSomethingOptions = { eksplisit membantu, tetapi agak rumit dan sulit dikenali dan diterapkan dalam tinjauan kode.

Ini sedikit ide di luar topik dan tidak akan menyelesaikan sebagian besar kasus penggunaan untuk ketepatan yang dijelaskan di sini, tetapi apakah mungkin untuk menjaga objek tetap segar ketika hanya digunakan sekali di dalam lingkup terlampir?

@RyanCavanaugh terima kasih telah menjelaskan EPC ... apakah perbedaan antara EPC dan tipe yang tepat dibahas lebih detail di mana saja? Sekarang saya merasa harus mendapatkan pemahaman yang lebih baik tentang mengapa EPC mengizinkan beberapa kasus yang tidak dimiliki oleh tipe yang tepat.

Hai @noppa, saya pikir itu ide yang bagus. Saya baru saja menemukan ini ketika saya melihat perbedaan antara menugaskan secara langsung versus menetapkan variabel terlebih dahulu - bahkan mengajukan pertanyaan pada SO yang membawa saya ke sini. Perilaku saat ini mengejutkan, setidaknya bagi saya ...

Saya yakin saya memiliki masalah yang sama dengan contoh mutasi GraphQL (pengetikan bersarang yang tepat, tidak ada properti tambahan yang diizinkan). Dalam kasus saya, saya berpikir untuk mengetik respons API dalam modul umum (dibagi antara frontend dan backend):

export type ProductsSlashResponse = {
  products: Array<{
    id: number;
    description: string;
  }>,
  total: number;
};

Di sisi server, saya ingin memastikan bahwa responsnya menghormati tanda tangan jenis itu:

router.get("products/", async () =>
  assertType<ProductsSlashResponse>(getProducts())));

Saya telah mencoba solusi dari sini. Salah satu yang tampaknya berfungsi adalah T extends U ? U extends T ? T : never : never , bersama dengan fungsi kari yang tidak ideal. Masalah utama dengan itu adalah bahwa Anda tidak mendapatkan umpan balik tentang properti yang hilang atau ekstra (mungkin kami dapat memperbaikinya, tetapi menjadi sulit untuk dilakukan ketika kami masuk ke properti bersarang). Solusi lain tidak berfungsi dengan objek yang sangat bersarang.

Tentu saja, frontend biasanya tidak akan mogok jika saya mengirim lebih banyak informasi daripada yang ditentukan, namun, ini dapat menyebabkan kebocoran informasi jika API mengirim lebih banyak informasi daripada yang seharusnya (dan karena sifat berkabut dalam membaca data dari database tipe mana yang tidak selalu sinkron dengan kode sepanjang waktu, ini bisa terjadi).

@fer22f GraphQL tidak mengirim bidang yang tidak diminta klien...kecuali jika Anda menggunakan tipe skalar JSON untuk products atau untuk elemen array, tidak ada yang perlu dikhawatirkan di sana.

Maaf saya salah membaca, saya pikir maksud Anda Anda menggunakan GraphQL

Seseorang telah menyebutkan GraphQL, tetapi hanya dalam hal "mengumpulkan kasus penggunaan" ( @DanielRosenwasser disebutkan beberapa tahun yang lalu di utas :-) dari "tidak memiliki kasus penggunaan apa pun"), dua kasus penggunaan di mana saya ingin gunakan Exact adalah:

  1. Melewati data ke penyimpanan data / database / ORM--bidang tambahan apa pun yang diteruskan akan dihapus secara diam-diam / tidak disimpan.

  2. Melewati data ke panggilan kawat / RPC / REST / GraphQL--sekali lagi bidang tambahan apa pun yang diteruskan akan dihapus secara diam-diam / tidak dikirim.

(Yah, mungkin tidak dijatuhkan secara diam-diam, itu bisa menjadi kesalahan runtime.)

Dalam kedua kasus saya ingin memberi tahu programmer/diri saya sendiri (melalui kesalahan kompilasi) "... Anda benar-benar tidak boleh memberi saya properti tambahan ini, b/c jika Anda mengharapkannya untuk 'disimpan' atau ' dikirim', itu tidak akan".

Ini terutama diperlukan dalam API gaya "pembaruan sebagian", yaitu tipe lemah:

type Data = { firstName:? string; lastName?: string; children?: [{ ... }] };
const data = { firstName: "a", lastNmeTypo: "b" };
await saveDataToDbOrWireCall(data);

Melewati pemeriksaan tipe lemah b/c setidaknya satu param cocok, firstName , jadi itu tidak 100% terputus-putus, namun masih ada kesalahan ketik "jelas" dari lsatNmeTypo yang tidak tertangkap.

Memang, EPC berfungsi jika saya melakukan:

await saveDataToDbOrWireCall({ firstName, lastNmeTypo });

Tetapi harus merusak + mengetik ulang setiap bidang cukup membosankan.

Solusi seperti @jcalz 's Exactify bekerja pada properti tingkat 1, tetapi kasus rekursif (yaitu children adalah array dan elemen array harus tepat) Saya berjuang dengan setelah hits kasus penggunaan "dunia nyata" dengan obat generik / seperti Exact<Foo<Bar<T>> .

Akan sangat bagus untuk memiliki built-in ini, dan hanya ingin mencatat kasus penggunaan eksplisit ini (pada dasarnya panggilan telepon dengan tipe parsial/lemah), jika itu membantu dengan prioritas/pemetaan jalan.

(FWIW https://github.com/stephenh/joist-ts/pull/35/files memiliki upaya saya saat ini pada Exact dan juga Exact.test.ts yang melewati kasus-kasus sepele, tetapi PR itu sendiri memiliki kesalahan kompilasi pada penggunaan yang lebih esoteris. Penafian Saya tidak benar-benar mengharapkan siapa pun untuk melihat PR khusus ini, tetapi saya hanya memberikannya sebagai "di sinilah Exact akan berguna" + "AFAICT ini sulit dilakukan di titik data pengguna-tanah".)

Hai,

Ingin tahu apa pendapat tim TS tentang jenis catatan dan proposal tupel yang tepat di sini? https://github.com/tc39/proposal-record-tuple

Apakah masuk akal untuk memperkenalkan tipe yang tepat untuk primitif baru itu?

@slorber Bukan TS, tapi itu ortogonal. Proposal itu menyangkut kekekalan, dan kekhawatirannya hampir identik antara itu dan perpustakaan seperti Immutable.js.

Saya mengulangi pada versi rekursif @stephenh . Saya mengalami masalah dalam menangani kasus yang tidak terdefinisi dengan benar dengan rekursi, saya terbuka untuk solusi yang lebih bersih. Ini mungkin tidak berfungsi pada beberapa kasus Edge dengan array atau struktur data yang kompleks.

export type Exact<Expected, Actual> = Expected &
  Actual & // Needed to infer `Actual`
  (null extends Actual
    ? null extends Expected
      ? Actual extends null // If only null stop here, because NonNullable<null> = never
        ? null
        : CheckUndefined<Expected, Actual>
      : never // Actual can be null but not Expected: forbid the field
    : CheckUndefined<Expected, Actual>);

type CheckUndefined<Expected, Actual> = undefined extends Actual
  ? undefined extends Expected
    ? Actual extends undefined // If only undefined stop here, because NonNullable<undefined> = never
      ? undefined
      : NonNullableExact<NonNullable<Expected>, NonNullable<Actual>>
    : never // Actual can be undefined but not Expected: forbid the field
  : NonNullableExact<NonNullable<Expected>, NonNullable<Actual>>;

type NonNullableExact<Expected, Actual> = {
  [K in keyof Actual]: K extends keyof Expected
    ? Actual[K] extends (infer ActualElement)[]
      ? Expected[K] extends (infer ExpectedElement)[] | undefined | null
        ? Exact<ExpectedElement, ActualElement>[]
        : never // Not both array
      : Exact<Expected[K], Actual[K]>
    : never; // Forbid extra properties
};

tempat bermain

Exact akan sangat berguna bagi kami saat mengembalikan respons API. Saat ini, inilah yang kami putuskan untuk:

const response = { companies };

res.json(exact<GetCompaniesResponse, typeof response>(response));
export function exact<S, T>(object: Exact<S, T>) {
  return object;
}

Di sini tipe Exact adalah apa yang disediakan @ArnaudBarre di atas.

Terima kasih @ArnaudBarre telah membuka blokir saya dan mengajari saya beberapa ts.
Mengungkap solusi Anda:

export type Exact<Expected, Actual> =
  keyof Expected extends keyof Actual
    ? keyof Actual extends keyof Expected
      ? Expected extends ExactElements<Expected, Actual>
        ? Expected
        : never
      : never
    : never;

type ExactElements<Expected, Actual> = {
  [K in keyof Actual]: K extends keyof Expected
    ? Expected[K] extends Actual[K]
      ? Actual[K] extends Expected[K]
        ? Expected[K]
        : never
      : never
    : never
};

// should succeed (produce exactly the Expected type)
let s1: Exact< { a: number; b: string }, { a: number; b: string } >;
let s2: Exact< { a?: number; b: string }, { a?: number; b: string } >;
let s3: Exact< { a?: number[]; b: string }, { a?: number[]; b: string } >;
let s4: Exact< string, string >;
let s5: Exact< string[], string[] >;
let s6: Exact< { a?: number[]; b: string }[], { a?: number[]; b: string }[] >;

// should fail (produce never)
let f1: Exact< { a: string; b: string }, { a: number; b: string } >;
let f2: Exact< { a: number; b: string }, { a?: number; b: string } >;
let f3: Exact< { a?: number; b: string }, { a: number; b: string } >;
let f4: Exact< { a: number[]; b: string }, { a: string[]; b: string } >;
let f5: Exact< { a?: number[]; b: string }, { a: number[]; b: string } >;
let f6: Exact< { a?: number; b: string; c: string }, { a?: number; b: string } >;
let f7: Exact< { a?: number; b: string }, { a?: number; b: string; c: string } >;
let f8: Exact< { a?: number; b: string; c?: string }, { a?: number; b: string } >;
let f9: Exact< { a?: number; b: string }, { a?: number; b: string; c?: string } >;
let f10: Exact< never, string >;
let f11: Exact< string, never >;
let f12: Exact< string, number >;
let f13: Exact< string[], string >;
let f14: Exact< string, string[] >;
let f15: Exact< string[], number[] >;
let f16: Exact< { a?: number[]; b: string }[], { a?: number[]; b: string } >;

Solusi sebelumnya 'berhasil' untuk f6, f8 dan f9.
Solusi ini juga mengembalikan hasil 'bersih'; ketika cocok, Anda mendapatkan kembali tipe 'Diharapkan'.
Seperti komentar @ArnaudBarre ... tidak yakin apakah semua kasus tepi ditangani, jadi ymmv ...

@heystewart Exact Anda tidak memberikan hasil simetris:

let a: Exact< { foo: number }[], { foo: number, bar?: string }[] >;
let b: Exact< { foo: number, bar?: string }[], { foo: number }[] >;

a = [{ foo: 123, bar: 'bar' }]; // error
b = [{ foo: 123, bar: 'bar' }]; // no error

Sunting: Versi @ArnaudBarre juga memiliki masalah yang sama

@papb Ya secara efektif pengetikan saya tidak berfungsi adalah titik masuknya adalah array. Saya membutuhkannya untuk API graphQL kami di mana variables selalu menjadi objek.

Untuk mengatasinya, Anda perlu mengisolasi ExactObject dan ExactArray dan memiliki titik masuk yang masuk ke satu atau yang lain.

Jadi apa cara terbaik untuk memastikan bahwa objek memiliki properti yang tepat, tidak kurang, tidak lebih?

@captain-yossarian meyakinkan tim TypeScript untuk mengimplementasikan ini. Tidak ada solusi yang disajikan di sini yang berfungsi untuk semua kasus yang diharapkan, dan hampir semuanya kurang jelas.

@toriningen tidak bisa membayangkan berapa banyak masalah yang akan ditutup jika tim TS akan menerapkan fitur ini

@RyanCavanaugh
Saat ini, saya memiliki satu kasus penggunaan yang membawa saya ke sini, dan itu langsung masuk ke topik Anda "Lain-lain". Saya ingin fungsi yang:

  1. mengambil parameter yang mengimplementasikan antarmuka dengan parameter opsional
  2. mengembalikan objek yang diketik ke antarmuka aktual yang lebih sempit dari parameter yang diberikan, sehingga

Tujuan langsung tersebut melayani tujuan ini:

  1. Saya mendapatkan kelebihan properti memeriksa input
  2. Saya mendapatkan penyelesaian otomatis dan keamanan tipe properti untuk output

Contoh

Saya telah mengurangi kasus saya menjadi ini:

type X = {
    red?: number,
    green?: number,
    blue?: number,
}

function y<
    Y extends X
>(
    y: (X extends Y ? Y : X)
) {
    if ((y as any).purple) throw Error('bla')

    return y as Y
}

const z = y({
    blue: 1,
    red: 3,
    purple: 4, // error
})
z.green // error

type Z = typeof z

Pengaturan itu berfungsi dan menyelesaikan semua tujuan yang diinginkan, jadi dari sudut pandang kelayakan murni dan sejauh ini, saya baik-baik saja. Namun, EPC dicapai melalui parameter mengetik (X extends Y ? Y : X) . Saya pada dasarnya menemukan itu secara kebetulan, dan saya agak terkejut bahwa itu berhasil.

Usul

Dan itulah mengapa saya ingin memiliki kata kunci implements yang dapat digunakan sebagai pengganti extends untuk menandai maksud bahwa tipe di sini tidak seharusnya memiliki kelebihan properti. Seperti:

type X = {
    red?: number,
    green?: number,
    blue?: number,
}

function x<
    Y implements X
>( y: Y ) {
    if ((y as any).purple) throw Error('bla')

    return y as Y
}

const z = y({
    blue: 1,
    red: 3,
    purple: 4, // error
})
z.green // error

type Z = typeof z

Ini tampaknya jauh lebih jelas bagi saya daripada solusi saya saat ini. Selain lebih ringkas, ia menempatkan seluruh batasan dengan deklarasi generik sebagai lawan dari pemisahan saya saat ini antara generik dan parameter.

Itu juga memungkinkan kasus penggunaan lebih lanjut yang saat ini tidak mungkin atau tidak praktis, tetapi saat ini hanya firasat.

Deteksi Tipe Lemah sebagai Alternatif

Khususnya, Deteksi Tipe Lemah sesuai #3842 harus memperbaikinya juga, dan mungkin menguntungkan karena tidak memerlukan sintaks tambahan, jika berfungsi sehubungan dengan extends , sesuai kasus penggunaan saya.

Mengenai Exact<Type> dll.

Akhirnya, implements , seperti yang saya bayangkan, seharusnya cukup jelas mengenai poin Anda tentang function f<T extends Exact<{ n: number }>(p: T) karena tidak mencoba untuk memecahkan kasus yang lebih umum dari Exact<Type> .

Secara umum, Exact<Type> tampaknya memiliki utilitas yang agak kecil di samping EPC, dan saya tidak dapat membayangkan kasus yang valid dan berguna secara umum yang berada di luar grup ini:

  • panggilan fungsi: ini dapat dengan mudah ditangani sekarang sesuai contoh saya, dan akan mendapat manfaat dari implements
  • tugas: cukup gunakan literal, jadi EPC berlaku
  • data dari luar domain kontrol Anda: pemeriksaan tipe tidak dapat melindungi Anda dari hal itu, Anda harus menanganinya saat runtime, di mana Anda kembali ke gips yang aman

Jelas, akan ada kasus ketika Anda tidak dapat menetapkan literal, tetapi ini juga harus dari himpunan yang terbatas:

  • jika Anda diberi data penugasan dalam suatu fungsi, tangani pemeriksaan jenis di tanda tangan panggilan
  • jika Anda menggabungkan beberapa objek, sesuai OP, maka tegaskan setiap jenis objek sumber dengan benar dan Anda dapat dengan aman melemparkan as DesiredType

Ringkasan: implements akan menyenangkan tetapi sebaliknya kami baik

Singkatnya, saya yakin bahwa dengan implements dan memperbaiki EPC (jika dan ketika masalah muncul), tipe yang tepat harus benar-benar ditangani.

Pertanyaan untuk semua pihak yang berkepentingan: apakah ada yang benar-benar terbuka di sini?

Setelah melihat kasus penggunaan di sini, saya pikir hampir semua repro ditangani dengan benar sekarang, dan sisanya dapat dibuat untuk bekerja dengan contoh kecil saya di atas. Itu menimbulkan pertanyaan: apakah ada yang masih memiliki masalah tentang ini hari ini dengan TS terbaru?

Saya memiliki ide yang belum matang Tentang anotasi tipe. Mencocokkan suatu benda yang dibagi menjadi anggota bisa sama persis, tidak lebih dan tidak kurang, lebih atau kurang, tidak lebih tetapi tidak kurang, lebih dan tidak kurang. Untuk setiap kasus di atas, harus ada satu ekspresi.

sama persis, yaitu tidak lebih dan tidak kurang:

function foo(p:{|x:any,y:any|})

//it matched 
foo({x,y})
//no match
foo({x})
foo({y})
foo({x,y,z})
foo({})

lebih tetapi tidak kurang:

function foo(p:{|x:any,y:any, ...|})

//it matched 
foo({x,y})
foo({x,y,z})

//no matched
foo({x})
foo({y})
foo({x,z})

tidak lebih tetapi kurang:

function foo(p:{x:any,y:any})

//it matched 
foo({x,y})
foo({x})
foo({y})

//no match
foo({x,z})
foo({x,y,z})

lebih atau kurang:

function foo(p:{x:any,y:any, ...})

//it matched 
foo({x,y})
foo({x})
foo({y})
foo({x,z})
foo({x,y,z})

kesimpulan:

Dengan garis vertikal menunjukkan bahwa tidak ada yang kurang, tanpa garis vertikal berarti ada yang kurang. Dengan tanda elipsis berarti bisa lebih banyak, tanpa tanda elipsis berarti tidak ada lagi. Pencocokan array adalah ide yang sama.

function foo(p:[|x,y|]) // p.length === 2
function foo(p:[|x,y, ... |]) // p.length >= 2
function foo(p:[x,y]) // p.length >= 0
function foo(p:[x,y,...]) // p.length >= 0

@rasenplanscher menggunakan contoh Anda, kompilasi ini:

const x = { blue: 1, red: 3, purple: 4 };
const z = y(x);

Namun dengan jenis yang tepat, seharusnya tidak. Yaitu meminta di sini adalah untuk tidak bergantung pada EPC.

@xp44mm "lebih tetapi tidak kurang" sudah menjadi perilaku dan "kurang lebih" adalah perilaku jika Anda menandai semua properti opsional

function foo(p:{x?: any, y?: any}) {}
const x = 1, y = 1, z = 1
// all pass
foo({x,y})
foo({x})
foo({y})
const p1 = {x,z}
foo(p1)
const p2 = {x,y,z}
foo(p2)

Demikian pula, jika kita memiliki tipe yang tepat, tipe yang tepat + semua properti opsional pada dasarnya adalah "tidak lebih tetapi kurang" .

Contoh lain untuk masalah ini. Demonstrasi yang bagus untuk proposal ini saya pikir. Dalam hal ini saya menggunakan rxjs untuk bekerja dengan Subjek tetapi ingin mengembalikan ("terkunci") Observable (yang tidak memiliki next , error , dll. metode untuk memanipulasi nilai.)

someMethod(): Observable<MyType> {
  const subject = new Subject<MyType>();

  // This works, but should not. (if this proposal is implemented.)
  return subject;

  // Only Observable should be allowed as return type.
  return subject.asObservable();
}

Saya selalu ingin hanya mengembalikan tipe persis Observable dan bukan Subject yang memperluasnya.

Usul:

// Adding exclamation mark `!` (or something else) to match exact type. (or some other position `method(): !Foo`, ...)
someMethod()!: Observable<MyType> {
  // ...
}

Tapi saya yakin Anda punya ide yang lebih baik. Terutama karena ini tidak hanya memengaruhi nilai pengembalian, bukan? Bagaimanapun, hanya demo kode semu. Saya pikir itu akan menjadi fitur yang bagus untuk menghindari kesalahan dan kekurangan. Seperti dalam kasus yang dijelaskan di atas. Solusi lain dapat menambahkan Jenis Utilitas baru.
Atau apakah saya melewatkan sesuatu? Apakah ini sudah bekerja? Saya menggunakan TypeScript 4.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat