React API ที่มีประโยชน์สำหรับการสร้างส่วนประกอบที่ยืดหยุ่นด้วย TypeScript

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างย่อ ↬ ทำ ปฏิกิริยากับ JSX เป็นเครื่องมือที่ยอดเยี่ยมสำหรับการสร้างส่วนประกอบที่ใช้งานง่าย ส่วนประกอบ typescript ทำให้นักพัฒนายินดีอย่างยิ่งที่จะรวมส่วนประกอบของคุณเข้ากับแอพของพวกเขาและสำรวจ API ของคุณ เรียนรู้เกี่ยวกับ React API ที่รู้จักกันน้อยสามตัว ซึ่งสามารถยกระดับคอมโพเนนต์ของคุณไปอีกระดับ และช่วยคุณสร้าง React Components ที่ดียิ่งขึ้นในบทความนี้

คุณเคยใช้ React.createElement โดยตรงหรือไม่? React.cloneElement อย่างไร React เป็นมากกว่าแค่การเปลี่ยน JSX ของคุณให้เป็น HTML มากกว่านั้นอีกมาก และเพื่อช่วยให้คุณยกระดับความรู้ของคุณเกี่ยวกับ API ที่ไม่ค่อยมีคนรู้จัก (แต่มีประโยชน์มาก) ที่ไลบรารี React ส่งมาด้วย เราจะพูดถึงบางส่วนและกรณีการใช้งานบางส่วนที่สามารถปรับปรุงการผสานรวมและประโยชน์ของคอมโพเนนต์ของคุณได้อย่างมาก

ในบทความนี้ เราจะพูดถึง React API ที่มีประโยชน์ซึ่งไม่เป็นที่รู้จักทั่วไปแต่มีประโยชน์มากสำหรับนักพัฒนาเว็บ ผู้อ่านควรมีประสบการณ์กับไวยากรณ์ React และ JSX ความรู้เกี่ยวกับ typescript มีประโยชน์ แต่ไม่จำเป็น ผู้อ่านจะละทิ้งทุกสิ่งที่จำเป็นต้องรู้เพื่อปรับปรุงส่วนประกอบ React อย่างมากเมื่อใช้ในแอปพลิเคชัน React

React.cloneElement

นักพัฒนาส่วนใหญ่อาจไม่เคยได้ยิน cloneElement หรือเคยใช้มาก่อน มีการแนะนำเมื่อเร็ว ๆ นี้เพื่อแทนที่ฟังก์ชัน cloneWithProps ที่เลิกใช้แล้วในขณะนี้ cloneElement โคลนองค์ประกอบ มันยังช่วยให้คุณรวมอุปกรณ์ประกอบฉากใหม่กับองค์ประกอบที่มีอยู่ แก้ไขหรือแทนที่ตามที่เห็นสมควร นี่เป็นการเปิดทางเลือกที่ทรงพลังอย่างมากสำหรับการสร้าง API ระดับโลกสำหรับส่วนประกอบที่ใช้งานได้ ลองดูลายเซ็น

 function cloneElement( element, props?, ...children)

นี่คือเวอร์ชันย่อของ Typescript:

 function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement

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

 export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }

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

 function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }

นี่เป็นสิ่งที่ดี แต่ถ้าผู้ใช้ต้องการแสดงองค์ประกอบ button แทนที่จะ a องค์ประกอบ เราสามารถเพิ่มอีกคุณสมบัติหนึ่งที่บอกส่วนประกอบว่าจะแสดงองค์ประกอบประเภทใด

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

นี่เป็นวิธีที่ดีกว่าโดยใช้ React.cloneElement

เราจะเริ่มต้นด้วยการเปลี่ยนอินเทอร์เฟซของเราเพื่ออ้างอิงประเภท ReactNode นี่เป็นประเภททั่วไปที่รวมทุกอย่างที่ React สามารถแสดงผล โดยทั่วไปแล้ว JSX Elements แต่สามารถเป็นสตริงและแม้แต่ null ได้ สิ่งนี้มีประโยชน์สำหรับการกำหนดว่าคุณต้องการยอมรับส่วนประกอบ React หรือ JSX เป็นอาร์กิวเมนต์แบบอินไลน์

 export interface ITabbarProps { links: ReactNode[] }

ตอนนี้เรากำลังขอให้ผู้ใช้มอบ React Elements ให้กับเรา และเราจะแสดงผลตามที่เราต้องการ

 function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }

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

เราสามารถทำได้ด้วย React.cloneElement และฟังก์ชันอื่น React.isValidElement สำหรับตรวจสอบอาร์กิวเมนต์สอดคล้องกับสิ่งที่เราคาดหวัง!

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

React.isValidElement

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

 function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }

เรากำลังเพิ่มส่วนประกอบสำคัญให้กับแต่ละองค์ประกอบที่เรากำลังส่งเข้าไป และทำให้ทุกลิงก์เป็นตัวหนาในเวลาเดียวกัน! ตอนนี้เราสามารถยอมรับ React Elements เป็นอุปกรณ์ประกอบฉากดังนี้:

 function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }

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

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

useState Setter Function

ใช้ตะขอ! useState hook มีประโยชน์อย่างยิ่งและเป็น API ที่ยอดเยี่ยมสำหรับการสร้างสถานะในส่วนประกอบของคุณอย่างรวดเร็วดังนี้:

 const [myValue, setMyValue] = useState()

