ปัญหา Typescript กับการจำกัดชนิดด้วยตัวดำเนินการ ==
ในเอกสารทางการของ TypeScript เกี่ยวกับการจำกัดประเภทด้วยความเท่าเทียม มีการกล่าวว่า TypeScript สามารถจำกัดประเภทโดยใช้ตัวดำเนินการ ===, !==, == และ != อย่างไรก็ตาม มีปัญหาเกี่ยวกับวิธีการจัดการค่าที่เป็น falsy ซึ่งอาจนำไปสู่ข้อผิดพลาดขณะรันไทม์ได้
พิจารณาตัวอย่างต่อไปนี้:
function example(x: string | number, y: string | boolean) {
if (x == y) {
// TypeScript assumes 'x' and 'y' are both 'string' here.
x.toUpperCase();
// ^?
y.toLowerCase();
// ^?
} else {
console.log(x);
// ^?
console.log(y);
// ^?
}
}
example(0, false); // This will throw a runtime error.
ในกรณีนี้ TypeScript ทำการจำกัดชนิดของ x และ y ไปเป็น string ภายในบล็อก if อย่างไม่ถูกต้อง แม้ว่าการเปรียบเทียบ (x == y) จะประเมินค่าเป็น true เพราะ 0 และ false เป็นค่าที่ถือว่าเป็น falsy ทั้งคู่ ซึ่งส่งผลให้เกิดข้อผิดพลาดในขณะรันไทม์เมื่อเรียกใช้ toUpperCase หรือ toLowerCase กับชนิดข้อมูลที่ไม่ใช่ string
พฤติกรรมนี้ตั้งใจทำหรือมีวิธีที่ดีกว่าที่จะจัดการกรณีเช่นนี้ใน TypeScript หรือไม่?
คาดหวังว่าสิ่งนี้จะถูกจัดการในเวลาคอมไพล์
คำตอบ #1
นี่เป็นบั๊กที่ทราบกันมานานใน TypeScript ซึ่งรายงานที่ microsoft/TypeScript#37251 อย่างที่คุณค้นพบ การจำกัดด้วยความเท่าเทียมกันดูเหมือนจะไม่พิจารณาว่าโอเปอเรเตอร์ความเท่าเทียมกันแบบหย่อน (==) จะบังคับให้ตัวแปรที่มีชนิดต่างกันเป็นชนิดเดียวกันก่อนที่จะเปรียบเทียบ มันสมมติอย่างผิด ๆ ว่าสองชนิดที่ต่างกันไม่สามารถเท่ากันได้ แต่เนื่องจาก 0 == false สิ่งไม่ดีสามารถเกิดขึ้นได้ในระหว่างรันไทม์
บั๊กนี้อยู่ใน Backlog ซึ่งหมายความว่าพวกเขาจะพิจารณา pull request ที่ส่งโดยสมาชิกชุมชน ดังนั้นใครก็ตามที่อยากเห็นปัญหานี้ถูกแก้ควรพิจารณาแก้ไขมันด้วยตัวเอง จนกว่าจะมีการรวมการแก้ไข คุณจะต้องหาวิธีแก้ปัญหาชั่วคราวไปก่อน ผมขอแนะนำให้ใช้กฎ linter เช่น eslint/eqeqeq เพื่อบังคับให้คุณใช้ตัวดำเนินการเปรียบเทียบอย่างเข้มงวด (===) แทน (ยกเว้นอาจต้องตั้งค่าให้อนุญาตการตรวจสอบแบบภาษาพูด เช่น == null ซึ่ง ถูก จัดการโดย TypeScript อย่างถูกต้อง)
คำตอบ #2
ตัวดำเนินการ == จะเปรียบเทียบค่าด้วยการพยายามแปลงค่าเหล่านั้นเป็นประเภทเดียวกันก่อนที่จะทำการเปรียบเทียบ ดังนั้น 0 == false จึงเป็นจริง
ถ้าคุณต้องการเปรียบเทียบพวกเขาและตรวจสอบว่าพวกเขาเป็นชนิดข้อมูลเดียวกันทั้งคู่ คุณควรใช้ === แทน == แบบนี้
function example(x: string | number, y: string | boolean) {
if (x === y) {
x.toUpperCase();
y.toLowerCase();
} else {
console.log(x);
console.log(y);
}
}
แต่ถ้ามีเหตุผลบางอย่างที่คุณจำเป็นต้องใช้เพียง == เป็นตัวดำเนินการ TypeScript ไม่สามารถทำนายพฤติกรรมในขณะรันไทม์ได้เสมอ
เพื่อความปลอดภัย คุณควรตรวจสอบชนิดของข้อมูลอย่างชัดเจนในขณะรันไทม์แบบนี้
function example(x: string | number, y: string | boolean) {
if (x == y) {
if (typeof x === 'string' && typeof y === 'string') {
x.toUpperCase();
y.toLowerCase();
}
} else {
console.log(x);
console.log(y);
}
}
