反应:通过更改键重新安装后,对子组件的引用为空
Posted
技术标签:
【中文标题】反应:通过更改键重新安装后,对子组件的引用为空【英文标题】:React: ref to child component is null after remounting by changing key 【发布时间】:2021-11-21 16:26:31 【问题描述】:对于 1v1 数独游戏,我的 GamePage
组件会渲染主要的 Game
组件,其中包含每个玩家的 Clock
。当双方玩家同意重赛时,整个 Game
将通过简单地将其 key
增加 1 来重置(在更改 GamePage 状态以反映新游戏的设置之后)。
我的问题:Game
将两个参考值 this.myClock
和 this.opponentClock
存储到两个时钟内的倒计时,因此当玩家填满一个正方形时,它们可以暂停/开始.这对于第一场比赛非常有效。然而,Game
重新挂载后,任何移动都会抛出“Cannot read properties of null (reading 'start')”,例如this.opponentClock.current.start()
.
我知道当组件卸载时 refs 被设置为 null,但是通过呈现 Game
的新版本,我希望它们再次在构造函数中设置。令我惊讶的是,新计时器设置正确并且其中一个正在运行(这也在 Game
的 componentDidMount
中使用 refs 完成),但之后的任何访问都会破坏应用程序。
对于任何有关可能原因的提示或评论,我将非常感激,我已经被困在这个问题上两天了,我的东西已经用完了谷歌。
GamePage.js:
export default function GamePage(props)
const [gameCounter, setGameCounter] = useState(0) //This is increased to render a new game
const [gameDuration, setGameDuration] = useState(0)
...
useEffect(() =>
...
socket.on('startRematch', data=>
...
setGameDuration(data.timeInSeconds*1000)
setGameBoard([data.generatedBoard, data.generatedSolution])
setGameCounter(prevCount => prevCount+1)
)
,[])
return (
<Game key=gameCounter initialBoard=gameBoard[0] solvedBoard=gameBoard[1] isPlayerA=isPlayerA
id=gameid timeInMs=gameDuration onGameOver=handleGamePageOver/>
)
Game.js:
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();
componentDidMount()
if(this.props.isPlayerA)
this.myClock.current.start()
else
this.opponentClock.current.start()
socket.on('newMove', data =>
if(data.isPlayerANext===this.props.isPlayerA)
this.opponentClock.current.pause()
this.myClock.current.start()
else
this.opponentClock.current.start()
this.myClock.current.pause()
)
...
render()
return(
<React.Fragment>
<Clock ref=this.opponentClock .../>
<Board gameBoard=this.state.gameBoard .../>
<Clock ref=this.myClock .../>
</React.Fragment>)
...
export default Game
Clock.js:
import Countdown, zeroPad from 'react-countdown';
const Clock = (props,ref) =>
const [paused, setPaused] = useState(true);
return <Countdown ref=ref ... />
export default forwardRef(Clock);
编辑:
接受的答案就像一个魅力。问题不在于新的 ref 本身,而在于使用旧 ref 的 socket.on('newMove',...)
和 socket.on('surrender',...)
在卸载旧游戏时没有正确清理。
【问题讨论】:
你有这个的github吗?我想看看。 这里是回购:Link。要重现我的问题,请使用 npm start 运行它,使用 npm run dev 运行后端。然后只需开始游戏,将链接复制到另一个选项卡,投降并单击两个选项卡中的重新匹配。不幸的是,一切仍然很混乱。 谢谢我去看看 【参考方案1】:我很高兴地通知您,经过大约 2 小时的调试(大声笑),我找到了问题的根源。
问题是你没有在组件卸载时清理你的 socket.on 函数,所以旧的仍然存在,并引用了旧的 refs。
看看我这里的做法,把功能清理干净,你的问题就解决了:
class Game extends React.Component
constructor(props)
super(props);
this.state =
gameBoard: props.initialBoard,
isPlayerANext: true,
gameLoser: null, //null,'A','B'
;
this.solvedBoard = props.solvedBoard;
this.wrongIndex = -1;
this.handleSquareChange = this.handleSquareChange.bind(this);
this.myClock = React.createRef();
this.opponentClock = React.createRef();
this.endTime = Date.now() + props.timeInMs; //sets both clocks to the chosen time
this.handleTimeOut = this.handleTimeOut.bind(this);
this.onNewMove = this.onNewMove.bind(this);
this.onSurrender = this.onSurrender.bind(this);
isDraw()
return !this.state.gameLoser && this.state.gameBoard === this.solvedBoard;
onNewMove(data)
console.log('NewMoveMyClock: ', this.myClock.current);
if (data.isPlayerANext === this.props.isPlayerA)
console.log(
'oppmove: ',
this.myClock.current,
this.opponentClock.current
);
this.opponentClock.current.pause();
this.myClock.current.start();
else
console.log('mymove: ', this.myClock.current, this.opponentClock.current);
this.opponentClock.current.start();
this.myClock.current.pause();
let idx = data.col + 9 * data.row;
let boardAfterOppMove =
this.state.gameBoard.substring(0, idx) +
data.val +
this.state.gameBoard.substring(idx + 1);
this.wrongIndex = data.gameLoser ? idx : this.wrongIndex;
this.setState(
gameBoard: boardAfterOppMove,
gameLoser: data.gameLoser,
isPlayerANext: data.isPlayerANext,
);
if (data.gameLoser)
this.handleGameOver(data.gameLoser);
else if (this.isDraw())
this.handleGameOver(null);
onSurrender(data)
this.handleSurrender(data.loserIsPlayerA);
componentDidMount()
console.log('component game did mount');
console.log(
this.myClock.current.initialTimestamp,
this.myClock ? this.myClock.current.state.timeDelta.total : null,
this.opponentClock
? this.opponentClock.current.state.timeDelta.total
: null,
this.props.gameCounter
);
if (this.props.isPlayerA)
this.myClock.current.start();
else
this.opponentClock.current.start();
socket.on('newMove', this.onNewMove);
socket.on('surrender', this.onSurrender);
componentWillUnmount()
socket.off('newMove', this.onNewMove);
socket.off('surrender', this.onSurrender);
【讨论】:
非常感谢您的努力!我早上第一件事就试试看! 按预期工作,我想这种行为应该让我对套接字更加怀疑。再次感谢,在您的频道上也放了一个订阅者。以上是关于反应:通过更改键重新安装后,对子组件的引用为空的主要内容,如果未能解决你的问题,请参考以下文章