You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Framer Motion useAnimate时函数组件无法接收refs的问题解决

错误解释与解决方案

错误含义

"Function component cannot be given refs" 本质是:React函数组件默认不支持接收ref属性——因为函数组件没有类组件那样的实例对象,无法直接通过ref指向组件实例。只有当组件通过forwardRef包装,并将外部传入的ref转发到内部DOM元素/类组件时,才能合法接收ref。

你的代码里虽然给Humburger套了forwardRef,但存在两个关键问题:

  1. 你没有把外部传入的forwardedRef绑定到任何内部DOM元素上(你用的scope是Framer Motion的动画作用域ref,和转发的ref是完全独立的两个引用);
  2. 即使你没显式给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 20:59:54