简体   繁体   English

将 ref 与 React.createElement 一起使用

[英]Using ref with React.createElement

I've got a reusable heading component that allows me to pass a tag prop, creating any sort of heading (h1, h2, h3 etc).我有一个可重用的标题组件,它允许我传递一个tag道具,创建任何类型的标题(h1、h2、h3 等)。 Here's that component:这是那个组件:

heading.tsx标题.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>;
};

However, I'm coming across a use case where I'd like to be able to have a ref , using the useRef() hook, on the Tag , so that I can access the element and animate with GSAP.但是,我遇到了一个用例,我希望能够在Tag上使用useRef()挂钩来使用ref ,这样我就可以访问元素并使用 GSAP 进行动画处理。 However, I can't figure out how do this using createElement .但是,我无法弄清楚如何使用createElement来做到这一点。

I've tried to do it by adding a ref directly to the Tag component, and adding it to the Tag props like so:我尝试通过将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>;
};

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

What am I doing wrong, and how can I safely add a ref to the component?我做错了什么,如何安全地向组件添加ref

Thanks.谢谢。

You need to forward ref and then pass it not as a child or element, but as it's property.您需要转发 ref,然后将其作为子元素或元素传递,而是作为其属性传递。

Here is documentation on ref forwarding: https://reactjs.org/docs/forwarding-refs.html这是关于参考转发的文档: https://reactjs.org/docs/forwarding-refs.html

Here is code exapmles for heading without intermediate component creation:这是无需创建中间组件的标题的代码示例:

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

Use object spread to add the ref to the props :使用 object spread 将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>

However, creating a component inside another component would mean that component would be recreated on each render.但是,在另一个组件中创建一个组件意味着该组件将在每次渲染时重新创建。 You can avoid that by using useMemo() .您可以通过使用useMemo()来避免这种情况。 However an easier option would be to render the tag itself as JSX:然而,更简单的选择是将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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM