Tujuannya adalah untuk menunjukkan di mana TS memberikan ilusi keamanan, yang memungkinkan Anda untuk mendapatkan kesalahan saat program sedang berjalan.Kami tidak akan membicarakan bug, di TS ada cukup1.500 bug terbuka dan 6.000 ditutup ('is: masalah adalah: label terbuka: Bug').Semua contoh akan dipertimbangkan dengan:- Mode ketat TS aktif (menulis artikel sambil memahami)
- Tanpa "any": "as any", "Objects", "Function", {[key: string]: tidak diketahui}
- Tanpa "apa pun" tersirat: (noImplicitAny): impor yang tidak diketik (file JS murni), inferensi jenis yang salah
- Tanpa tebakan salah tentang jenis: respons dari server, mengetik pustaka pihak ketiga
Kandungan:- pengantar
- Tipe nominal, tipe khusus - ketika semuanya tampak sama, tetapi sangat berbeda
- Ketik varians, tipe tepat - tentang hubungan antar jenis
- Pembatalan penyempurnaan - bicara tentang kepercayaan
- Pengecualian - apakah layak untuk mengakuinya ketika dikacaukan?
- Operasi yang tidak aman - kepercayaan diri tidak selalu baik
- Kasing Bonus - Pengecekan Jenis pada Tahap Tinjauan PR
- Kesimpulan
pengantar
Apakah sulit untuk menulis fungsi untuk menambahkan dua angka dalam JS? Ambil implementasi yang naiffunction sum(a, b) {
return a + b;
}
Mari kita periksa implementasi `sum (2, 2) === 4` kami, apakah semuanya berjalan dengan baik? Tidak juga, ketika kita mendeskripsikan suatu fungsi, kita harus memikirkan semua jenis nilai input, serta fungsi yang dapat dikembalikan1.1 + 2.7
NaN + 2
99999999999999992 + 99999999999999992
2n + 2
{} + true
2 + '2'
Tingkat kesehatan adalah kemampuan penganalisa untuk membuktikan bahwa tidak ada kesalahan saat program sedang berjalan. Jika program diterima oleh penganalisa, maka dijamin aman.Program aman adalah program yang dapat bekerja selamanya tanpa kesalahan. Itu program tidak akan crash atau melempar kesalahan.Program yang benar - program yang melakukan apa yang seharusnya dan tidak melakukan apa yang seharusnya tidak. Kebenaran tergantung pada pelaksanaan logika bisnis.Jenis dapat membuktikan bahwa program secara keseluruhan aman, dan menguji bahwa program tersebut aman dan benar hanya dalam data uji (cakupan 100%, tidak adanya "mutan" dari stryker, lulus tes berbasis properti dan sebagainya tidak dapat membuktikan apa pun, dan lisensi mengurangi risiko). Ada legenda yang membuktikan teorema dapat membuktikan kebenaran program.Penting untuk memahami filosofi TS, untuk memahami apa yang coba dipecahkan oleh alat dan, yang penting, apa yang tidak coba dipecahkan.Catatan tentang SoundnessTS melompati beberapa operasi yang tidak yakin pada tahap kompilasi. Tempat-tempat dengan perilaku tidak sehat dipikirkan dengan cermat.Tujuan desainBukan tujuan untuk TS - Untuk membuat sistem tipe dengan jaminan keamanan, alih-alih fokus pada keseimbangan antara keselamatan dan produktivitasContoh struktur:Masalahnya adalah perilaku yang tidak aman, daftar mungkin tidak lengkap, ini yang saya temukan di artikel, laporan, Masalah TS git.Proposal adalah masalah TS terbuka 3-4 tahun yang lalu, dengan banyak komentar dan penjelasan menarik oleh penulis.Tip - IMHO penulis, apa yang penulis anggap praktik yang baikPengetikan struktural vs Nominal
Pengetikan Struktural vs Nominal 1. Masalah
Mengetik struktural - ketika membandingkan jenis tidak memperhitungkan nama mereka atau di mana mereka dinyatakan, dan jenis dibandingkan menurut "struktur".Kami ingin mengirim surat `sendEmail` ke alamat yang benar` ValidatedEmail`, ada fungsi untuk memeriksa alamat` validateEmail` yang mengembalikan alamat yang benar` ValidatedEmail`. Sayangnya TS memungkinkan Anda untuk mengirim string apa pun ke `sendEmail`, karena `ValidatedEmail` untuk TS tidak berbeda dengan` string`type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail("asdf@gmail.com"));
// Should be error!
sendEmail("asdf@gmail.com");
Pengetikan Struktural vs Nominal 1. Penawaran
github.com/microsoft/TypeScript/issues/202Masukkan kata kunci `nominal` sehingga jenisnya diperiksa secara nominal. Sekarang kita bisa melarang lewat `string` di mana` ValidatedEmail` diharapkannominal type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Error!
sendEmail('asdf@gmail.com');
Pengetikan Struktural vs Nominal 1. Tip
Kita dapat membuat tipe `Buram`, yang akan mengambil sebagian` T` dan memberinya keunikan dengan menggabungkannya dengan tipe yang dibuat dari `K` yang dilewati. `K` dapat berupa simbol unik (` simbol unik`) atau string (maka akan diperlukan untuk memastikan bahwa string ini unik).type Opaque<K extends symbol | string, T>
= T & { [X in K]: never };
declare const validatedEmailK: unique symbol;
type ValidatedEmail = Opaque<typeof validatedEmailK, string>;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Argument of type '"asdf@gmail.com"' is not assignable
// to parameter of type 'Opaque<unique symbol, string>'.
sendEmail('asdf@gmail.com');
Pengetikan Struktural vs Nominal 2. Masalah
Kami memiliki kelas Dolar dan Euro, masing-masing kelas memiliki metode menambahkan untuk menambahkan Dolar ke Dolar dan Euro ke Euro. Untuk TS, kelas-kelas ini secara struktural sama dan kita dapat menambahkan Dolar ke Euro.export class Dollar {
value: number;
constructor(value: number) {
this.value = value;
}
add(dollar: Dollar): Dollar {
return new Dollar(dollar.value + this.value);
}
}
class Euro {
value: number;
constructor(value: number) {
this.value = value;
}
add(euro: Euro): Euro {
return new Euro(euro.value + this.value);
}
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
Pengetikan Struktural vs. Nominal 2. Penawaran
github.com/microsoft/TypeScript/issues/202Kalimatnya sama, dengan `nominal`, tetapi karena Karena kelas secara ajaib dapat menjadi Nominal (lebih lanjut tentang itu nanti), kemungkinan membuat transformasi seperti itu dengan cara yang lebih eksplisit dipertimbangkan.Pengetikan Struktural vs Nominal 1. Tip
Jika kelas memiliki bidang pribadi (asli dengan `#` atau dari TS c `private`), maka kelas secara ajaib menjadi Nominal, nama dan nilai dapat berupa apa saja. `!` (Penentuan penugasan pasti) digunakan untuk mencegah TS bersumpah pada bidang yang tidak diinisialisasi (strictNullChecks, flag strictPropertyInitialization diaktifkan).class Dollar {
private desc!: never;
value: number;
constructor(value: number) {
this.value = value;
}
add(dollar: Dollar) {
return new Dollar(dollar.value + this.value);
}
}
class Euro {
private desc!: never;
value: number;
constructor(value: number) {
this.value = value;
}
add(euro: Euro) {
return new Euro(euro.value + this.value);
}
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
Ketik varians 1. Masalah
Opsi pemrograman, singkatnya, adalah kemampuan untuk melewati Supertype / Subtype di sana, di mana Type diharapkan. Misalnya, ada hirarki Shape -> Circle -> Rectangle, apakah mungkin untuk mentransfer atau mengembalikan Shape / Rectangle jika Circle diharapkan?Varian dalam pemrograman habr , SO .Kita bisa meneruskan tipe dengan bidang di mana angka terletak pada fungsi yang mengharapkan bidang sebagai string atau angka, dan memutasi objek yang ditransmisikan dalam tubuh, mengubah bidang menjadi string. Itu `{status: number} sebagai {status: number | string} as {status: string} `di sini ada trik seperti mengubah angka menjadi string, menyebabkan kesalahan kejutan .function changeStatus(arg: { status: number | string }) {
arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Ketik varians 1. Penawaran
github.com/Microsoft/TypeScript/issues/10717Disarankan untuk memperkenalkan `masuk / keluar` untuk secara eksplisit membatasi kovarians / contravariance untuk obat generik.function changeStatus<
out T extends {
status: number | string;
}
>(arg: T) {
arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Ketik varians 1. Tip
Jika kami bekerja dengan struktur yang tidak dapat diubah, maka tidak akan ada kesalahan seperti itu (kami telah mengaktifkan flag strictFunctionTypes).function changeStatus(arg: Readonly<{ status: number | string }>) {
arg.status = "NotFound";
}
const error: Readonly<{ status: number }> = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Ketik varians 1. Bonus
Readonly dapat dialihkan kegithub.com/Microsoft/TypeScript/issues/13347github.com/microsoft/TypeScript/pull/6532#issuecomment-174356151, tetapi jika kita membuat tipe Readonly, TS tidak akan melarang untuk beralih ke fungsi di mana tidak diharapkan Readonly `Readonly <{status readonly: number}> sebagai {status: number | string} sebagai {status: string} `function changeStatus(arg: { status: number | string }) {
arg.status = "NotFound";
}
const error: Readonly<{ readonly status: number }>
= { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Ketik varians 2. Masalah
Objek dapat berisi bidang tambahan yang jenisnya tidak memiliki: `{message: string; status: string} as {message: string} `. Karena itu beberapa operasi mungkin tidak amanconst error: { message: string; status: string } = {
message: "No data",
status: "NotFound"
};
function updateError(arg: { message: string }) {
const defaultError = { message: "Not found", status: 404 };
const newError: { message: string; status: number }
= { ...defaultError, ...arg };
console.log(newError.status.toFixed());
}
updateError(error);
TS berpikir bahwa sebagai hasil dari penggabungan, status `{... {message: string, status: number}, ... {message: string}}` akan berupa angka.Pada kenyataannya, `{... {message:" Not found ", status: 404}, ... {message:" No data ", status:" NotFound "},}` status - string.Ketik varians 2. Penawaran
github.com/microsoft/TypeScript/issues/12936Memperkenalkan tipe `Exact` atau sintaksis serupa untuk mengatakan bahwa suatu tipe tidak dapat berisi bidang tambahan.const error: Exact<{ message: string; }> = {
message: "No data",
};
function updateError(arg: Exact<{ message: string }>) {
const defaultError = { message: "Not found", status: 404, };
const newError = { ...defaultError, ...arg };
console.log(newError.status.toFixed());
}
updateError(error);
Ketik varians 2. Tip
Gabungkan objek dengan mendaftarkan bidang secara eksplisit atau memfilter bidang yang tidak dikenal.const error: { message: string; status: string } = {
message: "No data",
status: "NotFound"
};
function updateError(arg: { message: string }) {
const defaultError = { message: "Not found", status: 404 };
const newError = { ...defaultError, message: arg.message };
console.log(newError.status.toFixed());
}
updateError(error);
Penyempurnaan penyempurnaan. Masalah
Setelah kami membuktikan sesuatu tentang keadaan eksternal, fungsi panggilan tidak aman, karena Tidak ada jaminan bahwa fungsi tidak mengubah keadaan eksternal ini:export function logAge(name: string, age: number) {
console.log(`${name} will lose ${age.toFixed()}`);
person.age = "PLACEHOLDER";
}
const person: { name: string; age: number | string } = {
name: "Person",
age: 42
};
if (typeof person.age === "number") {
logAge(person.name, person.age);
logAge(person.name, person.age);
}
Penyempurnaan penyempurnaan. Kalimat
github.com/microsoft/TypeScript/issues/7770#issuecomment-334919251Tambahkan pengubah `murni` untuk fungsi, ini setidaknya akan memungkinkan Anda untuk mempercayai fungsi tersebutPenyempurnaan penyempurnaan. Tip
Gunakan struktur data yang tidak berubah, maka pemanggilan fungsi akan menjadi apriori aman untuk pemeriksaan sebelumnya.Bonus
Jenis aliran sangat kuat sehingga tidak memiliki semua masalah yang tercantum di atas, tetapi sangat berjalan sehingga saya tidak akan merekomendasikan menggunakannya.Pengecualian. Masalah
TS tidak membantu untuk bekerja dengan Pengecualian dengan cara apa pun, tidak ada yang jelas pada tanda tangan fungsi.import { JokeError } from "../helpers";
function getJoke(isFunny: boolean): string {
if (isFunny) {
throw new JokeError("No funny joke");
}
return "Duh";
}
const joke: string = getJoke(true);
console.log(joke);
Pengecualian. Kalimat
github.com/microsoft/TypeScript/issues/13219Disarankan untuk memperkenalkan sintaksis yang memungkinkan secara eksplisit menjelaskan Pengecualian dalam tanda tangan fungsiimport { JokeError } from '../helpers';
function getJoke(isFunny: boolean): string | throws JokeError {
}
function getJokeSafe(isFunny: boolean): string {
try {
return getJoke(isFunny);
} catch (error) {
if (error instanceof JokeError) {
return "";
} else {
return error as never;
}
}
}
console.log(getJokeSafe(true));
Pengecualian. Bonus
github.com/microsoft/TypeScript/issues/6283Untuk beberapa alasan, dalam TS, definisi tipe untuk Janji mengabaikan jenis kesalahanconst promise1: Promise<number> = Promise.resolve(42);
const promise: Promise<never> = Promise.reject(new TypeError());
interface PromiseConstructor {
new <T>(
executor: (
resolve: (value?: T | PromiseLike<T>) => void,
reject: (reason?: any) => void
) => void
): Promise<T>;
}
Pengecualian. Tip
Ambil salah satu wadah seperti Janji, dengan hanya mengetik yang terbaik. ( Entah contoh implementasi )import { Either, exhaustiveCheck, JokeError } from "../helpers";
function getJoke(isFunny: boolean): Either<JokeError, string> {
if (isFunny) {
return Either.left(new JokeError("No funny joke"));
}
return Either.right("Duh");
}
getJoke(true)
.mapLeft(error => {
if (error instanceof JokeError) {
console.log("JokeError");
} else {
exhaustiveCheck(error);
}
})
.mapRight(joke => console.log(joke));
Operasi tidak aman. Masalah
Jika kami memiliki tupel dengan ukuran tetap, maka TS dapat menjamin bahwa ada sesuatu pada indeks yang diminta. Ini tidak akan berfungsi untuk array dan TS akan mempercayai kami
export const constNumbers: readonly [1, 2, 3]
= [1, 2, 3] as const;
console.log(constNumbers[100].toFixed());
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
Operasi tidak aman. Kalimat
github.com/microsoft/TypeScript/issues/13778Disarankan untuk menambahkan `tidak terdefinisi` ke tipe pengembalian` T` untuk akses indeks ke array. Tetapi dalam kasus ini, ketika mengakses pada indeks apa pun, Anda harus menggunakan `?` Atau melakukan pemeriksaan eksplisit.
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
console.log(dynamicNumbers[100]?.toFixed());
if (typeof dynamicNumbers[100] === 'number') {
console.log(dynamicNumbers[100].toFixed());
}
Operasi tidak aman. Tip
Agar tidak menghasilkan entitas di luar kebutuhan, kami mengambil wadah yang sebelumnya dikenal `Either` dan menulis fungsi aman untuk bekerja dengan indeks, yang akan kembali` Entah <null, T>`.import { Either } from "../helpers";
function safeIndex<T>(
array: T[],
index: number,
): Either<null, T> {
if (index in array) {
return Either.right(array[index]);
}
return Either.left(null);
}
const dynamicNumbers: number[] = [1, 2, 3];
safeIndex(dynamicNumbers, 100)
.mapLeft(() => console.log("Nothing"))
.mapRight(el => el + 2)
.mapRight(el => console.log(el.toFixed()));
Bonus Reload fungsi
Jika kita ingin mengatakan bahwa suatu fungsi mengambil beberapa baris dan mengembalikan string atau mengambil beberapa angka dan mengembalikan angka, maka dalam implementasi tanda tangan ini akan berdekatan, dan programmer harus menjamin kebenarannya, tetapi pada TS.PS melihat alternatif melalui tipe generik dan kondisional:function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: string | number,
b: string | number,
): string | number {
return `${a} + ${b}`;
}
const sum: number = add(2, 2);
sum.toFixed();
Bonus Ketik penjaga
TS mempercayai programmer bahwa `isSuperUser` dengan benar menentukan siapa` SuperUser` dan jika `Vasya` ditambahkan, tidak akan ada konfirmasi.PS layak untuk dipikirkan tentang bagaimana kita akan membedakan tipe yang sudah pada tahap union-tagged union merekatype SimpleUser = { name: string };
type SuperUser = {
name: string;
isAdmin: true;
permissions: string[]
};
type Vasya = { name: string; isAdmin: true; isGod: true };
type User = SimpleUser | SuperUser | Vasya;
function isSuperUser(user: User): user is SuperUser {
return "isAdmin" in user && user.isAdmin;
}
function doSomethings(user: User) {
if (isSuperUser(user)) {
console.log(user.permissions.join(","));
}
}
Kesimpulannya ada pada tipsnya
- Jenis nominal : Jenis buram, bidang pribadi- Jenis varians : Jenis yang tepat, Jenis DeepReadonly- Pengecualian : Salah satu monad- Penyangkalan penyempurnaan : Fungsi murni- Operasi tidak aman (akses indeks) : Either / Mungkin monadData tidak dapat diubah, fungsi murni, mon ... Selamat , kami membuktikan bahwa FP itu keren!temuan
- TS ingin mencapai keseimbangan antara kebenaran dan produktivitas
- Tes dapat membuktikan keamanan dan kebenaran hanya untuk data uji.
- Jenis dapat membuktikan keamanan keseluruhan program.
- Mutasi - buruk, oke?
Seperti DZ akan merekomendasikan untuk bermain dengan Flow setelah memperbaiki kesalahan sederhana:
declare function log(arg: { name: string, surname?: string }): void;
const person: { name: string } = { name: 'Negasi' };
log(person);
Contoh kode, solusi dan analisis masalah, tautan yang bermanfaat di repositori .