เหตุใดจึงใช้ประเภทคงที่ใน JavaScript (ไพรเมอร์ 4 ส่วนสำหรับการพิมพ์แบบคงที่ด้วย Flow)

ในฐานะนักพัฒนา JavaScript คุณสามารถเขียนโค้ดได้ตลอดทั้งวันโดยไม่ต้องเจอกับประเภทคงที่ แล้วทำไมต้องเรียนรู้เกี่ยวกับพวกเขา?

ปรากฎว่าประเภทการเรียนรู้ไม่ได้เป็นเพียงแบบฝึกหัดในการขยายความคิด หากคุณยินดีที่จะลงทุนเวลาในการเรียนรู้เกี่ยวกับข้อดีข้อเสียและกรณีการใช้งานของประเภทคงที่ก็สามารถช่วยการเขียนโปรแกรมของคุณได้อย่างมาก

สนใจ? คุณโชคดี - นั่นคือส่วนที่เหลือของซีรีส์สี่ตอนนี้เกี่ยวกับ

ประการแรกคำจำกัดความ

วิธีที่เร็วที่สุดในการทำความเข้าใจประเภทคงที่คือการเปรียบเทียบกับประเภทไดนามิก ภาษาประเภทคงที่จะเรียกเป็นภาษาแบบคงที่พิมพ์ ในทางกลับกันภาษาที่มีชนิดไดนามิกจะเรียกว่าภาษาที่พิมพ์แบบไดนามิก

ความแตกต่างหลักคือว่าแบบคงที่พิมพ์ภาษาดำเนินการตรวจสอบชนิดที่รวบรวมเวลาในขณะที่แบบไดนามิกพิมพ์ภาษาดำเนินการตรวจสอบประเภทที่รันไทม์

สิ่งนี้ทำให้คุณมีอีกหนึ่งแนวคิดในการจัดการ: " การตรวจสอบประเภท"หมายความว่าอย่างไร

เพื่ออธิบายเรามาดูประเภทใน Java กับ Javascript

“ ประเภท”หมายถึงประเภทของข้อมูลที่กำหนด

ตัวอย่างเช่นใน Java หากคุณกำหนดbooleanเป็น:

boolean result = true;

สิ่งนี้มีประเภทที่ถูกต้องเนื่องจากbooleanคำอธิบายประกอบตรงกับค่าที่กำหนดให้resultตรงข้ามกับจำนวนเต็มหรือสิ่งอื่นใด

ในทางกลับกันหากคุณพยายามประกาศ:

boolean result = 123;

…สิ่งนี้จะล้มเหลวในการรวบรวมเนื่องจากมีประเภทที่ไม่ถูกต้อง มันชัดเจนเครื่องหมายresultเป็นแต่แล้วกำหนดเป็นจำนวนเต็มboolean123

JavaScript และภาษาที่พิมพ์แบบไดนามิกอื่น ๆ มีวิธีการที่แตกต่างกันทำให้บริบทสามารถกำหนดประเภทของข้อมูลที่กำหนดได้:

var result = true;

เรื่องสั้นขนาดยาว: ภาษาที่พิมพ์แบบคงที่คุณต้องประกาศประเภทข้อมูลของโครงสร้างของคุณก่อนจึงจะใช้งานได้ ภาษาที่พิมพ์แบบไดนามิกไม่ทำ JavaScript บ่งบอกถึงชนิดข้อมูลในขณะที่ Java ระบุไว้ทันที

ดังที่คุณเห็นประเภทช่วยให้คุณสามารถระบุค่าคงที่ของโปรแกรมหรือการยืนยันเชิงตรรกะและเงื่อนไขที่โปรแกรมจะดำเนินการ

การตรวจสอบประเภทจะยืนยันและบังคับใช้ว่าประเภทของโครงสร้าง (ค่าคงที่บูลีนตัวเลขตัวแปรอาร์เรย์วัตถุ) ตรงกับค่าคงที่ที่คุณระบุไว้ ตัวอย่างเช่นคุณอาจระบุว่า“ ฟังก์ชันนี้ส่งคืนสตริงเสมอ” เมื่อโปรแกรมทำงานคุณสามารถสันนิษฐานได้อย่างปลอดภัยว่าจะส่งคืนสตริง

ความแตกต่างระหว่างการตรวจสอบประเภทคงที่และการตรวจสอบประเภทไดนามิกมีความสำคัญมากที่สุดเมื่อเกิดข้อผิดพลาดประเภท ในภาษาที่พิมพ์แบบสแตติกข้อผิดพลาดประเภทจะเกิดขึ้นระหว่างขั้นตอนการคอมไพล์นั่นคือในเวลาคอมไพล์ ในภาษาที่พิมพ์แบบไดนามิกข้อผิดพลาดจะเกิดขึ้นเมื่อเรียกใช้โปรแกรมเท่านั้น นั่นคือที่รันไทม์

