在 TypeScript中扩展HTML元素的属性

在我参与过的大多数大型应用程序和项目中,我经常发现自己构建了一堆组件,这些组件实际上是标准 HTML 元素之上的超集或抽象。一些示例包括自定义按钮元素,这些元素可能采用一个 prop 来定义该按钮是否应该是主按钮或辅助按钮,或者可能指示它将调用危险操作,例如从数据库中删除或删除项目。除了我想添加的道具之外,我仍然希望我的按钮具有按钮的所有属性。

另一个常见的情况是,我最终将创建一个允许我同时定义标签和输入字段的组件。我不想重新添加元素所<input />具有的所有属性。我希望我的自定义组件的行为就像输入字段一样,但也采用一个字符串作为标签,并自动连接htmlFor上的 prop<label />以与id上的相对应<input />。

在 JavaScript 中,我可以将{...props}任何 props 传递给底层 HTML 元素。这在 TypeScript 中可能有点棘手,我需要显式定义组件将接受哪些 props。虽然对组件接受的确切类型进行细粒度控制很好,但必须手动为每个属性添加类型信息可能很乏味。

在某些情况下,我需要一个可适应的组件,例如<div>,它可以根据当前主题更改样式。例如,也许我想根据用户是否手动启用 UI 的浅色或深色模式来定义应使用哪些样式。我不想为每个块元素(例如<section>、<article>、<aside>等)重新定义此组件。它应该能够表示不同语义的 HTML 元素,并且 TypeScript 自动调整以适应这些变化。

我们可以采用以下几种策略:

  • 对于仅对一种元素创建抽象的组件,我们可以扩展该元素的属性。

  • 对于想要定义不同元素的组件,我们可以创建多态组件。多态组件是设计为呈现为不同的 HTML 元素或组件,同时保持相同的属性和行为的组件。它允许我们指定一个 prop 来确定其渲染的元素类型。多态组件提供了灵活性和可重用性,而无需我们重新实现组件。举个具体的例子,你可以看看Radix 的多态组件的实现。(https://www.radix-ui.com/primitives/docs/utilities/polymorphic)

在本教程中,我们将了解第一个策略。

镜像和扩展 HTML 元素的属性

让我们从简介中提到的第一个示例开始。我们想要创建一个带有适当样式的按钮,以便在我们的应用程序中使用。在 JavaScript 中,我们也许可以做这样的事情:

镜像和扩展 HTML 元素的属性

让我们从简介中提到的第一个示例开始。我们想要创建一个带有适当样式的按钮,以便在我们的应用程序中使用。在 JavaScript 中,我们也许可以做这样的事情:

const Button = (props) => {
  return <button className="button" {...props} />;
};

在 TypeScript 中,我们只需添加我们知道需要的内容即可。例如,我们知道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} />;
};

现在,TypeScript 将阻止我们或其他任何人将className属性传递到我们的Button组件中。如果我们只想使用传入的内容扩展类列表,我们可以通过几种不同的方式来实现。我们可以将其附加到列表中:

type ButtonProps = React.ComponentProps<'button'>;

const Button = (props: ButtonProps) => {
  const className = 'button ' + props.className;

  return <button className={className.trim()} {...props} />;
};

我喜欢在处理类时使用clsx库,因为它代表我们处理大多数此类事情:

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';
};

我们现在说的是Button接受元素接受的所有 props<button>加上一个:variant。该道具将与我们继承的所有其他道具一起显示HTMLButtonElement。

Variant 显示为 Button 组件上的一个 prop

我们Button也可以添加对添加此类的支持:

const Button = ({ variant, className, ...props }: ButtonProps) => {
  return (
    <button
      className={clsx(
        'button',
        variant === 'primary' && 'button-primary',
        variant === 'secondary' && 'button-secondary',
        className,
      )}
      {...props}
    />
  );
};

我们现在可以更新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)

创建复合组件

我通常最终为自己制作的另一个常见组件是分别使用正确的for和属性正确连接标签和输入元素的组件。id我往往会厌倦一遍又一遍地输入以下内容:

<label htmlFor="set-count">Set Count</label>
<input type="number" id="set-count" name="set-count" />

如果不扩展 HTML 元素的 props,我最终可能会根据需要慢慢添加 props:

type LabeledInputProps = {
  id?: string;
  label: string;
  value: string | number;
  type?: string;
  className?: string;
  onChange?: ChangeEventHandler<HTMLInputElement>;
};

