javascript - tip ForwardRef bazat pe proprietăți
Folosesc React + TypeScript. Lucrez la un component care poate returna un element HTML dinamic, în funcție de props:
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>
);
});
Este posibil să tipăriți interfața lui ref într-un mod care să depindă de proprietatea label?
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" />
</>
);
}
Link către sandbox
Răspuns #1
Pentru a face asta, trebuie să folosim supraincarcarea funcțiilor, cu modelul de funcții de ordin superior și tipuri de protecție:
FIX
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' })}
</>
);
}
Teren de joacă
După cum ați observat, eu folosesc doar props din DynamicComponent.
De asemenea, parametrul generic T servește pentru a restrânge tipul ref
Am lăsat isButtonRef și isLabelRef neimplementate
ACTUALIZARE Se pare că exemplul meu anterior este inutil. Îmi pare rău pentru asta.
Scuze. Am rezolvat deja problema.
Ca soluție alternativă, poți suprascrie funcția încorporată 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
</>
);
}