ซึ่งหมายความว่าโปรแกรมที่เขียนด้วยภาษาที่พิมพ์แบบไดนามิก (เช่น JavaScript หรือ Python) สามารถคอมไพล์ได้แม้ว่าจะมีข้อผิดพลาดประเภทที่ทำให้สคริปต์ทำงานไม่ถูกต้องก็ตาม

ในทางกลับกันหากโปรแกรมที่เขียนด้วยภาษาที่พิมพ์แบบคงที่ (เช่น Scala หรือ C ++) มีข้อผิดพลาดประเภทโปรแกรมจะไม่สามารถคอมไพล์ได้จนกว่าข้อผิดพลาดจะได้รับการแก้ไข

JavaScript ยุคใหม่

เนื่องจาก JavaScript เป็นภาษาที่พิมพ์แบบไดนามิกคุณจึงสามารถประกาศตัวแปรฟังก์ชันออบเจ็กต์และอะไรก็ได้โดยไม่ต้องประกาศประเภท

สะดวก แต่ไม่เหมาะเสมอไป ซึ่งเป็นสาเหตุที่ทำให้เครื่องมืออย่าง Flow และ TypeScript เพิ่งเข้ามาเพื่อให้นักพัฒนา JavaScript มีตัวเลือก * ในการใช้ประเภทคงที่

Flowเป็นไลบรารีการตรวจสอบประเภทสแตติกแบบโอเพนซอร์สที่พัฒนาและเผยแพร่โดย Facebook ซึ่งช่วยให้คุณสามารถเพิ่มประเภทลงในโค้ด JavaScript ของคุณได้

ในทางกลับกันTypeScriptเป็นซูเปอร์เซ็ตที่คอมไพล์ลงไปที่ JavaScript แม้ว่ามันจะรู้สึกเหมือนเป็นภาษาที่พิมพ์แบบคงที่ใหม่ ๆ ในตัวของมันเอง กล่าวคือดูเหมือนและให้ความรู้สึกคล้ายกับ JavaScript มากและไม่ยากที่จะหยิบขึ้นมา

ไม่ว่าในกรณีใดเมื่อคุณต้องการใช้ประเภทคุณจะต้องบอกเครื่องมืออย่างชัดเจนว่าไฟล์ใดที่จะพิมพ์ตรวจสอบ สำหรับ TypeScript คุณทำได้โดยการเขียนไฟล์ที่มี.tsนามสกุลแทน.js. สำหรับ Flow คุณรวมความคิดเห็นไว้ที่ด้านบนของไฟล์ด้วย@flow

เมื่อคุณได้ประกาศว่าคุณต้องการพิมพ์ - ตรวจสอบไฟล์คุณจะต้องใช้ไวยากรณ์ตามลำดับเพื่อกำหนดประเภท ความแตกต่างอย่างหนึ่งที่ทำให้ระหว่างเครื่องมือทั้งสองคือ Flow เป็นประเภท "ตัวตรวจสอบ" ไม่ใช่คอมไพเลอร์ ในทางกลับกัน TypeScript เป็นคอมไพเลอร์

ฉันเชื่ออย่างแท้จริงว่าเครื่องมือเช่น Flow และ TypeScript นำเสนอการเปลี่ยนแปลงและความก้าวหน้าสำหรับ JavaScript

โดยส่วนตัวแล้วฉันได้เรียนรู้มากมายโดยใช้ประเภทในชีวิตประจำวันของฉัน นี่คือเหตุผลที่ฉันหวังว่าคุณจะเข้าร่วมกับฉันในการเดินทางระยะสั้นและแสนหวานนี้ในรูปแบบคงที่

ส่วนที่เหลือของโพสต์ 4 ส่วนนี้จะครอบคลุม:

ส่วนที่ I. บทนำสั้น ๆ เกี่ยวกับไวยากรณ์และภาษาของ Flow

ชิ้นส่วน II และ III ข้อดีและข้อเสียของประเภทคงที่ (พร้อมตัวอย่างคำแนะนำโดยละเอียด)

ส่วนที่ 4. คุณควรใช้ประเภทคงที่ใน JavaScript หรือไม่?

โปรดทราบว่าฉันเลือก Flow over TypeScript ในตัวอย่างในโพสต์นี้เนื่องจากความคุ้นเคยกับมัน เพื่อจุดประสงค์ของคุณเองโปรดค้นคว้าและเลือกเครื่องมือที่เหมาะสมสำหรับคุณ TypeScript นั้นยอดเยี่ยมเช่นกัน

เริ่มกันเลยดีกว่า!

ส่วนที่ 1: คำแนะนำสั้น ๆ เกี่ยวกับไวยากรณ์และภาษาของ Flow