正如我们在按钮中看到的那样,我们可以以类似的方式重构它:

type LabeledInputProps = React.ComponentProps<'input'> & {
  label: string;
};

除了label我们要传递给(呃)标签(我们经常希望将其与输入分组)之外,我们还手动将道具一一传递。我们要添加吗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 并不关心,因为HTMLElement它非常灵活,因为 DOM 早于 TypeScript。如果我们把一些完全令人震惊的东西扔进去,它只会抱怨。

您可以在本教程的 GitHub 存储库分支中input查看上述所有更改。(https://github.com/stevekinney/polymorphic/tree/input)


文章来源地址https://www.toymoban.com/diary/js/424.html

到此这篇关于在 TypeScript中扩展HTML元素的属性的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

原文地址:https://www.toymoban.com/diary/js/424.html

如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用
如何以编程方式关闭/隐藏 Android 软键盘?
上一篇 2023年10月18日 23:28
jQuery各种获取信息,如当前页面标题,链接,当前焦点的元素
下一篇 2023年10月18日 23:55

相关文章

  • HTML 元素的属性有哪些?

    前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而又亲切的学习平台。在这个

    2024年02月14日
    浏览(41)
  • HTML 元素中的name 属性

    name 属性是 HTML 元素中常用的属性之一。它用于指定表单元素的名称,以便在提交表单时将其值与对应的键关联起来。 每个表单元素(例如 input 、 select 和 textarea )都可以具有一个 name 属性,该属性为元素提供一个唯一的标识符。当用户在提交表单时,浏览器会将表单元素的

    2024年02月13日
    浏览(41)
  • HTML元素和属性快速参考指南

    ​ 以下是几个与HTML元素和属性相关的参考资料网站链接: HTML Reference - 提供所有HTML元素和属性的免费指南。 W3Schools HTML Reference - W3Schools 提供一个广泛的HTML标签和属性参考。 freeCodeCamp HTML Cheat Sheet - freeCodeCamp 提供了一个HTML元素列表参考,适合初学者。 ​​​​​​​​

    2024年02月21日
    浏览(52)
  • 【html】Video元素的属性介绍和用法

    HTML video 元素 用于在 HTML 或者 XHTML 文档中嵌入媒体播放器,用于支持文档内的视频播放。 平常若涉及到视频内容,就会使用到它。 只有一个视频源 只有一个视频的话,在video元素中的src属性填入即可。 多个视频源。 多个视频源,需要搭配source元素指定视频源,然后浏览器

    2024年01月18日
    浏览(87)
  • HTML 元素的 class 和 id 属性有何区别?

    前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而又亲切的学习平台。在这个

    2024年02月13日
    浏览(45)
  • 【温故而知新】HTML元素/属性/标题/注释/段落/文本格式/头部/主体

    HTML(HyperText Markup Language,超文本标记语言)是用于创建网页的标准标记语言。它被用来描述网页的结构和内容,并且是网页浏览器能够解析和渲染网页的基础。HTML5 是 HTML 的最新版本,增加了许多新的元素和功能,以适应现代网页开发的需求。 HTML5 提供了许多新的元素和

    2024年01月16日
    浏览(49)
  • 【HTML 往日冒险 01】标签 元素 属性 注释 文本格式化 颜色 CSS

    说在前面 HTML 对于现在的我来说,熟悉又陌生,熟悉的是其标签的结构清晰,陌生的是其丰富的使用细节,长期不使用难免会失去许多相关的记忆,但是不妨让我们与W3school教程一同补全往日的冒险日志…(主要是从中提炼关键的信息,具体的知识点还是参考相关手册) 重新开

    2024年02月07日
    浏览(46)
  • 〖大前端 - 基础入门三大核心之JS篇㊲〗- DOM改变元素节点的css样式、HTML属性

    说明:该文属于 大前端全栈架构白宝书专栏, 目前阶段免费 , 如需要项目实战或者是体系化资源,文末名片加V! 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 从事过全栈研发、产品经理等工作,目前在公司担任研发部门CTO。 荣誉: 2022年度博客之星Top4、2023年度超

    2024年02月04日
    浏览(65)
  • 项目中拖拽元素,可以使用html的draggable属性,当然也可以用第三方插件interact

    html的draggable属性需要自己写逻辑,用人家封装好的代码简单逻辑清楚,非常香 链接: https://blog.csdn.net/vvv3171071/article/details/122705408

    2024年02月08日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包