英文:
How to type React.forwardRef when creating conditional Anchor or Button component?
问题
以下是您要翻译的代码部分:
type ConditionalProps =
| ({
as: "button";
} & React.ButtonHTMLAttributes<HTMLButtonElement>)
| ({
as: "a";
} & React.AnchorHTMLAttributes<HTMLAnchorElement>);
const Trigger = forwardRef<
HTMLAnchorElement | HTMLButtonElement, // <- this is an issue
ConditionalProps
>(function Trigger({ as, ...props }, ref) {
return createElement(as, {
...props,
ref, // <- error
});
});
错误信息如下:
Type 'ForwardedRef<HTMLAnchorElement | HTMLButtonElement>' is not
assignable to type 'Ref<HTMLAnchorElement> | undefined'.
英文:
I'm creating a component that can be a Button or Anchor tag. But I'm having trouble making a conditional typing for the ref. How can I get the ref to be accepted?
type ConditionalProps =
| ({
as: "button";
} & React.ButtonHTMLAttributes<HTMLButtonElement>)
| ({
as: "a";
} & React.AnchorHTMLAttributes<HTMLAnchorElement>);
const Trigger = forwardRef<
HTMLAnchorElement | HTMLButtonElement, // <- this is an issue
ConditionalProps
>(function Trigger({ as, ...props }, ref) {
return createElement(as, {
...props,
ref, // <- error
});
});
The error states:
Type 'ForwardedRef<HTMLAnchorElement | HTMLButtonElement>' is not
assignable to type 'Ref<HTMLAnchorElement> | undefined'.
答案1
得分: 1
你可以根据获得的 as
属性有两个不同的 return
语句,并对给定的类型使用 TypeScript 断言。
type ConditionalProps =
| ({
as: 'button';
} & React.ButtonHTMLAttributes<HTMLButtonElement>)
| ({
as: 'a';
} & React.AnchorHTMLAttributes<HTMLAnchorElement>);
const Trigger = forwardRef<
HTMLAnchorElement | HTMLButtonElement,
ConditionalProps
>((function Trigger({ as, ...props }, ref) {
const elementType = 'button';
if (as === 'a') {
const aRef = ref as Ref<HTMLAnchorElement> | undefined;
return createElement(as, {
...props,
aRef,
});
} else {
const buttonRef = ref as Ref<HTMLButtonElement> | undefined;
return createElement(as, {
...props,
buttonRef,
});
}
});
英文:
You could have two return
statements depending on the as
property you get, and use TypeScript assertions for the given type.
type ConditionalProps =
| ({
as: 'button';
} & React.ButtonHTMLAttributes<HTMLButtonElement>)
| ({
as: 'a';
} & React.AnchorHTMLAttributes<HTMLAnchorElement>);
const Trigger = forwardRef<
HTMLAnchorElement | HTMLButtonElement,
ConditionalProps
>(function Trigger({ as, ...props }, ref) {
const elementType = 'button';
if (as === 'a') {
const aRef = ref as Ref<HTMLAnchorElement> | undefined;
return createElement(as, {
...props,
aRef,
});
} else {
const buttonRef = ref as Ref<HTMLButtonElement> | undefined;
return createElement(as, {
...props,
buttonRef,
});
}
});
答案2
得分: 0
尝试了您的代码后,我没有收到类型错误,联合类型在https://codesandbox.io/s/react-typescript-forked-elwo35?file=/src/App.tsx 上似乎运行正常。
但是对于这种情况,我认为泛型通常效果很好,因为只涉及一种类型,所以相当简单。
所以通常可以设置一些类型,为了清晰起见,这里有点冗长。
import { HTMLAttributes } from "react";
type TagElementMap = {
readonly button: HTMLButtonElement;
readonly a: HTMLAnchorElement;
};
type Tag = keyof TagElementMap;
type TagElement<T extends Tag> = TagElementMap[T];
type TagAttributes<T extends Tag> = HTMLAttributes<TagElementMap[T]>;
type Props<T extends Tag> = {
as: T;
myRef: RefObject<TagElement<T>>;
} & TagAttributes<T>;
然后,您可以使您的组件具有泛型。在此示例中,我使用了一个自定义的 ref 属性,而不是 forwardRef。逻辑是相同的,只是使用 forwardRef 麻烦一些,因为您必须在另一个具有自定义 ref 的组件中包装整个内容,然后将其传递给泛型。
const Trigger = <T extends Tag>({ as, ...props }: Props<T>) =>
createElement(as, props);
这里是一个完整示例 https://codesandbox.io/s/react-typescript-forked-k2flhc?file=/src/App.tsx
英文:
Trying your code I didnt get type errors, union seemed fine on https://codesandbox.io/s/react-typescript-forked-elwo35?file=/src/App.tsx
But whenever for these kind of situations I think generics work well, its just one type so fairly simple.
So usually can set up some types like this, bit verbose here for clarity.
import { HTMLAttributes } from "react";
type TagElementMap = {
readonly button: HTMLButtonElement;
readonly a: HTMLAnchorElement;
};
type Tag = keyof TagElementMap;
type TagElement<T extends Tag> = TagElementMap[T];
type TagAttributes<T extends Tag> = HTMLAttributes<TagElementMap[T]>;
type Props<T extends Tag> = {
as: T;
myRef: RefObject<TagElement<T>>;
} & TagAttributes<T>;
Then you can make your component generic, in this example I'm using a custom ref prop instead of forward, the logic is the same it's just annoying to do with forwardref you have to wrap the whole thing in another component with custom ref anyway and then forward that one with generic..
const Trigger = <T extends Tag>({ as, ...props }: Props<T>) =>
createElement(as, props);
Here's a full example https://codesandbox.io/s/react-typescript-forked-k2flhc?file=/src/App.tsx
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论