เพื่อทำความเข้าใจข้อดีและข้อเสียของประเภทคงที่ก่อนอื่นคุณควรทำความเข้าใจพื้นฐานเกี่ยวกับไวยากรณ์สำหรับประเภทคงที่โดยใช้ Flow หากคุณไม่เคยทำงานในภาษาที่พิมพ์แบบคงที่มาก่อนไวยากรณ์อาจใช้เวลาสักครู่เพื่อให้คุ้นเคย

เริ่มต้นด้วยการสำรวจวิธีการเพิ่มประเภทลงใน JavaScript primitives รวมถึงโครงสร้างเช่น Arrays, Object, Functions และอื่น ๆ

บูลีน

สิ่งนี้อธิบายถึงค่าboolean(จริงหรือเท็จ) ใน JavaScript

สังเกตว่าเมื่อคุณต้องการระบุประเภทไวยากรณ์ที่คุณใช้คือ:

จำนวน

สิ่งนี้อธิบายถึงหมายเลขทศนิยม IEEE 754 ไม่เหมือนกับภาษาโปรแกรมอื่น ๆ JavaScript ไม่ได้กำหนดตัวเลขประเภทต่างๆ (เช่นจำนวนเต็มสั้นยาวและทศนิยม) แต่ตัวเลขจะถูกจัดเก็บเป็นตัวเลขทศนิยมที่มีความแม่นยำสองเท่าเสมอ ดังนั้นคุณต้องใช้ตัวเลขเพียงประเภทเดียวในการกำหนดตัวเลขใด ๆ

numberรวมถึงInfinityและNaN.

สตริง

สิ่งนี้อธิบายถึงสตริง

โมฆะ

สิ่งนี้อธิบายถึงnullประเภทข้อมูลใน JavaScript

เป็นโมฆะ

สิ่งนี้อธิบายถึงundefinedประเภทข้อมูลใน JavaScript

สังเกตว่าnullและundefinedได้รับการปฏิบัติที่แตกต่างกัน หากคุณพยายามทำ:

การไหลจะโยนความผิดพลาดเพราะคนประเภทที่voidควรจะเป็นประเภทที่ไม่ได้เช่นเดียวกับชนิดundefinednull

อาร์เรย์

อธิบายอาร์เรย์ JavaScript คุณใช้ไวยากรณ์Array<T> เพื่ออธิบายอาร์เรย์ที่มีองค์ประกอบบางประเภท T

สังเกตว่าฉันแทนที่Tด้วยstringซึ่งหมายความว่าฉันกำลังประกาศmessagesเป็นอาร์เรย์ของสตริง

วัตถุ

สิ่งนี้อธิบายถึงออบเจ็กต์ JavaScript มีหลายวิธีในการเพิ่มประเภทให้กับวัตถุ

คุณสามารถเพิ่มประเภทเพื่ออธิบายรูปร่างของวัตถุ:

คุณสามารถกำหนดวัตถุเป็นแผนที่ที่คุณจับคู่สตริงกับค่าบางค่า:

คุณยังสามารถกำหนดวัตถุเป็นObjectประเภท:

วิธีสุดท้ายนี้ช่วยให้เราสามารถตั้งค่าคีย์และค่าใด ๆ บนวัตถุของคุณได้โดยไม่มีข้อ จำกัด ดังนั้นจึงไม่ได้เพิ่มมูลค่ามากเท่าที่เกี่ยวข้องกับการตรวจสอบประเภท

ใด ๆ

สิ่งนี้สามารถแสดงถึงประเภทใดก็ได้อย่างแท้จริง anyชนิดที่ไม่ถูกตรวจสอบได้อย่างมีประสิทธิภาพดังนั้นคุณควรพยายามที่จะหลีกเลี่ยงการใช้มันถ้าไม่จำเป็นจริง (เช่นเมื่อคุณจำเป็นต้องเลือกประเภทการตรวจสอบหรือต้องการหนีออกจากไข่)

สถานการณ์หนึ่งที่คุณอาจพบว่าanyมีประโยชน์คือเมื่อใช้ไลบรารีภายนอกที่ขยายต้นแบบของระบบอื่น (เช่น Object.prototype)

ตัวอย่างเช่นหากคุณกำลังใช้ไลบรารีที่ขยาย Object.prototype ด้วยdoSomethingคุณสมบัติ:

คุณอาจได้รับข้อผิดพลาด:

หากต้องการหลีกเลี่ยงสิ่งนี้คุณสามารถใช้any:

ฟังก์ชั่น

วิธีทั่วไปในการเพิ่มประเภทให้กับฟังก์ชันคือการเพิ่มประเภทให้กับอาร์กิวเมนต์อินพุตและ (เมื่อเกี่ยวข้อง) ค่าที่ส่งคืน:

