Hiểu rõ --strictNullCheck trong Typescript 2.0
Bài đăng này đã không được cập nhật trong 8 năm
Nhận thức mới về Null và Undefined
TypeScript có 2 kiểu đặc biệt, Null và Undefined, nó có giá trị tương ứng là null và undefined. Trước đây, nó không thể là tên của 1 kiểu , nhưng Null và Undefined giờ được xem như là kiểu trong checking mode.
Ở phiên bản trước, thì null và undefined có thể gán cho bất cứ giá trị nào. null và undefined là những giá trị khả thi cho bất cứ kiểu nào, do vậy không thể nhận biết được những sai lầm về kiểu trong việc sử dụng null hoặc undefined.
-- strictNullChecks
--strictNullChecks là 1 mode check null checking mode. Trong strict Null checking mode, giá trị null và undefined không thể gán cho tất cả các kiểu, chúng chỉ có thể gán cho kiểu của chúng(tương ứng null là Null và undefined là Undefined), và kiểu any (một ngoại lệ là undefined có thể gán cho kiểu void).Vì vậy, trong khi T và T| undefined được xem xét là như nhau trong regular type checking mode (kiểm tra kiểu một cách thông thường) bởi vì undefined được coi là kiểu con của bất cứ kiểu T nào đó, tuy nhiên, chúng lại là 2 kiểu khác nhau trong strict null type checking mode( mode kiểu tra kiểu một cách nghiêm ngặt), và chỉ có kiểu T | undefined mới đồng ý nhận gía trị undefined. Quan hệ này vẫn đúng và tương tự với T và T | null.
Ví dụ
// Compiled with --strictNullChecks
let x: number;
let y: number | undefined;
let z: number | null | undefined;
x = 1; // Ok
y = 1; // Ok
z = 1; // Ok
x = undefined; // Error
y = undefined; // Ok
z = undefined; // Ok
x = null; // Error
y = null; // Error
z = null; // Ok
x = y; // Error
x = z; // Error
y = x; // Ok
y = z; // Error
z = x; // Ok
z = y; // Ok
Kiểm tra việc gán trước khi sử dụng
Assigned-before-use checking Trong strict null checking mode, compiler đòi hỏi mỗi lần tham chiếu đến một biến cục bộ (local variable) có kiểu không phải là Undefined phải được đứng trước một phép toàn bằng. Nghĩa là nó phải được định nghĩa
Ví dụ
// Compiled with --strictNullChecks
let x: number;
let y: number | null;
let z: number | undefined;
x; // Error, reference not preceded by assignment
y; // Error, reference not preceded by assignment
z; // Ok
x = 1;
y = null;
x; // Ok
y; // Ok
Complier kiểm tra xem những biến đã chắc chắn được gán dựa trên control flow based type .
Optional parameters và properties
(Những tham số và thuộc tính tùy chọn)
Các tham sồ tùy chọn và các thuộc tính sẽ được tự động có thể kiểu Undefined, cũng bao gốm những kiểu không khai báo undefined một cách rõ ràng.
Ví dụ
// Compiled with --strictNullChecks
type T1 = (x?: number) => string; // x has type number | undefined
type T2 = (x?: number | undefined) => string; // x has type number | undefined
Non-null and non-undefined type guards
Một thuộc tính có thể truy cập hoặc một hàm gọi đến compile-time error nếu đối tương hoặc hàm đó là 1 kiểu bao gồm null hoặc undefined. Tuy nhiên, type guards được mở rộng để hỗ trợ non-null và non-undefined checks.
Ví dụ
// Compiled with --strictNullChecks
declare function f(x: number): string;
let x: number | null | undefined;
if (x) {
f(x); // Ok, type of x is number here
}
else {
f(x); // Error, type of x is number? here
}
let a = x != null ? f(x) : ""; // Type of a is string
let b = x && f(x); // Type of b is string?
Non-null và non-undefined type guards có thể sử dụng ==, !=, === !== để so sánh với null và undefined., giống như x!=null hoặc x === undefined. Các phép toán có ý nghĩa tương tự trong Javascript , ví dụ double-equals (==) kiểm tra 2 gía trị trong đó có thể có 1 giá trị đặc biệt, trong khi triple-equals(===) chỉ kiểm tra những giá trị đặc biệt.
Dotted names in type guards
Trong phiên bản type guard trước , chỉ hỗ trợ việc kiểm tra những biến và tham số một cách cục bộ. Type guard giờ hỗ trợ kiểm tra dotted names bao gồm các biến và cá tham số theo một hay nhiều thuộc tính được truy nhập.
Ví dụ
interface Options {
location?: {
x?: number;
y?: number;
};
}
function foo(options?: Options) {
if (options && options.location && options.location.x) {
const x = options.location.x; // Type of x is number
}
}
Type guards giành cho dotted name hoạt động giống như người dùng định nghĩa một type guard function và typeof và instanceof không phục thuộc vào --strictNullChecks option Một type guard giành cho một dotted name không có theo một phép toán, và bất cứ thành phần nào của dotted name. Ví dụ , một type guard cho x.y.z sẽ không có hiệu ứng trong phép toán x, x.y hoặc x.y.z
Expression operators
Expression operators đồng ý thực hiện với các kiểu bao gồm null và hoặc undefined,nhưng thông thường, nó sử dụng với các kiểu non-null và non-undefined
// Compiled with --strictNullChecks
function sum(a: number | null, b: number | null) {
return a + b; // Produces value of type number
}
Phép toán && được thêm vào null and/or undefined, kiểu của các thành phần bên phải phụ thuộc vào cái thể hiện ra ở các kiểu của bên trái phép toàn, và phép toán || loại bỏ cả 2 giá trị null và undefined từ các kiểu ở bên trái phép toán trong kết quả là kiểu union type
// Compiled with --strictNullChecks
interface Entity {
name: string;
}
let x: Entity | null;
let s = x && x.name; // s is of type string | null
let y = x || { name: "test" }; // y is of type Entity
Type widening
null và undefined không thể mở rộng thành any trong trict null checking mode
let z = null ; // Kiểu của z là Null
Trong regular type checking mode thì kiểu của z là any bởi vị sự mở rộng của kiểu (type widening), nhưng trong strict null checking mode thì kiểu của z là null (Null), và trước đó, thì các kiểu được đề cập thì null chỉ có thể gán cho null)
Non-null asertion operator
Có một hậu tố ! có thể sử dụng để kỳ vọng rằng phép toán này sẽ non-null và non-undefined trong bất cứ nội dung nào được kiểm tra trong thực tế. Đặc biệt phép toán x! sẽ định nghĩa một giá trị x với đính kèm loại bỏ null và undefined. Tương tự như kỳ vọng kiểu của form <T>x và x giống như T, non-null loai bỏ một cách đơn giản emitted JS code.
// Compiled with --strictNullChecks
function validateEntity(e?: Entity) {
// Throw exception if e is null or invalid entity
}
function processEntity(e?: Entity) {
validateEntity(e);
let s = e!.name; // Assert that e is non-null and access name
}
Khả năng tương thích
Một đặc điểm mới đã được thiết kế là chúng ta có thể sử dụng cả 2 mode, strict checking mode và regular type checking mode. Một phần trong đó, null và underfined type sẽ được tự động xóa bỏ trong union type trong regular type checking mode( bởi vì chúng là kiểu con của bất cứ kiểu nào)., và ! non-null sẽ cho phép nhưng sẽ không có hiệu ứng trong regular type checking mode. Như vậy, việc khai báo được cập nhật sử dụng null và undefined có thể sử dụng ở regular type checking mode và ngược lại. Trong điều kiện thực tế, strict null checking mode yêu cầu rằng mọi file được dự đoán kết quả null và undefined.
Control flow based type anylysis
(phân tích dòng điều khiển các kiểu cơ bản)
Typescript 2.0 cài đặt một control flow-based type analysis cho các biết cục bộ (local variables) và các tham số (parameters). Ở các phiên bản trước đây, type analysis thực hiện cho type guard một cách giới hạn cho câu lệnh if và ?:, và chúng không bao gồm các hiệu ứng gán gía trị và các khởi tạo điều kiện giống như các câu lệnh return và break. Với Typescript 2.0, type checker analyses sẽ kiểm tra mọi khả năng theo các flows của control trong các câu lệnh và các phép toán, đặc biệt kiểu có thể xảy ra ở mọi các giá trị cục bộ và các tham số, chúng được khai báo kiểu union.
Ví dụ
function foo(x: string | number | boolean) {
if (typeof x === "string") {
x; // type of x is string here
x = 1;
x; // type of x is number here
}
x; // type of x is number | boolean here
}
function bar(x: string | number) {
if (typeof x === "number") {
return;
}
x; // type of x is string here
}
Controll flow based type analysis là một điều quan trọng trong --strictNullChecks mode bởi vì nullable type được thể hiện và sử dụng như union types
function test(x: string | null) {
if (x === null) {
return;
}
x; // type of x is string in remainder of function
}
Hơn nữa, trong --strictNullChecks modes, control flow based type analysis bao gồm các định nghĩa phân tích kiểu gán (definite assignment analysis) cho các biết cục bộ , không cho phép giá trị undefined
function mumble(check: boolean) {
let x: number; // Type doesn't permit undefined
x; // Error, x is undefined
if (check) {
x = 1;
x; // Ok
}
x; // Error, x is possibly undefined
x = 2;
x; // Ok
}
Tagged union types
Typescript 2.0 thực hiện việc cài đặt hỗ trợ tagged(phân biệt) các union types. Đặc biệt , typescript compiler hiện tại hỗ trợ type guard, cho phép thắt chặt các union types based trong các tests của các thuộc tính, hơn nữa, nó còn mở rộng để thay đổi các câu lệnh
Ví dụ
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
// In the following switch statement, the type of s is narrowed in each case clause
// according to the value of the discriminant property, thus allowing the other properties
// of that variant to be accessed without a type assertion.
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.width * s.height;
case "circle": return Math.PI * s.radius * s.radius;
}
}
function test1(s: Shape) {
if (s.kind === "square") {
s; // Square
}
else {
s; // Rectangle | Circle
}
}
function test2(s: Shape) {
if (s.kind === "square" || s.kind === "rectangle") {
return;
}
s; // Circle
}
Một discriminant property type guard là một phép toán theo kiểu x.p == v, x.p === v, x.p != v hoặc x.p !== v, với p và v là các thuộc tính của phép toán với kiểu string hoặc union string.
Lưu ý rằng hiện chỉ hỗ trợ tính biệt thức của chuỗi loại chữ (kiểu string). Dự định sau này hỗ trợ một loại đen boolean và số.
never type
Typescript 2.0 giới thiệu thêm một kiểu nguyên thủy nữa: kiểu never. Kiểu nguyên thủy được hiểu là kiểu cơ bản, hiểu giống như boolean, string, number là các kiểu nguyên thủy. Kiểm never thể hiện rằng kiểu của các giá trị không bao giờ xảy ra. Đặc biệt, never là kiểu cùa các hàm trả về sẽ không bao giờ trả về và never là kiểu của các biến dưới type guard sẽ không bao giờ đúng. Kiểu never có một số đặc trưng sau:
- never là một kiểu con (subtype of) của các phép gán của mọi kiểu
- không có môt kiểu nào là subtype of hoặc có thể gán cho never(trừ chính bản thân kiểu never)
- Trong một expression function hoặc arrow function với không giá trị kiểu trả về, nếu function không có return statement hoặc chỉ có return statement, với kiểu của phép toán sẽ là never và nếu end point của function không thể truy nhập thì kiểu trả về của hàm đó là never
- Trong một function bao gồm kiểu never và một kiểu trả về gì đó, mọi câu lệnh return phải là các phép toán kiểu never và endpoint của function phải không thể truy cập
Bởi vì never là kiểu con của mọi kiểu, nên nó được bỏ qua trong union types và nó bỏ qua giá trị function trả về được truy cập đến ngay cả khi những giá trị khác có thể trả về Một số ví dụ về việc trả về kiểu never
// Function returning never must have unreachable end point
function error(message: string): never {
throw new Error(message);
}
// Inferred return type is never
function fail() {
return error("Something failed");
}
// Function returning never must have unreachable end point
function infiniteLoop(): never {
while (true) {
}
}
Một số ví dụ của việc sử dụng function return never type:
// Inferred return type is number
function move1(direction: "up" | "down") {
switch (direction) {
case "up":
return 1;
case "down":
return -1;
}
return error("Should never get here");
}
// Inferred return type is number
function move2(direction: "up" | "down") {
return direction === "up" ? 1 :
direction === "down" ? -1 :
error("Should never get here");
}
// Inferred return type is T
function check<T>(x: T | undefined) {
return x || error("Undefined value");
}
Bởi vì kiểu never có thể gán giá trị cho mọi kiểu, thì một function trả về kiểu never có thể sử dụng như là 1 callback returning và đặc biệt chúng được required:
function test(cb: () => string) {
let s = cb();
return s;
}
test(() => "hello");
test(() => fail());
test(() => { throw new Error(); })
Read-only properties và index signatures
Một property hoặc index signature có thể được khai báo với readonly modifier và thuộc về read-only Các thuộc tính read-only có thể cài đặt và gán trong các hàm khởi tạo với cùng một lớp khai báo, ngòai ra, các phép gán với read-only sẽ không thể thực hiện Thêm vào đó các entities là implicitly read-only trong một vài tính huống:
- Một thuộc tính với khai báo là 1 get accessor và không có set accessor thuộc về read-only
- Trong kiểu enum object, enum members thuộc về các thuộc tính read-only
- Trong các module object , exported const variable thuộc về read-only
- Một entity được khai báo từ câu lệnh import thuộc về read-only
- Một entity được truy cập thông qua es2015 namespace import thuộc về read-only
Ví dụ
interface Point {
readonly x: number;
readonly y: number;
}
var p1: Point = { x: 10, y: 20 };
p1.x = 5; // Error, p1.x is read-only
var p2 = { x: 1, y: 1 };
var p3: Point = p2; // Ok, read-only alias for p2
p3.x = 5; // Error, p3.x is read-only
p2.x = 5; // Ok, but also changes p3.x because of aliasing
class Foo {
readonly a = 1;
readonly b: string;
constructor() {
this.b = "hello"; // Assignment permitted in constructor
}
}
let a: Array<number> = [0, 1, 2, 3, 4];
let b: ReadonlyArray<number> = a;
b[5] = 5; // Error, elements are read-only
b.push(5); // Error, no push method (because it mutates array)
b.length = 3; // Error, length is read-only
a = b; // Error, mutating methods are missing
Kiểu của this trong function
Tiếp theo là kiểu của this trong một class hoặc interface, function và các method có thể khai báo kiểu của this. Theo mặc định, kiểu của this trong một function là any. Bắt đầu từ TS2.0, bạn có thể cung cấp một tham số this minh bạch. this có thể là tham số ảo và trở thành tham số đầu tiên trong các tham số của một function:
function f(this: void) {
// make sure `this` is unusable in this standalone function
}
Tham số this trong callbacks Nhiều thư viện sử dụng tham số this để khai báo làm thể nào callback được gọi. Ví dụ
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
this: void đồng nghĩa với việc rằng addClickListener đồng ý rằng onClick sẽ là một function không yêu cầu kiểu của this
Giờ, bạn gọi đoạn mã trên với this:
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used this here. using this callback would crash at runtime
this.info = e.message;
};
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
Nguồn : Có gì mới trong typescript 2.0
All rights reserved