用於使用 TypeScript 構建靈活組件的有用 React API
已發表: 2022-03-10 你曾經直接使用過React.createElement
嗎? React.cloneElement
呢? React 不僅僅是將您的 JSX 轉換為 HTML。 更多,並幫助您提高對 React 庫附帶的鮮為人知(但非常有用)的 API 的了解。 我們將介紹其中的一些以及它們的一些用例,這些用例可以極大地增強組件的集成度和實用性。
在本文中,我們將介紹一些不太為人所知但對 Web 開發人員非常有用的有用的 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 元素,但也可以是字符串,甚至是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
如果元素是有效的 React 元素並且 React 可以渲染它,則此函數返回true
。 這是修改上一個示例中的元素的示例。
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
在這裡,我們為傳入的每個元素添加了一個 key prop,同時使每個鏈接變為粗體! 我們現在可以接受任意 React Elements 作為 props,如下所示:
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
我們可以覆蓋元素上設置的任何道具,並輕鬆接受各種元素,從而使我們的組件更加靈活和易於使用。
“
這裡的好處是如果我們想為我們的按鈕設置一個自定義的onClick
處理程序,我們可以這樣做。 接受 React 元素本身作為參數是賦予組件設計靈活性的強大方法。
useState
設置器函數
使用鉤子! useState
鉤子非常有用,並且是一個出色的 API,可用於將狀態快速構建到組件中,如下所示:
const [myValue, setMyValue] = useState()
由於 JavaScript 運行時,它可能會出現一些問題。 還記得閉包嗎?
在某些情況下,變量可能不是正確的值,因為它所在的上下文,例如在常見的 for 循環或異步事件中。 這是因為詞法作用域。 當創建一個新函數時,詞法範圍被保留。 因為沒有 new 函數,所以不會保留newVal
的詞法範圍,因此該值實際上在使用時被取消引用。
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
您需要做的是將 setter 用作函數。 通過創建一個新函數,變量引用保留在詞法範圍內,並且 currentVal 由 React useState Hook 本身傳入。
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
這將確保您的值正確更新,因為在正確的上下文中調用了 setter 函數。 React 在這裡所做的是在正確的上下文中調用您的函數以發生 React 狀態更新。 這也可以用於其他有助於對當前值進行操作的情況,React 使用第一個參數作為當前值調用您的函數。
注意:關於異步和閉包主題的更多閱讀,我建議閱讀 Kent C. Dodds 的“ useState
Lazy Initialization And Function Updates”。
JSX 內聯函數
這是 JSX 內聯函數的 Codepen 演示:
不完全是一個 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'
。 我們顯然不想重新創建整個 button 元素,我們只想擴展它現有的屬性,也許再添加一個 prop。
import React, { ButtonHTMLAttributes } from 'react'
首先我們導入 React 和ButtonHTMLAttributes
類,這是一個包含HTMLButtonElement
屬性的類型。 您可以在此處閱讀此類接口的源代碼:
你可以看到 React 團隊已經在 TypeScript 中重新實現了所有 Web 的 API,因此可以進行類型檢查。
接下來,我們像這樣聲明我們的接口,添加我們的status
屬性。
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
最後,我們做了幾件事。 我們使用 ES6 解構來提取我們關心的道具( 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 是一個非常強大的庫,它迅速流行是有充分理由的。 它為您提供了一個強大的工具集來構建高性能且易於維護的 Web 應用程序。 它非常靈活,同時又非常嚴格,如果您知道如何使用它,這將非常有用。 這些只是一些值得注意且在很大程度上被忽視的 API。 在您的下一個項目中嘗試一下!
要進一步了解最新的 React API、鉤子,我建議閱讀 useHooks()。 Typescript Cheatsheet 也有一些關於 React 和 Typescript Hooks 的重要信息。