基于 props 的 ForwardRef 类型
ForwardRef type based on props
我正在使用 React + Typescript。我正在开发一个组件,它可以 return 动态 HTML 元素取决于道具:
interface Props {
label?: string;
}
const DynamicComponent = forwardRef<
HTMLButtonElement | HTMLLabelElement,
Props
>((props, ref) => {
if (props.label) {
return (
<label ref={ref as React.ForwardedRef<HTMLLabelElement>}>
{props.label}
</label>
);
}
return (
<button ref={ref as React.ForwardedRef<HTMLButtonElement>}>BUTTON</button>
);
});
是否可以根据 label
属性输入 ref
的界面?
export default function App() {
const btnRef = useRef<HTMLButtonElement>(null); // Allowed
const labelRef = useRef<HTMLLabelElement>(null); // Allowed
// const labelRef = useRef<HTMLButtonElement>(null); // Not allowed, because `label` prop was not provided
return (
<>
<DynamicComponent ref={btnRef} />
<DynamicComponent ref={labelRef} label="my label" />
</>
);
}
为此,我们需要使用函数重载,以及高阶函数模式和类型保护:
固定
import React, { forwardRef, useRef, Ref } from 'react'
interface Props {
label?: string;
}
// typeguard
const isLabelRef = (props: Props, ref: React.ForwardedRef<any>): ref is React.ForwardedRef<HTMLLabelElement> => {
return true // ! NON IMPLEMENTED
}
// typeguard
const isButtonRef = (props: Props, ref: React.ForwardedRef<any>): ref is React.ForwardedRef<HTMLButtonElement> => {
return true // ! NON IMPLEMENTED
}
// Higher order COmponent with overloads
function DynamicComponent<T extends HTMLButtonElement>(reference: Ref<T>): any
function DynamicComponent<T extends HTMLLabelElement>(reference: Ref<T>, props: Props): any
function DynamicComponent<T extends HTMLElement>(reference: Ref<T> | undefined, props?: Props) {
const WithRef = forwardRef<HTMLElement, Props>((_, ref) => {
if (props && isLabelRef(props, ref)) {
return (
<label ref={ref}>
{props.label}
</label>
);
}
if (props && isButtonRef(props, ref)) {
return (
<button ref={ref}>BUTTON</button>
);
}
return null
});
return <WithRef ref={reference} />
}
export default function App() {
const btnRef = useRef<HTMLButtonElement>(null); // Allowed
const labelRef = useRef<HTMLLabelElement>(null); // Allowed
return (
<>
{DynamicComponent(btnRef)}
{DynamicComponent(labelRef, { label: 'sdf' })}
</>
);
}
您可能已经注意到,我只使用 DynamicComponent
中的 props
。
另外 T
通用参数用于缩小 ref
类型
我 isButtonRef
和 isLabelRef
未实现
更新
看来我之前的例子没有用。抱歉。
我的错。我已经修好了。
作为替代解决方案,您可以覆盖内置的 forwardRef
函数。
interface Props {
label: string;
}
declare module "react" {
function forwardRef<T extends HTMLButtonElement, P>(
render: ForwardRefRenderFunction<HTMLButtonElement, never>
): ForwardRefExoticComponent<
PropsWithRef<{ some: number }> & RefAttributes<HTMLButtonElement>
>;
function forwardRef<T extends HTMLLabelElement, P extends { label: string }>(
render: ForwardRefRenderFunction<HTMLLabelElement, { label: string }>
): ForwardRefExoticComponent<
PropsWithRef<{ label: string }> & RefAttributes<HTMLLabelElement>
>;
}
const WithLabelRef = forwardRef<HTMLLabelElement, Props>((props, ref) => (
<label ref={ref}>{props.label}</label>
));
const WithButtonRef = forwardRef<HTMLButtonElement>((props, ref) => (
<button ref={ref}>{props}</button>
));
function App() {
const btnRef = useRef<HTMLButtonElement>(null);
const labelRef = useRef<HTMLLabelElement>(null);
const divRef = useRef<HTMLDivElement>(null);
return (
<>
<WithButtonRef ref={btnRef} />
<WithLabelRef ref={labelRef} label="my label" />
<WithLabelRef ref={divRef} label="my label" /> //expected error
</>
);
}
我正在使用 React + Typescript。我正在开发一个组件,它可以 return 动态 HTML 元素取决于道具:
interface Props {
label?: string;
}
const DynamicComponent = forwardRef<
HTMLButtonElement | HTMLLabelElement,
Props
>((props, ref) => {
if (props.label) {
return (
<label ref={ref as React.ForwardedRef<HTMLLabelElement>}>
{props.label}
</label>
);
}
return (
<button ref={ref as React.ForwardedRef<HTMLButtonElement>}>BUTTON</button>
);
});
是否可以根据 label
属性输入 ref
的界面?
export default function App() {
const btnRef = useRef<HTMLButtonElement>(null); // Allowed
const labelRef = useRef<HTMLLabelElement>(null); // Allowed
// const labelRef = useRef<HTMLButtonElement>(null); // Not allowed, because `label` prop was not provided
return (
<>
<DynamicComponent ref={btnRef} />
<DynamicComponent ref={labelRef} label="my label" />
</>
);
}
为此,我们需要使用函数重载,以及高阶函数模式和类型保护:
固定
import React, { forwardRef, useRef, Ref } from 'react'
interface Props {
label?: string;
}
// typeguard
const isLabelRef = (props: Props, ref: React.ForwardedRef<any>): ref is React.ForwardedRef<HTMLLabelElement> => {
return true // ! NON IMPLEMENTED
}
// typeguard
const isButtonRef = (props: Props, ref: React.ForwardedRef<any>): ref is React.ForwardedRef<HTMLButtonElement> => {
return true // ! NON IMPLEMENTED
}
// Higher order COmponent with overloads
function DynamicComponent<T extends HTMLButtonElement>(reference: Ref<T>): any
function DynamicComponent<T extends HTMLLabelElement>(reference: Ref<T>, props: Props): any
function DynamicComponent<T extends HTMLElement>(reference: Ref<T> | undefined, props?: Props) {
const WithRef = forwardRef<HTMLElement, Props>((_, ref) => {
if (props && isLabelRef(props, ref)) {
return (
<label ref={ref}>
{props.label}
</label>
);
}
if (props && isButtonRef(props, ref)) {
return (
<button ref={ref}>BUTTON</button>
);
}
return null
});
return <WithRef ref={reference} />
}
export default function App() {
const btnRef = useRef<HTMLButtonElement>(null); // Allowed
const labelRef = useRef<HTMLLabelElement>(null); // Allowed
return (
<>
{DynamicComponent(btnRef)}
{DynamicComponent(labelRef, { label: 'sdf' })}
</>
);
}
您可能已经注意到,我只使用 DynamicComponent
中的 props
。
另外 T
通用参数用于缩小 ref
类型
我 isButtonRef
和 isLabelRef
未实现
更新 看来我之前的例子没有用。抱歉。
我的错。我已经修好了。
作为替代解决方案,您可以覆盖内置的 forwardRef
函数。
interface Props {
label: string;
}
declare module "react" {
function forwardRef<T extends HTMLButtonElement, P>(
render: ForwardRefRenderFunction<HTMLButtonElement, never>
): ForwardRefExoticComponent<
PropsWithRef<{ some: number }> & RefAttributes<HTMLButtonElement>
>;
function forwardRef<T extends HTMLLabelElement, P extends { label: string }>(
render: ForwardRefRenderFunction<HTMLLabelElement, { label: string }>
): ForwardRefExoticComponent<
PropsWithRef<{ label: string }> & RefAttributes<HTMLLabelElement>
>;
}
const WithLabelRef = forwardRef<HTMLLabelElement, Props>((props, ref) => (
<label ref={ref}>{props.label}</label>
));
const WithButtonRef = forwardRef<HTMLButtonElement>((props, ref) => (
<button ref={ref}>{props}</button>
));
function App() {
const btnRef = useRef<HTMLButtonElement>(null);
const labelRef = useRef<HTMLLabelElement>(null);
const divRef = useRef<HTMLDivElement>(null);
return (
<>
<WithButtonRef ref={btnRef} />
<WithLabelRef ref={labelRef} label="my label" />
<WithLabelRef ref={divRef} label="my label" /> //expected error
</>
);
}