使用Framer Motion useAnimate时函数组件无法接收refs的问题解决
错误解释与解决方案
错误含义
"Function component cannot be given refs" 本质是:React函数组件默认不支持接收ref属性——因为函数组件没有类组件那样的实例对象,无法直接通过ref指向组件实例。只有当组件通过forwardRef包装,并将外部传入的ref转发到内部DOM元素/类组件时,才能合法接收ref。
你的代码里虽然给Humburger套了forwardRef,但存在两个关键问题:
- 你没有把外部传入的
forwardedRef绑定到任何内部DOM元素上(你用的scope是Framer Motion的动画作用域ref,和转发的ref是完全独立的两个引用); - 即使你没显式给
Humburger传ref,某些场景下(比如框架隐式处理、第三方库集成)可能会自动传递ref,此时未正确处理转发的forwardRef就会触发报错。
解决方案
根据你的需求,分两种情况处理:
情况1:不需要给Humburger传递外部ref
直接移除forwardRef,因为你当前代码完全不需要接收外部ref,多余的forwardRef反而会引发React的检测问题:
"use client"; import { useAnimate } from "framer-motion"; import React, { useState } from "react"; const Humburger = ({ onClick }: { onClick: () => void }) => { const [scope, animate] = useAnimate(); const [isHumburgOpen, setIsHumburgOpen] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const animateHumburger = async () => { if (isAnimating) return; setIsAnimating(true); onClick(); if (!isHumburgOpen) { animate("#top", { y: "0.45rem" }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); await animate("#bottom", { y: "-0.45rem", opacity: 0 }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); animate("#top", { rotate: "136deg" }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); animate("#middle", { rotate: "43deg" }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); setIsHumburgOpen(true); } else { animate("#top", { rotate: "0deg" }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); await animate("#middle", { rotate: "0deg" }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); animate("#top", { y: "0rem" }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); await animate("#bottom", { y: 0, opacity: 1 }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); setIsHumburgOpen(false); } setIsAnimating(false); }; return ( <div ref={scope}> <a onClick={animateHumburger} className="hover:focus-within:outline-none hover:focus:outline-none flex flex-col items-end justify-center h-24 gap-1 mt-50 cursor-pointer" > <span id="top" className="origin-center h-[3px] w-6 bg-black block rounded-full"></span> <span id="middle" className="h-[3px] w-6 bg-black rounded-full"></span> <span id="bottom" className="h-[3px] w-4 bg-black rounded-full"></span> </a> </div> ); }; export default Humburger;
情况2:需要给Humburger传递外部ref
如果后续需要通过外部ref获取Humburger内部的DOM元素,需要将forwardedRef和Framer Motion的scope ref合并,绑定到目标元素上:
"use client"; import { useAnimate } from "framer-motion"; import React, { forwardRef, useState } from "react"; // 自定义合并ref的工具函数 const mergeRefs = <T>(refs: Array<React.Ref<T> | undefined>) => { return (node: T) => { refs.forEach(ref => { if (typeof ref === "function") { ref(node); } else if (ref != null) { (ref as React.MutableRefObject<T>).current = node; } }); }; }; const Humburger = forwardRef<HTMLDivElement, { onClick: () => void }>(({ onClick }, forwardedRef) => { const [scope, animate] = useAnimate(); const [isHumburgOpen, setIsHumburgOpen] = useState(false); const [isAnimating, setIsAnimating] = useState(false); // 合并动画作用域ref和外部转发的ref const mergedRef = mergeRefs([scope, forwardedRef]); const animateHumburger = async () => { if (isAnimating) return; setIsAnimating(true); onClick(); if (!isHumburgOpen) { animate("#top", { y: "0.45rem" }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); await animate("#bottom", { y: "-0.45rem", opacity: 0 }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); animate("#top", { rotate: "136deg" }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); animate("#middle", { rotate: "43deg" }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); setIsHumburgOpen(true); } else { animate("#top", { rotate: "0deg" }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); await animate("#middle", { rotate: "0deg" }, { ease: [0.36, 0, 0.66, -0.56], duration: 0.5 }); animate("#top", { y: "0rem" }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); await animate("#bottom", { y: 0, opacity: 1 }, { ease: [0.22, 1, 0.36, 1], duration: 0.5 }); setIsHumburgOpen(false); } setIsAnimating(false); }; return ( <div ref={mergedRef}> <a onClick={animateHumburger} className="hover:focus-within:outline-none hover:focus:outline-none flex flex-col items-end justify-center h-24 gap-1 mt-50 cursor-pointer" > <span id="top" className="origin-center h-[3px] w-6 bg-black block rounded-full"></span> <span id="middle" className="h-[3px] w-6 bg-black rounded-full"></span> <span id="bottom" className="h-[3px] w-4 bg-black rounded-full"></span> </a> </div> ); }); export default Humburger;
关键说明
你内部使用的scope ref是Framer Motion用于限定动画作用域的引用,和外部传递的组件ref完全无关——组件内部使用ref不代表组件就能自动接收外部的ref,只有通过forwardRef并正确转发,才能支持外部传ref的场景。
内容的提问来源于stack exchange,提问作者alex
相关产品推荐
相关产品推荐

