如何在基于React.createElement实现的可复用Heading组件中正确使用ref?
Let's break down what's going wrong and fix your ref issue step by step.
What Caused the Error?
Your original approach had two key problems:
- Custom function components don't support refs by default: The
Tagcomponent you created insideHeadingis a regular function component. When you tried to passref={headingRef}to it, TypeScript threw an error because function components don't includerefin their props unless you explicitly enable it withforwardRef. - Incorrect
React.createElementarguments: You tried to pass the ref as a fourth argument tocreateElement, but the correct structure isReact.createElement(tag, propsObject, children). Any props (including ref) need to be part of the second argument object.
Solution 1: Simplify with Direct React.createElement Call
The easiest fix is to skip creating an internal Tag component entirely. Just pass the ref directly in the props object when calling createElement:
import React, { ReactNode, useRef, useEffect } from 'react'; import s from './Heading.scss'; import gsap from 'gsap'; interface HeadingProps { tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; children: ReactNode; className?: string; } export const Heading = ({ tag, children, className }: HeadingProps) => { // Type the ref to match the heading DOM element const headingRef = useRef<HTMLHeadingElement>(null); // Example: Use GSAP for animation once the element mounts useEffect(() => { if (headingRef.current) { gsap.from(headingRef.current, { opacity: 0, y: 20, duration: 1 }); } }, []); return React.createElement( tag, { className: s(s.heading, className), ref: headingRef // Attach ref directly to the dynamic heading element }, children ); };
This works because React.createElement accepts all valid HTML attributes (including ref) for the specified tag, and TypeScript will correctly type-check the props against HTMLHeadingElement.
Solution 2: Use forwardRef If You Need a Custom Component
If you want to keep a separate component structure (for additional logic), use React.forwardRef to make your component accept and pass through refs to the underlying DOM element:
import React, { ReactNode, useRef, useEffect, forwardRef } from 'react'; import s from './Heading.scss'; import gsap from 'gsap'; interface HeadingProps { tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; children: ReactNode; className?: string; } // Use forwardRef to enable ref support const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({ tag, children, className }, ref) => { useEffect(() => { if (ref.current) { gsap.from(ref.current, { opacity: 0, y: 20, duration: 1 }); } }, [ref]); return React.createElement( tag, { className: s(s.heading, className), ref: ref // Pass the forwarded ref to the dynamic heading }, children ); }); export default Heading;
With forwardRef, the ref is passed as a second argument to your component, and you can attach it directly to the DOM element created by createElement. TypeScript will recognize the ref type (in this case, HTMLHeadingElement) to ensure type safety.
Key Takeaways
- Avoid wrapping
createElementin an unnecessary internal component unless you need extra logic. - For function components that need to expose a ref to their underlying DOM element, use
forwardRef. - Always pass props (including ref) as part of the second argument to
React.createElement.
内容的提问来源于stack exchange,提问作者Jesse Winton