เนื่องจากรันไทม์ของ JavaScript จึงอาจมีสะดุดบ้าง จำการปิด?

ในบางสถานการณ์ ตัวแปรอาจไม่ใช่ค่าที่ถูกต้องเนื่องจากบริบทที่มีอยู่ เช่น ใน for-loop โดยทั่วไปหรือเหตุการณ์แบบอะซิงโครนัส นี่เป็นเพราะการกำหนดขอบเขตคำศัพท์ เมื่อมีการสร้างฟังก์ชันใหม่ ขอบเขตคำศัพท์จะยังคงอยู่ เนื่องจากไม่มีฟังก์ชันใหม่ ขอบเขตคำศัพท์ของ newVal จะไม่ถูกรักษาไว้ ดังนั้นค่าจึงถูกละเลยการอ้างอิงตามเวลาที่มีการใช้งาน

 setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)

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

 setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)

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

หมายเหตุ : สำหรับการอ่านเพิ่มเติมในหัวข้อ async และ closures ฉันแนะนำให้อ่าน “ useState Lazy Initialization And Function Updates” โดย Kent C. Dodds

ฟังก์ชันอินไลน์ JSX

นี่คือตัวอย่าง Codepen ของฟังก์ชันอินไลน์ JSX:

ดูปากกา [Hello World in React](https://codepen.io/smashingmag/pen/QWgQQKR) โดย Gaurav Khanna

ดู Pen Hello World ใน React โดย Gaurav Khanna

ไม่ใช่ React API ต่อคำพูดอย่างแน่นอน

JSX รองรับฟังก์ชันอินไลน์และมีประโยชน์มากในการประกาศตรรกะอย่างง่ายด้วยตัวแปรแบบอินไลน์ ตราบใดที่ส่งคืนองค์ประกอบ JSX

นี่คือตัวอย่าง:

 function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }

ที่นี่เรากำลังประกาศโค้ดภายใน JSX เราสามารถเรียกใช้โค้ดที่กำหนดเองได้ และสิ่งที่เราต้องทำคือส่งคืนฟังก์ชัน JSX เพื่อให้แสดงผล

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

 })()}

สิ่งนี้มีประโยชน์ในสถานการณ์ที่คุณต้องการดำเนินการกับโครงสร้างข้อมูลการรวบรวมในลักษณะที่ซับซ้อนกว่าที่ .map มาตรฐานอนุญาตสำหรับภายในองค์ประกอบ JSX

 function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }

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

component extends type

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

นี่เป็นตัวอย่างง่ายๆ สมมติว่าเราต้องการแทนที่คุณสมบัติหนึ่งหรือสองคุณสมบัติขององค์ประกอบ button แต่ยังคงให้ตัวเลือกแก่นักพัฒนาในการเพิ่มคุณสมบัติอื่นๆ ให้กับปุ่ม เช่นการตั้งค่า type='button' หรือ type='submit' เห็นได้ชัดว่าเราไม่ต้องการสร้างองค์ประกอบปุ่มทั้งหมดขึ้นใหม่ เราเพียงต้องการขยายคุณสมบัติที่มีอยู่และอาจเพิ่มองค์ประกอบเสริมอีกหนึ่งอย่าง

 import React, { ButtonHTMLAttributes } from 'react'

ขั้นแรก เรานำเข้า React และคลาส ButtonHTMLAttributes ซึ่งเป็นประเภทที่รวมอุปกรณ์ประกอบฉากของ HTMLButtonElement คุณสามารถอ่านซอร์สโค้ดสำหรับอินเทอร์เฟซประเภทนี้ได้ที่นี่:

และคุณจะเห็นว่าทีม React ได้ปรับใช้ API ของเว็บทั้งหมดใน TypeScript เพื่อให้สามารถตรวจสอบประเภทได้

ต่อไป เราประกาศอินเทอร์เฟซของเราเช่นนั้น เพิ่มคุณสมบัติ status ของเรา

 interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }

และสุดท้าย เราทำสองสิ่ง เราใช้ ES6 destructuring เพื่อดึงอุปกรณ์ประกอบฉากที่เราสนใจออกมา ( status และ children ) และประกาศคุณสมบัติอื่นใดเป็นการ rest และในเอาต์พุต JSX เราส่งคืนองค์ประกอบปุ่ม โดยมีโครงสร้าง ES6 เพื่อเพิ่มคุณสมบัติเพิ่มเติมให้กับองค์ประกอบนี้

 function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }

ดังนั้นตอนนี้นักพัฒนาจึงสามารถเพิ่มอุปกรณ์ประกอบฉาก type หรืออุปกรณ์ประกอบฉากอื่น ๆ ที่ปุ่มมักจะมี เราได้เพิ่มอุปกรณ์ประกอบฉากที่เราใช้ใน className เพื่อกำหนดรูปแบบของปุ่ม

นี่คือตัวอย่างทั้งหมด:

 import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }

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

 import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }

ที่นี่เราใช้ prop className ที่ส่งผ่านไปยังองค์ประกอบ Button ของเราแล้วใส่กลับเข้าไป โดยมีการตรวจสอบความปลอดภัยในกรณีที่ prop นั้น undefined

บทสรุป

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

สำหรับการอ่านเพิ่มเติมเกี่ยวกับ React APIs ล่าสุด ขอเกี่ยว ฉันขอแนะนำให้อ่าน useHooks() Typescript Cheatsheet ยังมีข้อมูลที่ยอดเยี่ยมสำหรับ React และ Typescript Hooks