reactjs - Typescript/React ForwardRefExoticComponent daha spesifik HTMLElement ref ile atama başarısız oluyor


Bir HOC yazmaya ve ona geçirebileceğiniz çocukları yazım türüyle belirtmeye çalışıyorum.

senaryo biraz şöyle görünüyor (ayrıca bu sandbox'ta: https://codesandbox.io/s/amazing-taussig-h2dpm?file=/src/App.tsx):

interface HOCProps {
    modules: React.ForwardRefExoticComponent<
        HOCChildProps & React.RefAttributes<HTMLElement>
    >[];
}
interface HOCChildProps {
    ...
}
declare const HOC: React.FC<HOCProps>;

// this produces an error in HOCTest if HTMLDivElement is used
// and an error in HOCChildTest if HTMLElement is used
// as the ref type in React.forwardRef
const HOCChildTest = React.forwardRef<HTMLDivElement, HOCChildProps>(
    (props, ref) => (
        <div ref={ref}>
            ...
        </div>
    )
);
const HOCTest: React.FC = () => <HOC modules={[HOCChildTest]} />;

Kod hâlâ çalışıyor, ama türlemeler çalışmıyor.

görünüşe göre bu görev başarısız oluyor:

declare const divTest: React.ForwardRefExoticComponent<
    React.RefAttributes<HTMLDivElement>
>;

export const htmlTest: React.ForwardRefExoticComponent<
    React.RefAttributes<HTMLElement>
> = divTest;

Typescript hata mesajı:

Type 'ForwardRefExoticComponent<RefAttributes<HTMLDivElement>>' is not assignable to type 'ForwardRefExoticComponent<RefAttributes<HTMLElement>>'.
  Types of parameters 'props' and 'props' are incompatible.
    Type 'RefAttributes<HTMLElement>' is not assignable to type 'RefAttributes<HTMLDivElement>'.

Son satırda RefAttributes<HTMLElement>'i RefAttributes<HTMLDivElement>'e atamaya çalıştığımı söylüyor, ama atamayı ters yönde yapıyorum (ilk satırın doğru şekilde gösterdiği gibi).

Bir şeyleri mi kaçırıyorum? Çünkü daha özel bir HTMLDivElement ref'ine sahip bir bileşenin, daha genel bir HTMLElement ref'ine sahip bir bileşene atanabilir olacağını varsaymıştım.

Cevap #1

forwardRef ile iletilebilecek ref türlerinden biri callback ref'tir. Eğer callback'leri iletme ile uğraştıysanız, callback ile çalışırken extends ile uğraşmanın biraz "ters" olduğunu bilirsiniz. Ancak forwardRef, bir RefObject'i de iletebilir.

Nesnenin ve geri aramanın birleşimiyle uğraştığınız için, tam doğru türde bir referansa sahip olmanız gerekir. Daha geniş veya daha dar olamaz.

Bunu açıklığa kavuşturmak için bazı örnekler şunlardır:

// ok
const f = (ref: (instance: HTMLDivElement | null) => void) => <div ref={ref}/>

// ok
const f = (ref: MutableRefObject<HTMLDivElement | null>) => <div ref={ref}/>

// ok because HTMLDivElement extends HTMLElement
const f = (ref: (instance: HTMLElement | null) => void) => <div ref={ref}/>

// error because HTMLElement is not assignable to HTMLDivElement
const f = (ref: MutableRefObject<HTMLElement | null>) => <div ref={ref}/>

// error because a div ref can only be assigned to a div
const f = (Element: "div" | "a", ref: MutableRefObject<HTMLDivElement | null>) => <Element ref={ref}/>

// error because we need a more exact ref type
const f = (Element: "div" | "a", ref: MutableRefObject<HTMLElement | null>) => <Element ref={ref}/>

// ok - the only possible ref here must be both a div and an a
const f = (Element: "div" | "a", ref: MutableRefObject<(HTMLDivElement & HTMLAnchorElement) | null>) => <Element ref={ref}/>

Bunun bir hata olmasını bekliyordum ve görünüşe göre öyle değil.

const f = (Element: "div" | "a", ref: (instance: (HTMLDivElement & HTMLAnchorElement) | null) => void) => <Element ref={ref}/>

Şimdi ise olası bir çözüme rastladım, bu da referansınızın tüm bireysel referans türlerinin kesişimi olabileceği.

HOC'nin ne yaptığını bilmeden en iyi çözümün ne olduğunu söylemek zor.