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

React修改key重挂载Game组件后子组件Clock的ref为null报错问题

问题根因

报错的核心原因是旧Game组件的socket事件监听器泄漏:
每次你通过修改key属性重挂载Game组件时,旧的Game实例会被卸载,但你在componentDidMount中给socket绑定的事件回调没有被同步清除。这些残留的回调仍然持有已经卸载的旧Game实例的引用,而旧实例卸载后内部的myClock和opponentClockref已经被置为null,当socket事件触发时,旧回调执行就会访问null的start/pause方法抛出错误。
新Game实例的ref本身是完全正常的,你在新实例的componentDidMount中可以正常调用ref方法就是证明。

解决方案

给Game类组件补充卸载逻辑,移除所有绑定的socket事件即可,注意移除事件时传入的回调必须和绑定时的是同一个引用,不能直接移除匿名函数,需要提前把回调定义为组件实例方法:

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            gameBoard: props.initialBoard, 
            isPlayerANext: true,
            gameLoser: null, //null,'A','B'
        };
        this.myClock = React.createRef(); 
        this.opponentClock = React.createRef();
        // 提前定义socket事件回调并绑定this,保证绑定和移除用同一个引用
        this.handleNewMove = this.handleNewMove.bind(this);
        // 其他socket事件(比如surrender)的回调也按同样方式定义
    }

    // 抽离newMove事件的处理逻辑
    handleNewMove(data) {
        if(data.isPlayerANext===this.props.isPlayerA){
            this.opponentClock.current?.pause()
            this.myClock.current?.start()
        }
        else{
            this.opponentClock.current?.start()
            this.myClock.current?.pause()
        }
    }

    componentDidMount(){
        if(this.props.isPlayerA){
            this.myClock.current.start()
        }
        else{
            this.opponentClock.current.start()
        }
        // 绑定事件用提前定义的实例方法
        socket.on('newMove', this.handleNewMove)
        // 其他socket事件同理绑定
        // socket.on('surrender', this.handleSurrender)
        ...
    }

    // 新增卸载生命周期,清除所有绑定的socket事件
    componentWillUnmount() {
        socket.off('newMove', this.handleNewMove)
        // 其他注册的socket事件也同步移除
        // socket.off('surrender', this.handleSurrender)
    }
    
    render(){
        return( 
        <React.Fragment>
            <Clock ref={this.opponentClock} .../>
            <Board gameBoard={this.state.gameBoard} .../>
            <Clock ref={this.myClock} .../>
        </React.Fragment>)
        ...
    }
}
额外优化建议
  • 给ref访问加上可选链操作符?.做防御性兼容,极端场景下也不会直接抛出错误导致应用崩溃
  • 全局单例的socket绑定的所有事件都要遵循「谁绑定谁卸载」的原则,避免后续再出现同类的事件泄漏问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 12:36:03