TypeScript ขั้นสูงพร้อมตัวอย่างจริง (ตอนที่ 2)

เผยแพร่แล้ว: 2020-11-20

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

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

รีวิวสั้นๆ เกี่ยวกับสิ่งที่เราได้ทำไปแล้ว…

ในส่วนแรกของบทช่วยสอน เราใช้ (ส่วนหนึ่งของ) ร้าน Redux ที่เรานำมาจาก Nelio Content เป็นตัวอย่างการทำงานของเรา ทุกอย่างเริ่มต้นจากโค้ด JavaScript ธรรมดาซึ่งต้องได้รับการปรับปรุงโดยการเพิ่มประเภทที่เป็นรูปธรรมที่ทำให้แข็งแกร่งและเข้าใจได้ง่ายขึ้น ตัวอย่างเช่น เรากำหนดประเภทต่อไปนี้:

 type PostId = number; type Day = string; type Post = { id: PostId; title: string; author: string; day: Day; status: string; isSticky: boolean; }; type State = { posts: Dictionary<PostId, Post>; days: Dictionary<Day, PostId[]>; };

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

เมื่อกำหนดประเภทเหล่านี้แล้ว เราก็แก้ไขฟังก์ชันทั้งหมดในตัวอย่างเพื่อใช้งาน งานง่าย ๆ นี้เปลี่ยนลายเซ็นฟังก์ชันทึบแสงของ JavaScript:

 // Selectors function getPost( state, id ) { ... } function getPostsInDay( state, day ) { ... } // Actions function receiveNewPost( post ) { ... } function updatePost( postId, attributes ) { ... } // Reducer function reducer( state, action ) { ... }

ถึงลายเซ็นฟังก์ชัน TypeScript ที่อธิบายตนเอง:

 // Selectors function getPost( state: State, id: PostId ): Post | undefined { ... } function getPostsInDay( state: State, day: Day ): PostId[] { ... } // Actions function receiveNewPost( post: Post ): any { ... } function updatePost( postId: PostId, attributes: any ): any { ... } // Reducer function reducer( state: State, action: any ): State { ... }

ฟังก์ชัน getPostsInDay เป็นตัวอย่างที่ดีมากว่า TypeScript จะปรับปรุงคุณภาพของโค้ดของคุณได้มากเพียงใด หากคุณดูที่คู่ของ JavaScript คุณไม่รู้จริงๆ ว่าฟังก์ชันนั้นจะส่งคืนอะไร แน่นอนว่าชื่อของมันอาจบ่งบอกถึงประเภทผลลัพธ์ (อาจเป็นรายการโพสต์ก็ได้) แต่คุณต้องดูที่ซอร์สโค้ดของฟังก์ชัน (และอาจเป็นการกระทำและตัวลดทอนด้วย) เพื่อให้แน่ใจ (จริงๆ แล้วมันคือรายการของ โพสต์ ID) สามารถปรับปรุงสถานการณ์นี้ได้โดยการตั้งชื่อสิ่งต่าง ๆ ให้ดีขึ้น ( getIdsOfPostsInDay ) แต่ไม่มีอะไรที่เหมือนกับประเภทที่เป็นรูปธรรมในการทำความสะอาดข้อสงสัย: PostId[]

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

วิธีพิมพ์วัตถุที่มีแอตทริบิวต์เป็นส่วนย่อยของวัตถุอื่น

มาอบอุ่นร่างกายด้วยการเริ่มต้นด้วยอะไรง่ายๆ ฟังก์ชัน updatePost จะสร้างการดำเนินการที่ส่งสัญญาณถึงความตั้งใจของเราในการอัปเดตแอตทริบิวต์บางอย่างของ ID ของโพสต์ที่ระบุ มีลักษณะดังนี้:

 function updatePost( postId: PostId, attributes: any ): any { return { type: 'UPDATE_POST', postId, attributes, }; }

