ReactJS中如何通过Start按钮触发Dice组件的指定函数
React新手问题:点击按钮触发子组件函数执行
我是React新手,正在从零搭建应用,目前遇到技术瓶颈:需点击Footer组件的Start按钮,触发Dice组件内的mortalWoundDecider和objectiveDecider函数执行。当前若直接执行这两个函数(不加function声明),它们会在回合数或目标数更新时自动运行,希望通过Start按钮控制其执行时机。
相关代码
Footer.js(含按钮组件)
import "./../App.css"; import TextField from '@mui/material/TextField'; import { IconButton, ButtonGroup, Button } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import RemoveIcon from '@mui/icons-material/Remove'; import Box from '@mui/material/Box'; import Body from './Body.js'; import { useState } from 'react'; // 补充遗漏的导入 function Footer () { //Constants and States let [round, setRound] = useState(0); let [objectiveValue, setObjectiveValue] = useState(0); let [passedValue, setPassedValue] = useState({objective: 0, roundValue: 0}); const changeState = () => { setPassedValue({objective: objectiveValue, roundValue: round}); }; //Functions for Incrementing and Decrementing Rounds function incrementRound() { round = round + 1; setRound(round); } function decrementRound() { if(round === 0){ round = 0; setRound(round); } else{ round = round - 1; setRound(round); } } //Clear Function to run on reset function clear(){ setObjectiveValue(0); setRound(0); setPassedValue({objective: 0, roundValue: 0}) } //Buttons that are stacked for round addition and subtraction as well as start and reset const buttons = [ <IconButton aria-label="add" size="small" onClick={() => incrementRound()}> <AddIcon fontSize="inherit" /> </IconButton>, <IconButton aria-label="remove" size="small" onClick={() => decrementRound()}> <RemoveIcon fontSize="inherit" /> </IconButton>, ]; const buttons2 = [ <Button variant="text" color="success" onClick={() => {changeState()}}>Start</Button>, <Button variant="text" color="error" onClick={() => {clear()}}>Reset</Button> ]; return ( <div> <div class="App-body"> <Body objective={passedValue.objective} roundValue={passedValue.roundValue}/> </div> <div class="App-footer"> <Box sx={{ width: 1 }}> <Box display="grid" gridTemplateColumns="repeat(12, 1fr)" gap={2}> <Box gridColumn="span 9"> </Box> <Box gridColumn="span 1"> <TextField id="outlined-number" label="Number of Objectives" type="number" InputLabelProps={{ shrink: true, }} variant="standard" sx={{float: "left", marginTop: "12px"}} onChange={(newValue) => setObjectiveValue(newValue.target.value)} value = {objectiveValue} /> </Box> <Box gridColumn="span 1"> <ButtonGroup orientation="vertical" aria-label="vertical contained button group" variant="text" sx={{float: "left"}} > {buttons2} </ButtonGroup> </Box> <Box gridColumn="span 1"> </Box> <Box gridColumn="span 9"> </Box> <Box gridColumn="span 1"> <h4 className='vertical-center' style={{textAlign: "left", bottom: "58px"}}>Round: {round}</h4> </Box> <Box gridColumn="span 1"> <ButtonGroup orientation="vertical" aria-label="vertical contained button group" variant="text" sx={{float: "left"}} > {buttons} </ButtonGroup> </Box> <Box gridColumn="span 1"> </Box> </Box> </Box> </div> </div> ); } export default Footer;
Dice.js(处理骰子逻辑组件)
import React from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import Divider from '@mui/material/Divider'; function Dice(props) { //Variables needed for Dice Function const min = 1; const max = 6; const objectiveNumber = props.objective; const roundNumber = props.roundNum; let passedObjectives = []; let mortalWoundRoll = []; function diceRoll(){ return Math.floor(min + Math.random() * (max - min)); }; function objectiveDecider(){ for(var i = 0; i < objectiveNumber; i++){ let currentDice= diceRoll(); if(currentDice>=5){ passedObjectives.push(i); } else{ }; }; } function mortalWoundDecider(){ for(var z = 0; z < passedObjectives.length; z++){ let woundArray = []; for(var r = 0; r < roundNumber; r++){ let currentMWDice = diceRoll(); woundArray.push(currentMWDice); }; mortalWoundRoll.push(woundArray); }; } return ( <div> <Row> <Col><h3>Objective Number</h3></Col> <Col xs={6}><h3>Dice</h3></Col> <Col><h3>Mortal Wound Number</h3></Col> </Row> <Divider /> </div> ); } export default Dice;
解决方案
核心思路是通过触发状态控制Dice组件中函数的执行时机,避免随props更新自动运行,具体修改如下:
1. 给Footer组件添加触发状态并传递
修改Footer.js,新增控制骰子滚动的状态,点击Start时触发:
import { useState } from 'react'; // 确保导入useState function Footer () { // ... 原有状态 const [shouldRoll, setShouldRoll] = useState(false); // 新增触发状态 const changeState = () => { setPassedValue({objective: objectiveValue, roundValue: round}); setShouldRoll(true); // 点击Start时激活触发状态 }; // 修正回合增减函数,用函数式更新避免状态不同步 function incrementRound() { setRound(prev => prev + 1); } function decrementRound() { setRound(prev => prev > 0 ? prev - 1 : 0); } // 修正clear函数,同时重置触发状态 function clear(){ setObjectiveValue(0); setRound(0); setPassedValue({objective: 0, roundValue: 0}); setShouldRoll(false); } // 处理输入转数字,避免字符串问题 const handleObjectiveChange = (e) => { const num = parseInt(e.target.value) || 0; setObjectiveValue(num); }; // 修改Body组件的props,传递触发状态和重置回调 return ( <div> <div className="App-body"> <Body objective={passedValue.objective} roundValue={passedValue.roundValue} shouldRoll={shouldRoll} onRollComplete={() => setShouldRoll(false)} /> </div> {/* 修改TextField的onChange为handleObjectiveChange */} <TextField // ... 其他属性不变 onChange={handleObjectiveChange} value={objectiveValue} /> {/* 其他代码不变 */} </div> ); }
2. 修改Dice组件,用useEffect监听触发状态
将Dice里的普通变量改为状态,确保数据持久化,同时监听触发状态执行函数:
import React, { useState, useEffect } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import Divider from '@mui/material/Divider'; function Dice(props) { const { objective, roundNum, shouldRoll, onRollComplete } = props; const min = 1; const max = 6; // 用state存储结果,避免重渲染丢失 const [passedObjectives, setPassedObjectives] = useState([]); const [mortalWoundRoll, setMortalWoundRoll] = useState([]); function diceRoll(){ return Math.floor(min + Math.random() * (max - min)); }; function objectiveDecider(){ const newPassed = []; for(let i = 0; i < objective; i++){ const currentDice = diceRoll(); if(currentDice >= 5){ newPassed.push(i); } }; setPassedObjectives(newPassed); return newPassed; } function mortalWoundDecider(passedObjs){ const newWounds = []; for(let z = 0; z < passedObjs.length; z++){ const woundArray = []; for(let r = 0; r < roundNum; r++){ woundArray.push(diceRoll()); }; newWounds.push(woundArray); }; setMortalWoundRoll(newWounds); } // 监听shouldRoll,触发时执行骰子逻辑 useEffect(() => { if(shouldRoll && objective > 0 && roundNum > 0){ const passed = objectiveDecider(); mortalWoundDecider(passed); onRollComplete(); // 执行完成后通知父组件重置触发状态 } }, [shouldRoll, objective, roundNum, onRollComplete]); // 渲染结果示例 return ( <div> <Row> <Col><h3>Objective Number</h3></Col> <Col xs={6}><h3>Dice</h3></Col> <Col><h3>Mortal Wound Number</h3></Col> </Row> <Divider /> {passedObjectives.map((_, idx) => ( <Row key={idx}> <Col>{idx + 1}</Col> <Col xs={6}>Passed</Col> <Col>{mortalWoundRoll[idx]?.join(', ') || '-'}</Col> </Row> ))} </div> ); } export default Dice;
3. 确保Body组件传递props到Dice
如果Body只是中转组件,需要把shouldRoll和onRollComplete传递给Dice:
// Body.js import Dice from './Dice'; function Body(props) { return ( <div> <Dice objective={props.objective} roundNum={props.roundValue} shouldRoll={props.shouldRoll} onRollComplete={props.onRollComplete} /> </div> ); } export default Body;
内容的提问来源于stack exchange,提问作者Fugismar
相关产品推荐
相关产品推荐

