ค้นหาเว็บไซต์

ปัญหา 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);
  }
}

บทความที่เกี่ยวข้อง