javascript - 基于 props 的 ForwardRef 类型

标签 javascript reactjs typescript ref

我正在使用 React + Typescript。我正在开发一个可以根据 props 返回动态 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 的接口(interface)?

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

最佳答案

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

已修复

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

您可能已经注意到,我仅使用 DynamicComponent 中的 props

此外,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
    </>
  );
}

关于javascript - 基于 props 的 ForwardRef 类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68067622/

相关文章:

javascript - slice.call() 在 get() jquery 函数中做什么

javascript - 匹配打开和关闭的 html 标签的正则表达式

reactjs - 清除浏览器数据后无法在 Object.extractRehydrationInfo 读取未定义的属性(读取 [api.reducerPath])

javascript - 在非 InputElement 上调用 Angular2 单击事件

javascript - 如何将 skype-uri.js 与个性化按钮一起使用?

php - 根据文本输入更改选项值

reactjs - 如何保存或缓存组件的 html?

javascript - 使用 setTimeout react insideHTML

Angular 5 Material Snackbar 面板类配置

typescript - 有没有办法使用 typescript 和 Sequelize 向查询添加选项?