我在学习 React 钩子时遇到了困难。
我正在关注这个网站,https://reactjs.org/docs/hooks-reference.html
const onGameOver = React.useCallback(
({ playerScore, playerHealth, gameId }) => {
setPages(player =>
arrayMove(playerScore, playerHealth, gameId)
);
console.log('gameId: ', gameId);
},
[player, gameId]
);
我可以看到 playerScore 和 playerHealth,但看不到 gameId。
我将"gameId"放在我的依赖项数组中,但它在控制台中始终是"未定义的".log。
出于测试目的,我只是给 gameId 一个虚拟 ID,如下所示:
const gameId = useState(123);
但最终,我会像这样使用它:
<GameOverScreen controlId={ControlId} stats={endGameStats} onGameOver=({onGameOver, gameId}) />
我可能做错了什么?
谢谢
依赖项数组中的gameId
在调用函数时与函数中的值不同。这是因为您的函数定义从传递给它的第一个参数中解构gameId
:
vvvvvv
const onGameOver = React.useCallback(({ playerScore, playerHealth, gameId }) => {
这将在传递给React.useCallback()
的函数之外"影子"gameId
的值。
传递给React.useCallback()
的依赖项数组不会隐式传递给正在创建的函数。该数组用于确定传递给该特定渲染React.useCallback()
的函数是否应该替换 React* 记忆的函数 - 请记住,React.useCallback()
大致等同于:
React.useMemo(() => f, deps)
执行时,您必须将gameId
传递给onGameOver
,如下所示:
onGameOver({ gameId: .... })
或者,您需要从解构任务中删除gameId
:
const onGameOver = React.useCallback(({ playerScore, playerHealth }) => {
后者可能是正确的方法,因为这样onGameOver
将始终具有正确的gameId
值,而无需调用方知道它。
* 依赖数组是必需的,因为钩子本身在每个渲染时都会被调用,但我们可能希望在不同的渲染中保持一些值稳定。
每个渲染、deps
数组中的每个元素都与上一个渲染中的deps
数组进行比较。如果其中任何一个发生了变化,那么钩子被标记为"过时",并且会发生某种效果,具体取决于钩子:
useMemo(f, deps)
将执行函数f
,该函数的返回值将作为useMemo()
的返回值提供在此当前渲染和后续渲染上,直到deps
再次更改。useCallback()
是useMemo()
的包装器,在打算记忆函数时稍微更容易使用。useCallback(f, deps)
相当于useMemo(() => f, deps)
。- 当依赖数组更改时,
useEffect(f, deps)
和useLayoutEffect(f, deps)
都将执行f
,尽管执行这些函数的时间会因您使用的钩子而异。如果需要与 DOM 交互,则应使用useLayoutEffect()
,否则应使用useEffect()
。
这就是为什么用空数组代替deps
数组将导致在组件生命周期中只执行一次效果的原因 - 因为数组值永远不会更改,因此效果永远不会重新运行。
更新以响应您的编辑:
您添加了const gameId = useState(123);
但这不太正确。useState
返回一个包含状态值的数组,以及一个可以调用来更新该状态的函数。通常,您应该这样做:
const [gameId, setGameId] = useState(123)
然后你添加了这个:
<GameOverScreen ... onGameOver=({onGameOver, gameId}) />
但这不是有效的JSX。要在组件上设置道具,您需要将其放在引号(如<MyComponent message="Hello"/>
)或大括号(如<MyComponent gameId={gameId}/>
)中。括号不起作用。另外,我不确定您要用像{onGameOver, gameId}
这样的值做什么......如果你试图将这两件事作为道具传递,它应该更像
<GameOverScreen ... onGameOver={onGameOver} gameId={gameId} />
原始答案
想象你的函数是孤立的:
function actualOnGameOver({ playerScore, playerHealth, gameId }) {
setPages(player =>
arrayMove(playerScore, playerHealth, gameId)
);
console.log('gameId: ', gameId);
}
查看函数引用的所有内容,并确定它们的来源:
setPages
- 从函数外部(我从useState
调用中猜测)arrayMove
- 从函数外部(我从import
猜测)playerScore
- 作为参数接收playerHealth
- 作为参数接收gameId
- 作为参数接收
请注意,useCallback
是useMemo
的方便版本,即它正在制作/获取函数的缓存版本。依赖数组用于告诉 React 何时应该使该缓存失效。函数引用的来自函数外部的任何非常量值都应在该数组中提及。作为函数参数接收的值(例如gameId
)不应该进去。
所以你的依赖项数组应该是[setPages]
的(或者[setPages, arrayMove]
如果我关于 arrayMove 是导入的猜测是错误的),因为这是函数引用的唯一非常量值,没有作为参数传入。
当你将actualOnGameOver
传递到useCallback
时,结果是一个具有相同签名的函数,所以你会以相同的方式调用onGameOver
,例如
onGameOver({
playerScore: 100,
playerHealth: 75,
gameId: 'abc123'
})
修复依赖项数组后,如果gameId
仍未定义,则应查看onGameOver
函数之外的代码。确保在调用onGameOver
时传递gameId
的值。
将gameId放入useCallback deps数组中很奇怪。我认为您不必在其上放置函数参数,而只需在函数定义中使用的变量(props 或 useState 值)即可。
要回答您的问题,这取决于调用它时传递onGameOver函数的参数。