在 React.createElement 中使用 ref

时间:2021-03-09 15:22:02

标签: javascript reactjs react-hooks createelement use-ref

我有一个可重复使用的标题组件,它允许我传递一个 tag 属性,创建任何类型的标题(h1、h2、h3 等)。这是该组件:

heading.tsx

import React, { ReactNode } from 'react';

import s from './Heading.scss';

interface HeadingProps {
  tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
  children: ReactNode;
  className?: string;
}

export const Heading = ({ tag, children, className }: HeadingProps) => {
  const Tag = ({ ...props }: React.HTMLAttributes<HTMLHeadingElement>) =>
    React.createElement(tag, props, children);

  return <Tag className={s(s.heading, className)}>{children}</Tag>;
};

但是,我遇到了一个用例,我希望能够在 ref 上使用 useRef() 钩子拥有 Tag,以便我可以访问元素并使用 GSAP 制作动画。但是,我不知道如何使用 createElement

我尝试通过将 ref 直接添加到 Tag 组件,并将其添加到 Tag 道具来做到这一点,如下所示:

import React, { ReactNode, useRef } from 'react';

import s from './Heading.scss';

interface HeadingProps {
  tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
  children: ReactNode;
  className?: string;
}

export const Heading = ({ tag, children, className }: HeadingProps) => {
  const headingRef = useRef(null);
  const Tag = ({ ...props }: React.HTMLAttributes<HTMLHeadingElement>) =>
    React.createElement(tag, props, children, {ref: headingRef});

  return <Tag className={s(s.heading, className)} ref={headingRef}>{children}</Tag>;
};

我收到错误 Property 'ref' does not exist on type 'IntrinsicAttributes & HTMLAttributes<HTMLHeadingElement>'.

我做错了什么,如何安全地向组件添加 ref

谢谢。

2 个答案:

答案 0 :(得分:0)

您需要转发 ref,然后将其作为子项或元素传递,而是将其作为属性传递。

这里是关于引用转发的文档:https://reactjs.org/docs/forwarding-refs.html

以下是无需创建中间组件的标题代码示例:

import React, { ReactNode, useRef } from "react";

import s from "./Heading.scss";

interface HeadingProps {
  tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
  children: ReactNode;
  className?: string;
}

export const Heading = forwardRef(
  ({ tag: Tag, children, className }: HeadingProps, ref) => {
    return (
      <Tag className={s(s.heading, className)} ref={headingRef}>
        {children}
      </Tag>
    );
  }
);

export const HeadingWithoutJSX = forwardRef(
  ({ tag, children, className }: HeadingProps, ref) => {
    return createElement(
      tag,
      { className: s(s.heading, className), ref },
      children
    );
  }
);

答案 1 :(得分:0)

使用对象展开将 ref 添加到 props

const { useRef, useEffect } = React;

const Heading = ({ tag, children, className }) => {
  const headingRef = useRef(null);
  const Tag = (props) => React.createElement(tag, {ref: headingRef, ...props }, children);
  
  useEffect(() => { console.log(headingRef); }, []); // demo - use the ref

  return <Tag>{children}</Tag>;
};

ReactDOM.render(
  <div>
    <Heading tag="h1">h1</Heading>
    <Heading tag="h2">h2</Heading>
    <Heading tag="h3">h3</Heading>
  </div>,
  root
);
.as-console-wrapper { max-height: 100% !important; top: 0; left: 50% !important; }
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

然而,在另一个组件内创建一个组件意味着该组件将在每次渲染时重新创建。您可以通过使用 useMemo() 来避免这种情况。然而,更简单的选择是将 tag 本身呈现为 JSX:

const { useRef, useEffect } = React;

const Heading = ({ tag: Tag, children, className }) => {
  const headingRef = useRef(null);
  
  useEffect(() => { console.log(headingRef); }, []); // demo - use the ref

  return <Tag className={className} ref={headingRef}>{children}</Tag>;
};

ReactDOM.render(
  <div>
    <Heading tag="h1">h1</Heading>
    <Heading tag="h2">h2</Heading>
    <Heading tag="h3">h3</Heading>
  </div>,
  root
);
.as-console-wrapper { max-height: 100% !important; top: 0; left: 50% !important; }
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

相关问题