และนี่คือวิธีที่ตัวลดใช้การดำเนินการเพื่ออัปเดตโพสต์ในร้านค้าของเรา:

 function reducer( state: State, action: any ): State { // ... switch ( action.type ) { // ... case 'UPDATE_POST': if ( ! state.posts[ action.postId ] ) { return state; } const post = { ...state.posts[ action.postId ], ...action.attributes, }; return { ... }; } // ... }

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

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

 type UpdatePostAction = { type: 'UPDATE_POST'; postId: number; attributes: Post; };

แต่ถ้าเราลองใช้เราจะเห็นว่ามันไม่ได้ผล :

 const post: Post = { id: 1, title: 'Title', author: 'Ruth', day: '2020-10-01', status: 'draft', isSticky: false, }; const action: UpdatePostAction = { type: 'UPDATE_POST', postId: 1, attributes: { author: 'Toni', }, };

เพราะเราไม่ต้องการให้ attributes เป็น Post เอง เราต้องการให้มันเป็นชุดย่อยของแอตทริบิวต์ Post (เช่น เราต้องการระบุเฉพาะแอตทริบิวต์ของวัตถุ Post ที่เราจะเขียนทับ)

ในการแก้ปัญหานี้ เพียงใช้ประเภทยูทิลิตี้ Partial :

 type UpdatePostAction = { type: 'UPDATE_POST'; postId: number; attributes: Partial<Post>; };

และนั่นแหล่ะ! หรือว่า?

การกรองแอตทริบิวต์อย่างชัดเจน

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

 const post = { ...state.posts[ action.postId ], ...action.attributes, };

และนั่นเป็นส่วนที่ผิดพลาดในโค้ดของเรา เป็นไปได้ว่าแอ็ตทริบิวต์ postId ของแอ็คชันมี pots ID x และ id แอ็ตทริบิวต์ใน attributes ทริบิวต์มี post ID ที่แตกต่างกัน y :

 const action: UpdatePostAction = { type: 'UPDATE_POST', postId: 1, attributes: { id: 2, author: 'Toni', }, };

เห็นได้ชัดว่านี่เป็นการกระทำที่ถูกต้อง ดังนั้น TypeScript จึงไม่ทำให้เกิดข้อผิดพลาดใดๆ แต่เรารู้ว่าไม่ควรเป็นเช่นนั้น แอตทริบิวต์ id ใน attributes (ถ้ามี) และแอตทริบิวต์ postId ควรมีค่าเท่ากัน มิฉะนั้นเราจะมีการดำเนินการที่ไม่ต่อเนื่องกัน ประเภทการกระทำของเราไม่แน่ชัดเพราะทำให้เรากำหนดสถานการณ์ที่ไม่น่าจะเป็นไปไม่ได้...แล้วเราจะแก้ไขได้อย่างไร? ค่อนข้างง่าย: เพียงแค่เปลี่ยนประเภทนี้เพื่อให้สถานการณ์ที่ไม่น่าจะเป็นไปไม่ได้กลายเป็นเป็นไปไม่ได้จริงๆ

วิธีแก้ปัญหาแรกที่ฉันคิดว่ามีดังต่อไปนี้: ลบแอตทริบิวต์ postId ออกจากการดำเนินการและเพิ่ม ID ใน attributes แอตทริบิวต์:

 type UpdatePostAction = { type: 'UPDATE_POST'; attributes: Partial<Post>; }; function updatePost( postId: PostId, attributes: Partial<Post> ): UpdatePostAction { return { type: 'UPDATE_POST', attributes: { ...attributes, id: postId, }, }; }

จากนั้นอัปเดตตัวลดเพื่อให้ใช้ action.attributes.id แทน action.postId เพื่อค้นหาและเขียนทับโพสต์ที่มีอยู่

ขออภัย โซลูชันนี้ไม่เหมาะ เนื่องจาก attributes เป็น "โพสต์บางส่วน" จำได้ไหม ซึ่งหมายความว่า ตามทฤษฎีแล้ว คุณลักษณะ id อาจอยู่ในวัตถุ attributes หรือไม่ก็ได้ แน่นอน เรารู้ว่ามันจะอยู่ที่นั่น เพราะเราเป็นคนสร้างการกระทำ… แต่ประเภทของเรายังไม่ชัดเจน หากในอนาคตมีคนแก้ไขฟังก์ชัน updatePost และไม่แน่ใจว่า attributes นั้นรวม postId ไว้ด้วย การดำเนินการที่เป็นผลลัพธ์จะถูกต้องตาม TypeScript แต่โค้ดของเราจะใช้งานไม่ได้:

 const workingAction: UpdatePostAction = { type: 'UPDATE_POST', attributes: { id: 1, author: 'Toni', }, }; const failingAction: UpdatePostAction = { type: 'UPDATE_POST', attributes: { author: 'Toni', }, };

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

  1. หากเรามีแอ็ตทริบิวต์ postId ที่ทำงานอยู่ (อย่างที่เราทำในตอนเริ่มต้น) อ็อบเจก attributes ทริบิวต์ ต้องไม่มี แอ็ตทริบิวต์ id
  2. ในทางกลับกัน หากการดำเนินการไม่มีแอตทริบิวต์ postId attributes นั้น จะต้องมี แอตทริบิวต์ id

โซลูชันแรกสามารถระบุได้อย่างง่ายดายโดยใช้ยูทิลิตี้ประเภทอื่น Omit ซึ่งช่วยให้เราสร้างประเภทใหม่ได้โดยการลบแอตทริบิวต์ออกจากประเภทที่มีอยู่:

 type UpdatePostAction = { type: 'UPDATE_POST'; postId: PostId, attributes: Partial< Omit<Post, 'id'> >; };

ซึ่งทำงานได้ตามที่คาดไว้ :

 const workingAction: UpdatePostAction = { type: 'UPDATE_POST', postId: 1, attributes: { author: 'Toni', }, }; const failingAction: UpdatePostAction = { type: 'UPDATE_POST', postId: 1, attributes: { id: 1, author: 'Toni', }, };

สำหรับตัวเลือกที่สอง เราต้องเพิ่มแอตทริบิวต์ id ให้ชัดเจนที่ด้านบนของประเภท Partial<Post> ที่เรากำหนด:

 type UpdatePostAction = { type: 'UPDATE_POST'; attributes: Partial<Post> & { id: PostId }; };

ซึ่งอีกครั้งให้ผลลัพธ์ที่คาดหวังแก่เรา:

 const workingAction: UpdatePostAction = { type: 'UPDATE_POST', attributes: { id: 1, author: 'Toni', }, }; const failingAction: UpdatePostAction = { type: 'UPDATE_POST', attributes: { author: 'Toni', }, };

ประเภทสหภาพ

ในส่วนที่แล้ว เราได้เห็นวิธีพิมพ์หนึ่งในสองการกระทำที่ร้านค้าของเรามีแล้ว ลองทำเช่นเดียวกันกับการกระทำที่สอง การรู้ว่า receiveNewPost มีลักษณะดังนี้:

 function receiveNewPost( post: Post ): any { return { type: 'RECEIVE_NEW_POST', post, }; }

ประเภทของมันสามารถกำหนดได้ดังนี้:

 type ReceiveNewPostAction = { type: 'RECEIVE_NEW_POST'; post: Post; };

ง่ายใช่มั้ย?

ทีนี้มาดูที่ตัวลดของเรากัน: มันใช้ state และการ action (ซึ่งเรายังไม่รู้ประเภท) และสร้าง State ใหม่:

 function reducer( state: State, action: any ): State { ... }

ร้านค้าของเรามีการดำเนินการสองประเภท: UpdatePostAction และ ReceiveNewPostAction ดังนั้นประเภทของอาร์กิวเมนต์ action คืออะไร? อย่างใดอย่างหนึ่งใช่ไหม? เมื่อตัวแปรสามารถรับมากกว่าหนึ่งประเภท A , B , C และอื่นๆ ประเภทของตัวแปรจะเป็นการรวมกันของประเภท นั่นคือประเภทอาจเป็น A หรือ B หรือ C เป็นต้น ประเภทสหภาพแรงงานคือประเภทที่มีค่าสามารถเป็นประเภทใดก็ได้ที่ระบุไว้ในสหภาพนั้น

ต่อไปนี้คือวิธีที่ประเภท Action ของเราสามารถกำหนดเป็นประเภทสหภาพได้:

 type Action = UpdatePostAction | ReceiveNewPostAction;

ตัวอย่างก่อนหน้าเป็นเพียงการระบุว่าการ Action อาจเป็นอินสแตนซ์ของประเภท UpdatePostAction หรืออินสแตนซ์ของประเภท ReceiveNewPostAction

หากตอนนี้เราใช้ Action ในตัวลดของเรา:

 function reducer( state: State, action: Action ): State { ... }

เราสามารถเห็นได้ว่าโค้ดเวอร์ชันใหม่ของเราซึ่งพิมพ์มาอย่างดีนี้ทำงานได้อย่างราบรื่นอย่างไร

วิธีที่ Union Types ขจัดกรณีเริ่มต้น

“เดี๋ยวก่อน” คุณอาจพูดว่า “ลิงก์ก่อนหน้าทำงานไม่ราบรื่น คอมไพเลอร์ทำให้เกิดข้อผิดพลาด!” ตาม TypeScript ตัวลดของเรามีรหัสที่ไม่สามารถเข้าถึงได้:

 function reducer( state: State, action: Action ): State { // ... switch ( action.type ) { case 'RECEIVE_NEW_POST': // ... case 'UPDATE_POST': // ... } return state; //Error! Unreachable code }

รออะไร? ให้ฉันอธิบายสิ่งที่เกิดขึ้นที่นี่ ...

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

ในกรณีของเรา การ Action ทั้งสองประเภทมีแอตทริบิวต์ type ที่มีค่า RECEIVE_NEW_POST สำหรับ ReceiveNewPostAction และ UPDATE_POST สำหรับ UpdatePostAction เนื่องจากเราทราบดีว่าการ Action จำเป็นต้องเป็นอินสแตนซ์ของการกระทำอย่างใดอย่างหนึ่งหรืออย่างอื่น switch สองสาขาของเราจึงครอบคลุมความเป็นไปได้ทั้งหมด: action.type คือ RECEIVE_NEW_POST หรือ UPDATE_POST ดังนั้นการ return ครั้งสุดท้ายจึงซ้ำซ้อนและจะไม่สามารถเข้าถึงได้

สมมติว่าเราลบการ return นั้นเพื่อแก้ไขข้อผิดพลาดนี้ เราได้รับอะไรนอกเหนือจากการลบโค้ดที่ไม่จำเป็นหรือไม่? คำตอบคือใช่ หากตอนนี้เราเพิ่มการกระทำประเภทใหม่ในโค้ดของเรา:

 type Action = | UpdatePostAction | ReceiveNewPostAction | NewFeatureAction; type NewFeatureAction = { type: 'NEW_FEATURE'; // ... };

ทันใดนั้น คำสั่ง switch ในตัวลดของเราจะไม่ครอบคลุมสถานการณ์ที่เป็นไปได้ทั้งหมดอีกต่อไป:

 function reducer( state: State, action: Action ): State { // ... switch ( action.type ) { case 'RECEIVE_NEW_POST': // ... case 'UPDATE_POST': // ... // case NEW_FEATURE is missing... } // return undefined is now implicit }

ซึ่งหมายความว่าตัวลดอาจส่งคืนค่าที่ undefined โดยปริยาย หากเราเรียกใช้โดยใช้การกระทำประเภท NEW_FEATURE และนั่นเป็นสิ่งที่ไม่ตรงกับลายเซ็นของฟังก์ชัน เนื่องจากความไม่ตรงกันนี้ TypeScript จึงบ่นและแจ้งให้เราทราบว่าเราขาดสาขาใหม่เพื่อจัดการกับประเภทการดำเนินการใหม่นี้

ฟังก์ชัน Polymorphic พร้อมประเภทการส่งคืนตัวแปร

หากคุณมาไกลถึงขนาดนี้ ขอแสดงความยินดีด้วย คุณได้เรียนรู้ทุกสิ่งที่ต้องทำเพื่อปรับปรุงซอร์สโค้ดของแอปพลิเคชัน JavaScript โดยใช้ TypeScript และเพื่อเป็นรางวัล ฉันจะแบ่งปัน "ปัญหา" ที่ฉันพบเมื่อสองสามวันก่อนและวิธีแก้ปัญหาให้กับคุณ ทำไม? เนื่องจาก TypeScript เป็นโลกที่ซับซ้อนและน่าสนใจ และฉันต้องการแสดงให้คุณเห็นว่าข้อใดเป็นความจริง

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

 function getPostsInDay( state: State, day: Day ): PostId[] { return state.days[ day ] ?? []; }

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

 const ids: PostId[] = getPostsInDay( '2020-10-01', 'id' ); const posts: Post[] = getPostsInDay( '2020-10-01', 'all' );

เราสามารถทำได้ใน TypeScript หรือไม่? แน่นอนเราทำ! ทำไมฉันถึงนำสิ่งนี้ขึ้นมาเป็นอย่างอื่น? สิ่งที่เราต้องทำคือกำหนดฟังก์ชัน polymorphic ซึ่งผลลัพธ์จะขึ้นอยู่กับพารามิเตอร์อินพุต

แนวคิดก็คือเราต้องการให้มีฟังก์ชันเดียวกันสองเวอร์ชันที่แตกต่างกัน ควรส่งคืนรายการ PostId หากแอตทริบิวต์ใดแอตทริบิวต์หนึ่งเป็น id string อีกอันควรส่งคืนรายการ Post หากแอตทริบิวต์เดียวกันนั้นเป็น string ทั้งหมด

มาสร้างพวกเขาทั้งสองกันเถอะ:

 function getPostsInDay( state: State, day: Day, mode: 'id' ): PostId[] { // ... } function getPostsInDay( state: State, day: Day, mode: 'all' ): Post[] { // ... }

ง่ายใช่มั้ย? ผิด! นี้ไม่ทำงาน ตาม TypeScript เรามี "การใช้งานฟังก์ชันที่ซ้ำกัน"

โอเค เรามาลองทำอย่างอื่นกันดีกว่า มารวมคำจำกัดความสองคำก่อนหน้านี้เป็นฟังก์ชันเดียว:

 function getPostsInDay( state: State, day: Day, mode: 'id' | 'all' = 'id' ): PostId[] | Post[] { if ( 'id' === mode ) { return state.days[ day ] ?? []; } return []; }

สิ่งนี้ประพฤติตามที่เราต้องการหรือไม่? กลัวมันไม่...

นี่คือสิ่งที่ลายเซ็นของฟังก์ชันกำลังบอกเรา: " getPostsInDay เป็นฟังก์ชันที่รับสองอาร์กิวเมนต์ คือ state และ mode ที่มีค่าอาจเป็น id หรือ ทั้งหมด ประเภทการส่งคืนจะเป็นรายการของ PostId หรือรายการของ Post ” กล่าวอีกนัยหนึ่ง ข้อกำหนดของฟังก์ชันก่อนหน้านี้ไม่ได้ระบุที่ใดๆ ที่มีความสัมพันธ์ระหว่างค่าที่กำหนดให้กับอาร์กิวเมนต์ของ mode และประเภทการส่งคืนของฟังก์ชัน ดังนั้นรหัสเช่นนี้:

 const state: State = { posts: {}, days: {} }; const ids: PostId[] = getPostsInDay( state, '2020-10-01', 'id' ); const posts: Post[] = getPostsInDay( state, '2020-10-01', 'all' );

ถูกต้องและไม่ประพฤติตามที่เราต้องการ

ตกลง ความพยายามครั้งสุดท้าย จะเกิดอะไรขึ้นถ้าเราผสมผสานสัญชาตญาณเริ่มต้นของเรา ซึ่งเราอธิบายลายเซ็นของฟังก์ชันที่เป็นรูปธรรมด้วยการใช้งานที่ถูกต้องเพียงครั้งเดียว

 function getPostsInDay( state: State, day: Day, mode: 'id' ): PostId[]; function getPostsInDay( state: State, day: Day, mode: 'all' ): Post[]; function getPostsInDay( state: State, day: Day, mode: 'id' | 'all' ): PostId[] | Post[] { const postIds = state.days[ day ] ?? []; if ( 'id' === mode ) { return postIds; } return postIds .map( ( pid ) => getPost( state, pid ) ) .filter( ( p ): p is Post => !! p ); }

ตัวอย่างก่อนหน้ามีการใช้งานฟังก์ชันที่ถูกต้องซึ่งใช้งานได้ แต่กำหนดลายเซ็นฟังก์ชันพิเศษสองรายการซึ่งผูกค่าที่เป็นรูปธรรมใน mode ด้วยประเภทการส่งคืนของฟังก์ชัน

เมื่อใช้วิธีนี้ รหัสนี้ใช้ได้:

 const ids: PostId[] = getPostsInDay( state, '2020-10-01', 'id' ); const posts: Post[] = getPostsInDay( state, '2020-10-01', 'all' );

และอันนี้ไม่:

 const ids: PostId[] = getPostsInDay( state, '2020-10-01', 'all' ); const posts: Post[] = getPostsInDay( state, '2020-10-01', 'id' );

บทสรุป

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

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

ภาพเด่นโดย Mike Kenneally บน Unsplash