在 Typescript React App 中指定特定道具并接受一般 HTML 道具
Specify specific props and accept general HTML props in Typescript React App
我有一个 React Wrapper 组件,它接受一些道具,但将所有其他道具转发给子组件(尤其是与 className、id 等原生道具相关的内容)。
然而,当我传递本机道具时,Typescript 会抱怨。查看错误消息:
TS2339: Property 'className' does not exist on type
'IntrinsicAttributes & IntrinsicClassAttributes< Wrapper > & Readonly< {
children?: ReactNode; }> & Readonly< WrapperProps>'.
如何获得具有特定 props 的组件也接受原生 props(而不 接受任何 props 并放弃类型检查)?
我的代码如下所示:
interface WrapperProps extends JSX.IntrinsicAttributes {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper>
}
仅供参考:我在这里发现了一个类似的问题,但答案基本上放弃了类型检查,我想避免这种情况:
我们可以看看 div
props 是如何定义的:
interface IntrinsicElements {
div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}
如果我们使用 React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
作为基本类型,我们将拥有 div
的所有属性。由于 DetailedHTMLProps
只是将 ref
添加到 React.HTMLAttributes<HTMLDivElement>
,我们可以将其用作获取所有 div
属性的基本接口:
interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper> // works now
}
我的一个同事想通了。在这里分享以获得更广泛的知名度:
interface ComponentPropTypes = {
elementName?: keyof JSX.IntrinsicElements; // list of all native DOM components
...
}
// Function component
function Component({
elementName: Component = 'div',
...rest,
// React.HTMLAttributes<HTMLOrSVGElement>) provides all possible native DOM attributes
}: ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>)): JSX.Element {
return <Component {...rest} />;
}
// Class component
class Component extends React.Component<ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>> {
render() {
const {
elementName: Component,
...rest,
} = this.props;
return <Component {...rest} />
}
}
JSX.IntrinsicElements 有这个信息,例如
const FooButton: React.FC<JSX.IntrinsicElements['button']> = props => (
<button {...props} className={`foo ${props.className}`} />
)
// alternative...
const FooButton: React.FC<React.PropsWithoutRef<
JSX.IntrinsicElements['button']
>> = props => <button {...props} className={`foo ${props.className}`} />
在 react-typescript-cheatsheet 项目中发现了这个。
查看 ComponentProps
、ComponentPropsWithRef
和 ComponentPropsWithoutRef
- 这将接受可以是 "div"
、"button"
或任何其他组件。它将包括 React 特定的道具,例如 className
以及:
import React, {
forwardRef,
ComponentPropsWithoutRef,
ComponentProps,
ComponentPropsWithRef
} from "react";
const ExampleDivComponent = forwardRef<
HTMLDivElement,
ComponentPropsWithoutRef<"div">
>(({ children, ...props }, ref) => {
return (
<div {...props} ref={ref}>
{children}
</div>
);
});
<ExampleDivComponent
className=""
style={{ background: "green" }}
tabIndex={0}
onTouchStart={() => alert("touched")}
/>;
const ExampleButtonComponent: React.FC<ComponentProps<"button">> = ({
children,
...props
}) => {
return <button {...props}>{children}</button>;
};
<ExampleButtonComponent onClick={() => alert("clicked")} />;
我有一个 React Wrapper 组件,它接受一些道具,但将所有其他道具转发给子组件(尤其是与 className、id 等原生道具相关的内容)。
然而,当我传递本机道具时,Typescript 会抱怨。查看错误消息:
TS2339: Property 'className' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes< Wrapper > & Readonly< { children?: ReactNode; }> & Readonly< WrapperProps>'.
如何获得具有特定 props 的组件也接受原生 props(而不 接受任何 props 并放弃类型检查)?
我的代码如下所示:
interface WrapperProps extends JSX.IntrinsicAttributes {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper>
}
仅供参考:我在这里发现了一个类似的问题,但答案基本上放弃了类型检查,我想避免这种情况:
我们可以看看 div
props 是如何定义的:
interface IntrinsicElements {
div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}
如果我们使用 React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
作为基本类型,我们将拥有 div
的所有属性。由于 DetailedHTMLProps
只是将 ref
添加到 React.HTMLAttributes<HTMLDivElement>
,我们可以将其用作获取所有 div
属性的基本接口:
interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper> // works now
}
我的一个同事想通了。在这里分享以获得更广泛的知名度:
interface ComponentPropTypes = {
elementName?: keyof JSX.IntrinsicElements; // list of all native DOM components
...
}
// Function component
function Component({
elementName: Component = 'div',
...rest,
// React.HTMLAttributes<HTMLOrSVGElement>) provides all possible native DOM attributes
}: ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>)): JSX.Element {
return <Component {...rest} />;
}
// Class component
class Component extends React.Component<ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>> {
render() {
const {
elementName: Component,
...rest,
} = this.props;
return <Component {...rest} />
}
}
JSX.IntrinsicElements 有这个信息,例如
const FooButton: React.FC<JSX.IntrinsicElements['button']> = props => (
<button {...props} className={`foo ${props.className}`} />
)
// alternative...
const FooButton: React.FC<React.PropsWithoutRef<
JSX.IntrinsicElements['button']
>> = props => <button {...props} className={`foo ${props.className}`} />
在 react-typescript-cheatsheet 项目中发现了这个。
查看 ComponentProps
、ComponentPropsWithRef
和 ComponentPropsWithoutRef
- 这将接受可以是 "div"
、"button"
或任何其他组件。它将包括 React 特定的道具,例如 className
以及:
import React, {
forwardRef,
ComponentPropsWithoutRef,
ComponentProps,
ComponentPropsWithRef
} from "react";
const ExampleDivComponent = forwardRef<
HTMLDivElement,
ComponentPropsWithoutRef<"div">
>(({ children, ...props }, ref) => {
return (
<div {...props} ref={ref}>
{children}
</div>
);
});
<ExampleDivComponent
className=""
style={{ background: "green" }}
tabIndex={0}
onTouchStart={() => alert("touched")}
/>;
const ExampleButtonComponent: React.FC<ComponentProps<"button">> = ({
children,
...props
}) => {
return <button {...props}>{children}</button>;
};
<ExampleButtonComponent onClick={() => alert("clicked")} />;