简体   繁体   English

如何在传递道具时使用 TypeScript 实现“as”道具?

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

I am building a library of components and I need some of them to have a customizable tag name.我正在构建一个组件库,我需要其中一些组件具有可自定义的标签名称。 For example, sometimes what looks like a <button> is actually a <a> .例如,有时看起来像<button>的东西实际上是<a> So I would like to be able to use the button component like so:所以我希望能够像这样使用按钮组件:

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

Ideally, I would like the available props to be inferred based on the "as" prop:理想情况下,我希望根据“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>

We might need to pass a custom component as well:我们可能还需要传递一个自定义组件:

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

Here's the implementation, without TypeScript:这是实现,没有 TypeScript:

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

So, how can I implement a "as" prop with TypeScript while passing down the props?那么,如何在传递道具时使用 TypeScript 实现“as”道具?

Note: I am basically trying to do what styled-components does.注意:我基本上是在尝试做styled-components所做的事情。 But we are using CSS modules and SCSS so I can't afford adding styled-components.但是我们使用的是 CSS 模块和 SCSS,所以我无法负担添加样式组件。 I am open to simpler alternatives, though.不过,我对更简单的选择持开放态度。

I spent some time digging into styled-components' types declarations.我花了一些时间研究样式化组件的类型声明。 I was able to extract the minimum required code, here it is:我能够提取最低要求的代码,这里是:

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 playground TypeScript操场

I removed a lot of stuff from styled-components which is way more complex than that.我从 styled-components 中删除了很多比这更复杂的东西。 For example, they have some workaround to deal with class components which I removed.例如,他们有一些解决方法来处理我删除的 class 组件。 So this snippet might need to be customized for advanced use cases.所以这个片段可能需要针对高级用例进行定制。

I found that you can make the same thing with JSX.IntrinsicElements.我发现你可以用 JSX.IntrinsicElements 做同样的事情。 I have Panel element:我有面板元素:

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

I omitted native types like ref and className because i have my own types for these fields我省略了像 ref 和 className 这样的原生类型,因为我对这些字段有自己的类型

And this how props will look look like这就是道具的样子

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

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

Then you can use React.createElement然后你可以使用 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)

在此处输入图像描述

I see no ts errors in this case and have completions for label props like htmlFor在这种情况下,我看不到 ts 错误,并且完成了 label 道具(如 htmlFor)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何传递对象数组作为对组件的支持,然后将数组成员传递作为对嵌套组件的支持? - How can I pass down an array of objects as a prop to a component and then pass down the array's members as props to nested components? 传递 props 时何时使用 this.prop? - When to use this.prop when passing down props? 我怎样才能解构一个 React 道具并仍然访问其他道具? - How can I destructure a React prop and still access other props? 如何使用 TypeScript 传递组件传播道具? - How do I pass down components spread props with TypeScript? 如何在 Typescript 中传递道具? - How can I pass props in Typescript? 当道具在 React、Typescript 中可以有多种类型时,如何传递特定道具? - How to pass a specific prop when the props can have multiple types in React, Typescript? 通过 React 组件向下传递道具时如何使用 Typescript 和接口? - How to use Typescript and Interfaces when passing props down through React components? 如何在 React TypeScript 中实现 e 和 props? - How to implement e and props in React TypeScript? 将 state 传递到子组件的 prop 中,使用“(this.props.(propHere))”内联访问它,由于“This”而未定义 - Passing state down into a prop to child component, accessing it inline with “(this.props.(propHere))” coming up undefined due to “This” 如何像Angact一样在Angular 2+中传递道具? - How can I pass props down in Angular 2+ like React?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM