React父组件向子组件传值时子端接收数据与父端不一致问题
问题根因
你遇到的现象本质是父组件每次重新渲染时都会重新执行randomSelect()生成新的随机ID,导致传给子组件的飞船数据被替换。
你的playerOneId、computerId是直接定义在父组件函数体中的普通变量,只要父组件触发重渲染(比如你调用setResult、setPlayerScore、setPlayerSelected更新状态时),这两个变量就会被重新赋值,对应的playerOneShip、computerShip自然也会变化。
你觉得父组件的console.log(1)打印正常,大概率是你只注意到了第一次渲染的输出,实际上每次父组件重渲染时这个打印的内容也会同步更新,和子组件拿到的props是一致的。
修复方案
你只需要把生成的随机ID存到状态里,避免每次渲染都重新生成即可,这里给两种可行方案:
方案1:在获取到飞船数据后生成随机ID存入状态
修改父组件的状态定义和useEffect逻辑:
// 新增两个状态存储玩家和电脑的选中ID const [playerOneId, setPlayerOneId] = useState(null); const [computerId, setComputerId] = useState(null); useEffect(() => { fetch('https://connectr-swapi.herokuapp.com/', { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({query: STARSHIP_QUERY}) }) .then(response => response.json()) .then(data => { const starships = data.data.allStarships.starships; setStarships(starships); // 拿到数据后再生成随机ID const starShipIds = starships.map(ship => ship.id); const randomSelect = () => { const random = Math.floor(Math.random() * starShipIds.length); return starShipIds[random]; } setPlayerOneId(randomSelect()); setComputerId(randomSelect()); }) .catch(error => console.log({'Error': error})) },[])
然后删除原来写在函数体里的randomSelect、starShipIds、valueOneID、valueTwoID相关的普通变量定义即可,后续父组件因为其他状态更新重渲染时,这两个ID不会变化,传给子组件的飞船数据也就不会变了。
方案2:用useMemo缓存随机ID
如果你不想改状态,也可以用useMemo缓存生成结果,只有当starships变化时才重新生成:
import { useMemo } from 'react'; // 把原来的普通变量定义替换成useMemo const {playerOneId, computerId, playerOneShip, computerShip} = useMemo(() => { if(starships.length === 0) return { playerOneId: null, computerId: null, playerOneShip: [], computerShip: [] } const starShipIds = starships.map(ship => ship.id); const randomSelect = () => { const random = Math.floor(Math.random() * starShipIds.length); const selectedId = starShipIds[random]; return selectedId; } const playerOneId = randomSelect(); const computerId = randomSelect(); return { playerOneId, computerId, playerOneShip: starships.filter(ship => ship.id === playerOneId), computerShip: starships.filter(ship => ship.id === computerId) } }, [starships]); // 只有starships变化时才会重新计算
这种方案同样可以避免其他状态更新导致随机ID被重新生成的问题。
额外优化建议
你当前的代码在starships为空时调用randomSelect会出现starShipIds.length为0的情况,生成的随机数是NaN,返回的ID是undefined,上面的两种方案都已经规避了这个问题。
内容的提问来源于stack exchange,提问作者Coder

