用於使用 TypeScript 構建靈活組件的有用 React API

已發表: 2022-03-10
快速總結↬ React with JSX 是製作易於使用的組件的絕佳工具。 Typescript 組件讓開發人員將您的組件集成到他們的應用程序中並探索您的 API 絕對是一種樂趣。 在本文中了解三個鮮為人知的 React API,它們可以讓您的組件更上一層樓,並幫助您構建更好的 React 組件。

你曾經直接使用過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 演示:

請參閱 Gaurav Khanna 的 Pen [Hello World in React](https://codepen.io/smashingmag/pen/QWgQQKR)。

請參閱 Gaurav Khanna 的筆 Hello World in React。

不完全是一個 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 解構來提取我們關心的道具( statuschildren ),並將任何其他屬性聲明為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 的重要信息。