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

React中如何在子组件中使用父组件的Ref?

React Canvas游戏组件拆分与Ref传递解决方案

核心问题梳理

当前代码有两个关键错误导致子组件无法正常工作:

  1. <canvas>是HTML替换元素,内部的React子组件不会被浏览器渲染(仅作为不支持canvas时的降级内容),所以<Background/>和<Player/>根本没执行逻辑
  2. 即便用了forwardRef,也没正确传递父组件的canvas ref,且子组件实际需要的是绘图上下文context而非原始ref

修正后的代码实现

1. 父组件Canvas.jsx(重构结构,传递上下文与资源)

import { useRef, useEffect, useState } from 'react'
import Background from './Background'
import Player from './Player'

function Canvas() {
    const myCanvasRef = useRef(null)
    const [canvasCtx, setCanvasCtx] = useState(null)
    const [allImagesLoaded, setAllImagesLoaded] = useState(false)

    // 预加载游戏资源
    const backgroundImg = new Image()
    const playerImg = new Image()

    useEffect(() => {
        let loadedCount = 0
        const checkLoadComplete = () => {
            loadedCount++
            if (loadedCount === 2) setAllImagesLoaded(true)
        }

        // 绑定加载事件
        backgroundImg.onload = checkLoadComplete
        playerImg.onload = checkLoadComplete
        // 设置资源路径
        backgroundImg.src = 'src/assets/PelletTown.png'
        playerImg.src = 'src/assets/playerRight.png'

        // 初始化Canvas
        const canvas = myCanvasRef.current
        const ctx = canvas.getContext('2d')
        canvas.width = 1024
        canvas.height = 576
        setCanvasCtx(ctx)

        // 清空画布
        ctx.fillStyle = 'black'
        ctx.fillRect(0, 0, canvas.width, canvas.height)

        // 清理函数
        return () => {
            backgroundImg.onload = null
            playerImg.onload = null
        }
    }, [])

    return (
        <>
            <canvas ref={myCanvasRef} />
            {/* 确保上下文和资源就绪后再渲染子组件 */}
            {canvasCtx && allImagesLoaded && (
                <>
                    <Background ctx={canvasCtx} img={backgroundImg} />
                    <Player ctx={canvasCtx} img={playerImg} />
                </>
            )}
        </>
    )
}

export default Canvas

2. 子组件Player.jsx(接收上下文,执行绘制逻辑)

import { useEffect } from "react";

const Player = ({ ctx, img }) => {
    useEffect(() => {
        // 绘制玩家
        ctx.drawImage(img, 0, 0)

        // 如果需要动态更新(比如移动动画),可以在这里添加帧循环
        const animId = requestAnimationFrame(() => {
            // 后续更新逻辑写在这里
        })

        return () => cancelAnimationFrame(animId)
    }, [ctx, img])

    // 子组件不需要渲染DOM,所有绘制都在父组件的canvas上完成
    return null
}

export default Player

3. 子组件Background.jsx(同理实现)

import { useEffect } from "react";

const Background = ({ ctx, img }) => {
    useEffect(() => {
        ctx.drawImage(img, -700, -500)
    }, [ctx, img])

    return null
}

export default Background

关键说明

  • Canvas内部不能放React组件:<canvas>的内部内容仅作为降级显示,浏览器不会解析其中的React组件,必须把组件放在canvas外部
  • 直接传递上下文更高效:子组件需要的是绘图上下文ctx,直接通过props传递比传递ref再手动获取ctx更符合React数据流,也避免了ref传递的复杂操作
  • 统一管理资源加载:确保所有图片加载完成后再绘制,避免出现空白或绘制失败的情况

内容的提问来源于stack exchange,提问作者Frigyes Vass

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 06:55:55