【问题标题】:Using ref with React.createElement将 ref 与 React.createElement 一起使用
【发布时间】:2021-06-07 12:13:36
【问题描述】:

我有一个可重复使用的标题组件,它允许我传递 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>;
};

但是,我遇到了一个用例,我希望能够在Tag 上使用useRef() 挂钩来拥有ref,以便我可以访问元素并制作动画与 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 &amp; HTMLAttributes&lt;HTMLHeadingElement&gt;'.

我做错了什么,如何安全地将ref 添加到组件中?

谢谢。

【问题讨论】:

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


    【解决方案1】:

    使用对象扩展将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>

    【讨论】:

      【解决方案2】:

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

      这是关于 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
          );
        }
      );
      

      【讨论】:

      • 我一直在搞砸这个,但我应该将它传递给Heading 组件,还是Tag
      • 我添加了带和不带 jsx 的代码示例。但是我删除了中间组件,因为不需要它。
      猜你喜欢
      • 2010-11-01
      • 1970-01-01
      • 2021-08-29
      • 2021-08-21
      • 1970-01-01
      • 2013-02-01
      • 2012-09-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多