การทำงานกับ React Hooks และ TypeScript

เผยแพร่แล้ว: 2022-03-11

Hooks ได้รับการแนะนำให้รู้จักกับ React ในเดือนกุมภาพันธ์ 2019 เพื่อปรับปรุงความสามารถในการอ่านโค้ด เราได้พูดถึง React hooks ในบทความก่อนหน้านี้แล้ว แต่คราวนี้เรากำลังตรวจสอบว่า hooks ทำงานกับ TypeScript อย่างไร

ก่อน hooks ส่วนประกอบ React เคยมีสองรสชาติ:

  • ชั้นเรียน ที่จัดการกับรัฐ
  • ฟังก์ชั่น ที่กำหนดโดยอุปกรณ์ประกอบฉากอย่างเต็มที่

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

React Hook คืออะไร?

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

React Hook คืออะไร?

แต่เมื่อโค้ดเติบโตขึ้น ส่วนประกอบการทำงานก็มักจะถูกแปลงเป็นส่วนประกอบคอนเทนเนอร์

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

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

 // put signature in local state and toggle signature when signed changes function QuotationSignature({quotation}) { const [signed, setSigned] = useState(quotation.signed); useEffect(() => { fetchPost(`quotation/${quotation.number}/sign`) }, [signed]); // effect will be fired when signed changes return <> <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/> Signature </> }

มีโบนัสก้อนโตสำหรับสิ่งนี้—การเข้ารหัสด้วย TypeScript นั้นยอดเยี่ยมกับ Angular แต่เต็มไปด้วย React อย่างไรก็ตาม การเข้ารหัส React hooks ด้วย TypeScript นั้นเป็นประสบการณ์ที่น่าพึงพอใจ

TypeScript พร้อม React แบบเก่า

