React API ที่มีประโยชน์สำหรับการสร้างส่วนประกอบที่ยืดหยุ่นด้วย TypeScript
เผยแพร่แล้ว: 2022-03-10 คุณเคยใช้ 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:
ไม่ใช่ 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