Nhảy tới nội dung

Definite assignment assertion

Definite assignment assertion là toán tử cho compiler TypeScript biết rằng biến hoặc property chắc chắn đã được khởi tạo.

strictNullChecks và lỗi khởi tạo biến

Khi compiler option strictNullChecks được đặt là true, TypeScript sẽ báo lỗi khi tham chiếu đến biến chưa được khởi tạo.

ts
let num: number;
console.log(num * 2);
Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.
ts
let num: number;
console.log(num * 2);
Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.

Ngay cả khi biến rõ ràng được khởi tạo bên trong một function, compiler vẫn báo lỗi rằng biến chưa được khởi tạo.

ts
let num: number;
initNum(); // Khởi tạo num trong function nhưng...
console.log(num * 2);
Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.
function initNum() {
num = 2;
}
ts
let num: number;
initNum(); // Khởi tạo num trong function nhưng...
console.log(num * 2);
Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.
function initNum() {
num = 2;
}

strictPropertyInitialization và lỗi khởi tạo property

Trong TypeScript, khi cả hai compiler option sau đều là true, sẽ báo lỗi nếu property của class chưa được khởi tạo.

ts
class Foo {
num: number;
Property 'num' has no initializer and is not definitely assigned in the constructor.2564Property 'num' has no initializer and is not definitely assigned in the constructor.
}
ts
class Foo {
num: number;
Property 'num' has no initializer and is not definitely assigned in the constructor.2564Property 'num' has no initializer and is not definitely assigned in the constructor.
}

TypeScript compiler kiểm tra xem property có được khởi tạo tại định nghĩa property hoặc trong constructor hay không. Tuy nhiên, nó không theo dõi việc khởi tạo trong các method khác ngoài constructor. Ví dụ, trong ví dụ sau, num3 thực tế được khởi tạo, nhưng compiler vẫn cảnh báo rằng nó chưa được khởi tạo.

ts
class Foo {
num1: number = 1; // Đã khởi tạo
num2: number;
num3: number;
Property 'num3' has no initializer and is not definitely assigned in the constructor.2564Property 'num3' has no initializer and is not definitely assigned in the constructor.
 
constructor() {
this.num2 = 1; // Đã khởi tạo
this.initNum3(); // Khởi tạo num3
}
 
initNum3() {
this.num3 = 1;
}
}
ts
class Foo {
num1: number = 1; // Đã khởi tạo
num2: number;
num3: number;
Property 'num3' has no initializer and is not definitely assigned in the constructor.2564Property 'num3' has no initializer and is not definitely assigned in the constructor.
 
constructor() {
this.num2 = 1; // Đã khởi tạo
this.initNum3(); // Khởi tạo num3
}
 
initNum3() {
this.num3 = 1;
}
}

Sử dụng definite assignment assertion

Để cho compiler biết rằng biến hoặc property chắc chắn đã được khởi tạo, sử dụng definite assignment assertion. Viết ! sau tên biến hoặc tên property trong khai báo.

ts
let num!: number;
// ^Definite assignment assertion
initNum();
console.log(num * 2); // Không còn lỗi
function initNum() {
num = 2;
}
ts
let num!: number;
// ^Definite assignment assertion
initNum();
console.log(num * 2); // Không còn lỗi
function initNum() {
num = 2;
}
ts
class Foo {
num!: number;
// ^Definite assignment assertion
}
ts
class Foo {
num!: number;
// ^Definite assignment assertion
}

Non-null assertion

Một phương pháp khác là sử dụng non-null assertion. Trong trường hợp này, viết ! sau biến tại nơi tham chiếu đến biến.

ts
let num: number;
initNum();
console.log(num! * 2); // Không còn lỗi
// ^Non-null assertion
function initNum() {
num = 2;
}
ts
let num: number;
initNum();
console.log(num! * 2); // Không còn lỗi
// ^Non-null assertion
function initNum() {
num = 2;
}

Viết code an toàn hơn

Definite assignment assertion và non-null assertion chuyển trách nhiệm đảm bảo an toàn type từ compiler sang programmer. Và về type, con người dễ mắc sai lầm hơn compiler. Vì vậy, không sử dụng các assertion này sẽ an toàn hơn.

Ví dụ, trong ví dụ trên, việc gán giá trị trả về của initNum cho num sẽ là code an toàn hơn.

ts
let num: number;
num = initNum();
console.log(num * 2);
function initNum() {
return 2;
}
ts
let num: number;
num = initNum();
console.log(num * 2);
function initNum() {
return 2;
}

Ngoài ra, còn có phương pháp kiểm tra xem num có phải là kiểu number hay không bằng type guard.

ts
let num: number | undefined;
initNum();
// Type guard
if (typeof num === "number") {
console.log(num * 2);
}
function initNum() {
num = 2;
}
ts
let num: number | undefined;
initNum();
// Type guard
if (typeof num === "number") {
console.log(num * 2);
}
function initNum() {
num = 2;
}

Khuyến nghị trước tiên hãy xem xét các phương pháp không dựa vào assertion. Sau đó, chỉ sử dụng assertion khi thực sự cần thiết. Đôi khi do yêu cầu của framework hoặc library, việc sử dụng là không thể tránh khỏi.

Chia sẻ kiến thức

・Definite assignment assertion cho TypeScript compiler biết rằng biến chắc chắn đã được khởi tạo
・Viết ! sau tên biến
・Vì nó chuyển trách nhiệm an toàn type từ compiler sang programmer, hãy xem xét phương pháp không sử dụng trước
・Chỉ sử dụng khi không còn cách nào khác

Từ 『Survival TypeScript』

Đăng nội dung này lên X

Thông tin liên quan

📄️ strictNullChecks

Làm nghiêm ngặt check null và undefined

📄️ strictPropertyInitialization

Bắt buộc khởi tạo class property