TypeScript ได้รับการออกแบบโดย Microsoft และปฏิบัติตามเส้นทางเชิงมุมเมื่อ React พัฒนา Flow ซึ่งขณะนี้สูญเสียการยึดเกาะ การเขียนคลาส React ด้วย TypeScript ที่ไร้เดียงสานั้นค่อนข้างเจ็บปวดเพราะนักพัฒนา React ต้องพิมพ์ทั้ง props และ state แม้ว่าหลายปุ่มจะเหมือนกันก็ตาม

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

 interface Quotation{ id: number title:string; lines:QuotationLine[] price: number } interface QuotationState{ readonly quotation:Quotation; signed: boolean } interface QuotationProps{ quotation:Quotation; } class QuotationPage extends Component<QuotationProps, QuotationState> { // ... }

แต่ลองนึกภาพ QuotationPage จะขอ รหัส เซิร์ฟเวอร์จากเซิร์ฟเวอร์ เช่น ใบเสนอราคาครั้งที่ 678 ของบริษัท นั่นหมายความว่า QuotationProps ไม่ทราบจำนวนที่สำคัญนั้น—ไม่ได้ตัด Quotation อย่างแน่นอน เราต้องประกาศโค้ดเพิ่มเติมในอินเทอร์เฟซ QuotationProps:

 interface QuotationProps{ // ... all the attributes of Quotation but id title:string; lines:QuotationLine[] price: number }

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

ประโยชน์ของ TypeScript กับ Hooks

ด้วยการใช้ hooks เราจะสามารถกำจัดอินเทอร์เฟซ QuotationState ก่อนหน้าได้ ในการทำเช่นนี้ เราจะแบ่ง QuotationState ออกเป็นสองส่วนที่แตกต่างกันของรัฐ

 interface QuotationProps{ quotation:Quotation; } function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

การแบ่งสถานะทำให้เราไม่ต้องสร้างอินเทอร์เฟซใหม่ ประเภทสถานะท้องถิ่นมักจะอนุมานด้วยค่าสถานะเริ่มต้น

ส่วนประกอบที่มีตะขอเป็นฟังก์ชันทั้งหมด ดังนั้น เราสามารถเขียนองค์ประกอบเดียวกันที่ส่งคืนประเภท FC<P> ที่กำหนดไว้ในไลบรารี React ฟังก์ชันนี้ประกาศประเภทการส่งคืนอย่างชัดเจน โดยตั้งค่าตามประเภทอุปกรณ์ประกอบฉาก

 const QuotationPage : FC<QuotationProps> = ({quotation}) => { const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

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

มีเหตุผลมากมายที่ทำให้คุณหลีกเลี่ยง TypeScript ไม่ว่าจะใช้ React หรือไม่ก็ตาม แต่ถ้าคุณเลือกใช้ คุณควรใช้ขอเกี่ยวด้วยเช่นกัน

คุณสมบัติเฉพาะของ TypeScript เหมาะสำหรับ Hooks

ในตัวอย่าง React hooks TypeScript ก่อนหน้านี้ ฉันยังมีแอตทริบิวต์ number ใน QuotationProps แต่ยังไม่มีเงื่อนงำว่าตัวเลขนั้นคืออะไร

TypeScript แสดงรายการยูทิลิตี้ประเภทต่างๆ มากมาย และสามประเภทจะช่วยเราในเรื่อง React โดยลดเสียงรบกวนของคำอธิบายอินเทอร์เฟซจำนวนมาก

  • Partial<T> : คีย์ย่อยใดๆ ของ T
  • Omit<T, 'x'> : คีย์ทั้งหมดของ T ยกเว้นคีย์ x
  • Pick<T, 'x', 'y', 'z'> : เฉพาะปุ่ม x, y, z จาก T

คุณสมบัติเฉพาะของ TypeScript เหมาะสำหรับ Hooks

ในกรณีของเรา เราต้องการให้ Omit<Quotation, 'id'> ละเว้น id ของใบเสนอราคา เราสามารถสร้างประเภทใหม่ได้ทันทีด้วยคีย์เวิร์ด type

Partial<T> และ Omit<T> ไม่มีอยู่ในภาษาที่พิมพ์ส่วนใหญ่ เช่น Java แต่ช่วยได้มากสำหรับตัวอย่างกับ Forms ในการพัฒนาส่วนหน้า ช่วยลดความยุ่งยากในการพิมพ์

 type QuotationProps= Omit<Quotation, id>; function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); // ... }

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

อย่างไรก็ตาม ตอนนี้เรามั่นใจว่าเราจะไม่กระจายวัตถุที่เราคิดว่ามี number การใช้ Partial<T> ไม่ได้ทำให้เกิดการรับประกันทั้งหมด ดังนั้นโปรดใช้ดุลยพินิจ

Pick<T, 'x'|'y'> เป็นอีกวิธีหนึ่งในการประกาศประเภทได้ทันทีโดยไม่ต้องประกาศอินเทอร์เฟซใหม่ หากเป็นองค์ประกอบ ให้แก้ไขชื่อใบเสนอราคา:

 type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>

หรือเพียงแค่:

 function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}

อย่าตัดสินฉัน ฉันเป็นแฟนตัวยงของ Domain Driven Design ฉันไม่ขี้เกียจจนไม่อยากเขียนอีกสองบรรทัดสำหรับอินเทอร์เฟซใหม่ ฉันใช้อินเทอร์เฟซเพื่ออธิบายชื่อโดเมนได้อย่างแม่นยำ และยูทิลิตี้เหล่านี้ทำงานเพื่อความถูกต้องของโค้ดในเครื่อง หลีกเลี่ยงสัญญาณรบกวน ผู้อ่านจะรู้ว่า Quotation เป็นอินเทอร์เฟซตามรูปแบบบัญญัติ

ประโยชน์อื่นๆ ของ React Hooks

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

 interface Place{ city:string, country:string } const initialState:Place = { city: 'Rosebud', country: 'USA' }; function reducer(state:Place, action):Partial<Place> { switch (action.type) { case 'city': return { city: action.payload }; case 'country': return { country: action.payload }; } } function PlaceForm() { const [state, dispatch] = useReducer(reducer, initialState); return ( <form> <input type="text" name="city" onChange={(event) => { dispatch({ type: 'city',payload: event.target.value}) }} value={state.city} /> <input type="text" name="country" onChange={(event) => { dispatch({type: 'country', payload: event.target.value }) }} value={state.country} /> </form> ); }

นี่เป็นกรณีที่การใช้ Partial นั้นปลอดภัยและเป็นทางเลือกที่ดี

แม้ว่าฟังก์ชันสามารถดำเนินการได้หลายครั้ง แต่ตะขอ useReducer ที่เกี่ยวข้องจะถูกสร้างขึ้นเพียงครั้งเดียว

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

สิ่งนี้ดีกว่าอย่างชัดเจนสำหรับการทดสอบ—บางฟังก์ชันจัดการกับ JSX, อื่นๆ เกี่ยวกับพฤติกรรม, อื่นๆ ด้วยตรรกะทางธุรกิจ และอื่นๆ

คุณ (เกือบ) ไม่ต้องการส่วนประกอบระดับสูงอีกต่อไป รูปแบบ Render props เขียนได้ง่ายขึ้นด้วยฟังก์ชัน

ดังนั้นการอ่านโค้ดจึงง่ายกว่า รหัสของคุณไม่ใช่โฟลว์ของคลาส/ฟังก์ชัน/รูปแบบ แต่เป็นโฟลว์ของฟังก์ชัน อย่างไรก็ตาม เนื่องจากฟังก์ชันของคุณไม่ได้แนบกับอ็อบเจ็กต์ การตั้งชื่อฟังก์ชันเหล่านี้ทั้งหมดอาจเป็นเรื่องยาก

TypeScript ยังคงเป็น JavaScript

JavaScript เป็นเรื่องสนุกเพราะคุณสามารถฉีกโค้ดของคุณไปในทิศทางใดก็ได้ ด้วย TypeScript คุณยังสามารถใช้ keyof เพื่อเล่นกับคีย์ของอ็อบเจกต์ได้ คุณสามารถใช้ type unions เพื่อสร้างสิ่งที่อ่านไม่ได้และไม่สามารถจัดการได้ - ไม่ ฉันไม่ชอบสิ่งเหล่านั้น คุณสามารถใช้นามแฝงประเภทเพื่อแสร้งทำเป็นว่าสตริงเป็น UUID

แต่คุณสามารถทำได้ด้วยความปลอดภัยเป็นโมฆะ ตรวจสอบให้แน่ใจว่า tsconfig.json ของคุณมีตัวเลือก "strict":true เช็คก่อนเริ่มโปรเจกต์ ไม่งั้นต้อง refactor แทบทุกบรรทัด!

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

นอกจากนี้ คุณยังสามารถสร้างข้อผิดพลาดรันไทม์ได้! TypeScript นั้นง่ายกว่า Java และหลีกเลี่ยงปัญหาความแปรปรวนร่วม/การขัดแย้งกับ Generics

ในตัวอย่าง Animal/Cat นี้ เรามี Animal list ที่เหมือนกันกับ Cat list น่าเสียดายที่มันเป็นสัญญาในบรรทัดแรกไม่ใช่บรรทัดที่สอง จากนั้น เราเพิ่มเป็ดลงในรายการสัตว์ ดังนั้นรายการของแมวจึงเป็นเท็จ

 interface Animal {} interface Cat extends Animal { meow: () => string; } const duck = {age: 7}; const felix = { age: 12, meow: () => "Meow" }; const listOfAnimals: Animal[] = [duck]; const listOfCats: Cat[] = [felix]; function MyApp() { const [cats , setCats] = useState<Cat[]>(listOfCats); // Here the thing: listOfCats is declared as a Animal[] const [animals , setAnimals] = useState<Animal[]>(listOfCats) const [animal , setAnimal] = useState(duck) return <div onClick={()=>{ animals.unshift(animal) // we set as first cat a duck ! setAnimals([...animals]) // dirty forceUpdate } }> The first cat says {cats[0].meow()}</div>; }

TypeScript มีเพียงแนวทางสองประการสำหรับ generics ที่ง่ายและช่วยให้นักพัฒนา JavaScript ยอมรับ หากคุณตั้งชื่อตัวแปรอย่างถูกต้อง คุณจะไม่ค่อยเพิ่ม duck ลงใน listOfCats

นอกจากนี้ยังมีข้อเสนอในการเพิ่มสัญญาเข้าและออกสำหรับความแปรปรวนร่วมและความขัดแย้ง

บทสรุป

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

TypeScript น่าจะเป็นผู้ชนะที่ยิ่งใหญ่ในปี 2019 มันได้รับ React แต่ยังพิชิตโลกแบ็คเอนด์ด้วย Node.js และความสามารถในการพิมพ์ไลบรารีเก่าด้วยไฟล์ประกาศที่ค่อนข้างง่าย มันกำลังฝัง Flow แม้ว่าบางคนจะใช้ ReasonML ไปตลอดทาง

ตอนนี้ ฉันรู้สึกว่า hooks+TypeScript นั้นน่าพอใจและได้ผลมากกว่า Angular ฉันไม่ได้คิดอย่างนั้นเมื่อหกเดือนก่อน