簡體   English   中英

如何在傳遞道具時使用 TypeScript 實現“as”道具?

[英]How can I implement a "as" prop with TypeScript while passing down the props?

我正在構建一個組件庫,我需要其中一些組件具有可自定義的標簽名稱。 例如,有時看起來像<button>的東西實際上是<a> 所以我希望能夠像這樣使用按鈕組件:

<Button onClick={onClick}>Click me!</Button>
<Button as="a" href="/some-url">Click me!</Button>

理想情況下,我希望根據“as”道具推斷可用道具:

// Throws an error because the default value of "as" is "button",
// which doesn't accept the "href" attribute.
<Button href="/some-url">Click me!<Button>

我們可能還需要傳遞一個自定義組件:

// Doesn't throw an error because RouterLink has a "to" prop
<Button as={RouterLink} to="/">Click me!</Button>

這是實現,沒有 TypeScript:

function Button({ as = "button", children, ...props }) {
  return React.createElement(as, props, children);
}

那么,如何在傳遞道具時使用 TypeScript 實現“as”道具?

注意:我基本上是在嘗試做styled-components所做的事情。 但是我們使用的是 CSS 模塊和 SCSS,所以我無法負擔添加樣式組件。 不過,我對更簡單的選擇持開放態度。

我花了一些時間研究樣式化組件的類型聲明。 我能夠提取最低要求的代碼,這里是:

import * as React from "react";
import { Link } from "react-router-dom";

type CustomComponentProps<
  C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
  O extends object
> = React.ComponentPropsWithRef<
  C extends keyof JSX.IntrinsicElements | React.ComponentType<any> ? C : never
> &
  O & { as?: C };

interface CustomComponent<
  C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
  O extends object
> {
  <AsC extends keyof JSX.IntrinsicElements | React.ComponentType<any> = C>(
    props: CustomComponentProps<AsC, O>
  ): React.ReactElement<CustomComponentProps<AsC, O>>;
}

const Button: CustomComponent<"button", { variant: "primary" }> = (props) => (
  <button {...props} />
);

<Button variant="primary">Test</Button>;
<Button variant="primary" to="/test">
  Test
</Button>;
<Button variant="primary" as={Link} to="/test">
  Test
</Button>;
<Button variant="primary" as={Link}>
  Test
</Button>;

TypeScript操場

我從 styled-components 中刪除了很多比這更復雜的東西。 例如,他們有一些解決方法來處理我刪除的 class 組件。 所以這個片段可能需要針對高級用例進行定制。

我發現你可以用 JSX.IntrinsicElements 做同樣的事情。 我有面板元素:

export type PanelAsKeys = 'div' | 'label'
export type PanelAsKey = Extract<keyof JSX.IntrinsicElements, PanelAsKeys>
export type PanelAs<T extends PanelAsKey> = JSX.IntrinsicElements[T]
export type PanelAsProps<T extends PanelAsKey> = Omit<PanelAs<T>, 'className' | 'ref'>

我省略了像 ref 和 className 這樣的原生類型,因為我對這些字段有自己的類型

這就是道具的樣子

export type PanelProps<T extends PanelAsKey = 'div'> = PanelAsProps<T> & {}

const Panel = <T extends PanelAsKey = 'div'>(props: PanelProps<T>) => {}

然后你可以使用 React.createElement

return React.createElement(
  as || 'div',
  {
    tabIndex: tabIndex || -1,
    onClick: handleClick,
    onFocus: handleFocus,
    onBlur: handleBlur,
    onMouseEnter: handleMouseEnter,
    onMouseLeave: handleMouseLeave,
    'data-testid': 'panel',
    ...rest
  },
  renderChildren)

在此處輸入圖像描述

在這種情況下,我看不到 ts 錯誤,並且完成了 label 道具(如 htmlFor)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM