在我參與過的大多數(shù)大型應用程序和項目中,我經(jīng)常發(fā)現(xiàn)自己構(gòu)建了一堆組件,這些組件實際上是標準 HTML 元素之上的超集或抽象。一些示例包括自定義按鈕元素,這些元素可能采用一個 prop 來定義該按鈕是否應該是主按鈕或輔助按鈕,或者可能指示它將調(diào)用危險操作,例如從數(shù)據(jù)庫中刪除或刪除項目。除了我想添加的道具之外,我仍然希望我的按鈕具有按鈕的所有屬性。
另一個常見的情況是,我最終將創(chuàng)建一個允許我同時定義標簽和輸入字段的組件。我不想重新添加元素所<input />具有的所有屬性。我希望我的自定義組件的行為就像輸入字段一樣,但也采用一個字符串作為標簽,并自動連接htmlFor上的 prop<label />以與id上的相對應<input />。
在 JavaScript 中,我可以將{...props}任何 props 傳遞給底層 HTML 元素。這在 TypeScript 中可能有點棘手,我需要顯式定義組件將接受哪些 props。雖然對組件接受的確切類型進行細粒度控制很好,但必須手動為每個屬性添加類型信息可能很乏味。
在某些情況下,我需要一個可適應的組件,例如<div>,它可以根據(jù)當前主題更改樣式。例如,也許我想根據(jù)用戶是否手動啟用 UI 的淺色或深色模式來定義應使用哪些樣式。我不想為每個塊元素(例如<section>、<article>、<aside>等)重新定義此組件。它應該能夠表示不同語義的 HTML 元素,并且 TypeScript 自動調(diào)整以適應這些變化。
我們可以采用以下幾種策略:
對于僅對一種元素創(chuàng)建抽象的組件,我們可以擴展該元素的屬性。
對于想要定義不同元素的組件,我們可以創(chuàng)建多態(tài)組件。多態(tài)組件是設計為呈現(xiàn)為不同的 HTML 元素或組件,同時保持相同的屬性和行為的組件。它允許我們指定一個 prop 來確定其渲染的元素類型。多態(tài)組件提供了靈活性和可重用性,而無需我們重新實現(xiàn)組件。舉個具體的例子,你可以看看Radix 的多態(tài)組件的實現(xiàn)。(https://www.radix-ui.com/primitives/docs/utilities/polymorphic)
在本教程中,我們將了解第一個策略。
鏡像和擴展 HTML 元素的屬性
讓我們從簡介中提到的第一個示例開始。我們想要創(chuàng)建一個帶有適當樣式的按鈕,以便在我們的應用程序中使用。在 JavaScript 中,我們也許可以做這樣的事情:
鏡像和擴展 HTML 元素的屬性
讓我們從簡介中提到的第一個示例開始。我們想要創(chuàng)建一個帶有適當樣式的按鈕,以便在我們的應用程序中使用。在 JavaScript 中,我們也許可以做這樣的事情:
const Button = (props) => { return <button className="button" {...props} />; };
在 TypeScript 中,我們只需添加我們知道需要的內(nèi)容即可。例如,我們知道children如果我們希望自定義按鈕的行為與 HTML 按鈕相同,則需要:
const Button = ({ children }: React.PropsWithChildren) => { return <button className="button">{children}</button>; };
您可以想象一次添加一個屬性可能會有點乏味。<button>相反,我們可以告訴 TypeScript 我們想要匹配它在 React 中用于元素的相同屬性:
const Button = (props: React.ComponentProps<'button'>) => { return <button className="button" {...props} />; };
但我們有一個新問題?;蛘吒_切地說,我們遇到了JavaScript 示例中也存在的問題,但我們忽略了它。如果有人使用我們的新Button組件傳遞一個classNameprop,它將覆蓋我們的className. 我們可以(并且我們將會)添加一些代碼來處理這個問題,但我不想錯過向您展示如何在 TypeScript 中使用實用程序類型的機會,以表示“我想使用所有來自 HTML 按鈕的 props,除了一個(或多個)”:
type ButtonProps = Omit<React.ComponentProps<'button'>, 'className'>; const Button = (props: ButtonProps) => { return <button className="button" {...props} />; };
現(xiàn)在,TypeScript 將阻止我們或其他任何人將className屬性傳遞到我們的Button組件中。如果我們只想使用傳入的內(nèi)容擴展類列表,我們可以通過幾種不同的方式來實現(xiàn)。我們可以將其附加到列表中:
type ButtonProps = React.ComponentProps<'button'>; const Button = (props: ButtonProps) => { const className = 'button ' + props.className; return <button className={className.trim()} {...props} />; };
我喜歡在處理類時使用clsx庫,因為它代表我們處理大多數(shù)此類事情:
import React from 'react'; import clsx from 'clsx'; type ButtonProps = React.ComponentProps<'button'>; const Button = ({ className, ...props }: ButtonProps) => { return <button className={clsx('button', className)} {...props} />; }; export default Button;
我們學習了如何限制組件接受的 props。為了擴展 props,我們可以使用交集:
type ButtonProps = React.ComponentProps<'button'> & { variant?: 'primary' | 'secondary'; };
我們現(xiàn)在說的是Button接受元素接受的所有 props<button>加上一個:variant。該道具將與我們繼承的所有其他道具一起顯示HTMLButtonElement。
我們Button也可以添加對添加此類的支持:
const Button = ({ variant, className, ...props }: ButtonProps) => { return ( <button className={clsx( 'button', variant === 'primary' && 'button-primary', variant === 'secondary' && 'button-secondary', className, )} {...props} /> ); };
我們現(xiàn)在可以更新src/application.tsx以使用新的按鈕組件:
diff --git a/src/application.tsx b/src/application.tsx index 978a61d..fc8a416 100644 --- a/src/application.tsx +++ b/src/application.tsx @@ -1,3 +1,4 @@ +import Button from './components/button'; import useCount from './use-count'; const Counter = () => { @@ -8,15 +9,11 @@ const Counter = () => { <h1>Counter</h1> <p className="text-7xl">{count}</p> <div className="flex place-content-between w-full"> - <button className="button" onClick={decrement}> + <Button onClick={decrement}> Decrement - </button> - <button className="button" onClick={reset}> - Reset - </button> - <button className="button" onClick={increment}> - Increment - </button> + </Button> + <Button onClick={reset}>Reset</Button> + <Button onClick={increment}>Increment</Button> </div> <div> <form @@ -32,9 +29,9 @@ const Counter = () => { > <label htmlFor="set-count">Set Count</label> <input type="number" id="set-count" name="set-count" /> - <button className="button-primary" type="submit"> + <Button variant="primary" type="submit"> Set - </button> + </Button> </form> </div> </main>
您可以在本教程的 GitHub 存儲庫分支中button找到上述更改。(https://github.com/stevekinney/polymorphic/tree/button)
創(chuàng)建復合組件
我通常最終為自己制作的另一個常見組件是分別使用正確的for和屬性正確連接標簽和輸入元素的組件。id我往往會厭倦一遍又一遍地輸入以下內(nèi)容:
<label htmlFor="set-count">Set Count</label> <input type="number" id="set-count" name="set-count" />
如果不擴展 HTML 元素的 props,我最終可能會根據(jù)需要慢慢添加 props:
type LabeledInputProps = { id?: string; label: string; value: string | number; type?: string; className?: string; onChange?: ChangeEventHandler<HTMLInputElement>; };
正如我們在按鈕中看到的那樣,我們可以以類似的方式重構(gòu)它:
type LabeledInputProps = React.ComponentProps<'input'> & { label: string; };
除了label我們要傳遞給(呃)標簽(我們經(jīng)常希望將其與輸入分組)之外,我們還手動將道具一一傳遞。我們要添加嗎autofocus?最好添加另一個道具。最好做這樣的事情:
import { ComponentProps } from 'react'; type LabeledInputProps = ComponentProps<'input'> & { label: string; }; const LabeledInput = ({ id, label, ...props }: LabeledInputProps) => { return ( <> <label htmlFor={id}>{label}</label> <input {...props} id={id} readOnly={!props.onChange} /> </> ); }; export default LabeledInput;
我們可以在以下位置交換新組件src/application.tsx:
<LabeledInput id="set-count" label="Set Count" type="number" onChange={(e) => setValue(e.target.valueAsNumber)} value={value} />
我們可以取出需要使用的東西,然后將其他所有東西傳遞給組件<input />,然后在接下來的日子里假裝它是一個標準HTMLInputElement。
TypeScript 并不關(guān)心,因為HTMLElement它非常靈活,因為 DOM 早于 TypeScript。如果我們把一些完全令人震驚的東西扔進去,它只會抱怨。
您可以在本教程的 GitHub 存儲庫分支中input查看上述所有更改。(https://github.com/stevekinney/polymorphic/tree/input)文章來源:http://www.zghlxwxcb.cn/article/424.html
文章來源地址http://www.zghlxwxcb.cn/article/424.html
到此這篇關(guān)于在 TypeScript中擴展HTML元素的屬性的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!