【问题标题】:ForwardRef type based on props基于 props 的 ForwardRef 类型
【发布时间】:2021-09-05 02:34:40
【问题描述】:

我正在使用 React + Typescript。我正在开发一个可以返回动态 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" />
    </>
  );
}

Sandbox link

【问题讨论】:

    标签: javascript reactjs typescript ref


    【解决方案1】:

    为了做到这一点,我们需要使用函数重载,具有高阶函数模式和类型保护:

    已修复

    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' })}
        </>
      );
    }
    

    Playground

    您可能已经注意到,我只使用来自DynamicComponentprops

    还有T 泛型参数用于缩小ref 类型

    我没有实现 isButtonRefisLabelRef

    更新 看来我之前的例子没用。很抱歉。

    我的错。我已经修好了。

    作为替代解决方案,您可以覆盖内置的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.forwardRef() 吗?”
    • 坦率地说,我没有检查反应沙箱中的编译代码。能否请您分享链接。可能我没有考虑到一些反应限制
    • codesandbox.io/s/clever-bouman-skf65。我还尝试在那里解决另一个使用不同 ref 类型的反应问题,但现在没有成功。是否可以在不超载的情况下做类似的事情? function DynamicComponent(props: EmptyProp | LabelProp | ButtonProp) 之类的东西?
    • 它不起作用(在租用时不起作用),因为您从我的解决方案中删除了 &lt;WithRef /&gt;。请将我的解决方案粘贴到沙箱中而不进行任何修改
    猜你喜欢
    • 1970-01-01
    • 2021-01-07
    • 2019-12-30
    • 2018-10-02
    • 2020-11-04
    • 2021-12-24
    • 2023-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多