คุณยังสามารถเพิ่มประเภทให้กับฟังก์ชัน async (ดูด้านล่าง) และเครื่องกำเนิดไฟฟ้า:

สังเกตว่าพารามิเตอร์ตัวที่สองของเราgetPurchaseLimitใส่คำอธิบายประกอบเป็นฟังก์ชันที่ส่งกลับ a Promise. และamountExceedsPurchaseLimitมีคำอธิบายประกอบในขณะที่ส่งกลับไฟล์Promise.

พิมพ์นามแฝง

การใช้นามแฝงประเภทเป็นหนึ่งในวิธีโปรดของฉันในการใช้ประเภทคงที่ ช่วยให้คุณใช้ประเภทที่มีอยู่ (ตัวเลขสตริง ฯลฯ ) เพื่อสร้างประเภทใหม่:

ด้านบนฉันได้สร้างชนิดใหม่ที่เรียกว่าPaymentMethodซึ่งมีคุณสมบัติที่ประกอบด้วยnumberและstringประเภท

ตอนนี้หากคุณต้องการใช้PaymentMethodประเภทคุณสามารถทำได้:

คุณยังสามารถสร้างนามแฝงประเภทสำหรับดั้งเดิมโดยการรวมประเภทที่อยู่ภายใต้ประเภทอื่น ตัวอย่างเช่นหากคุณต้องการพิมพ์นามแฝง a NameและEmailAddress:

การทำเช่นนี้แสดงว่าคุณกำลังบ่งบอกสิ่งนั้นNameและEmailเป็นสิ่งที่แตกต่างไม่ใช่แค่สตริง เนื่องจากชื่อและอีเมลไม่สามารถใช้แทนกันได้การทำเช่นนี้จะป้องกันไม่ให้คุณผสมกันโดยไม่ได้ตั้งใจ

Generics

Generics เป็นวิธีการสร้างนามธรรมเหนือประเภทต่างๆ สิ่งนี้หมายความว่า?

ลองมาดู:

Tฉันสร้างนามธรรมสำหรับประเภท Tตอนนี้คุณสามารถใช้สิ่งที่ประเภทที่คุณต้องการที่จะเป็นตัวแทน สำหรับnumberT, เป็นประเภทT numberในขณะเดียวกันสำหรับarrayTT เป็นประเภทArrayer>.

Yes, I know. It’s dizzying stuff if this is the first time you’re looking at types. I promise the “gentle” intro is almost over!

Maybe

Maybe type allows us to type annotate a potentially null or undefined value. They have the type T|void|null for some type T, meaning it is either type T or it is undefined or null. To define a maybe type, you put a question mark in front of the type definition:

Here I’m saying that message is either a string, or it’s null or undefined.

You can also use maybe to indicate that an object property will be either of some type T or undefined:

By putting the ? next to the property name for middleInitial, you can indicate that this field is optional.

Disjoint unions

This is another powerful way to model data. Disjoint unions are useful when you have a program that needs to deal with different kinds of data all at once. In other words, the shape of the data can be different based on the situation.

Extending on the PaymentMethod type from our earlier generics example, let’s say that you have an app where users can have one of three types of payment methods. In this case, you can do something like:

Then you can define your PaymentMethod type as a disjoint union with three cases.

Payment method now can only ever be one of these three shapes. The property type is the property that makes the union type “disjoint”.

You’ll see more practical examples of disjoint union types later in part II.

All right, almost done. There are a couple other features of Flow worth mentioning before concluding this intro:

1) Type inference: Flow uses type inference where possible. Type inference kicks in when the type checker can automatically deduce the data type of an expression. This helps avoid excessive annotation.

For example, you can write:

Even though this Class doesn’t have types, Flow can adequately type check it:

Here I’ve tried to define area as a string, but in the Rectangle class definition we defined width and height as numbers. So based on the function definition for area, it must be return a number. Even though I didn’t explicitly define types for the area function, Flow caught the error.

One thing to note is that the Flow maintainers recommend that if you were exporting this class definition, you’d want to add explicit type definitions to make it easier to find the cause of errors when the class is not used in a local context.

2) Dynamic type tests: What this basically means is that Flow has logic to determine what the the type of a value will be at runtime and so is able to use that knowledge when performing static analysis. They become useful in situations like when Flow throws an error but you need to convince flow that what you’re doing is right.

I won’t go into too much detail because it’s more of an advanced feature that I hope to write about separately, but if you want to learn more, it’s worth reading through the docs.

We’re done with syntax

We covered a lot of ground in one section! I hope this high-level overview has been helpful and manageable. If you’re curious to go deeper, I encourage you to dive into the well-written docs and explore.

With syntax out of the way, let’s finally get to the fun part: exploring the advantages and disadvantages of using types!

Next up: Part 2 & 3.

Original text