React中使用useState更新状态时如何避免修改props值?
解决编辑门店营业时间时props被意外修改的问题
你的问题根源在于浅拷贝导致的引用共享:初始化allOpeningHours时用[...studio.openingHours.regularDays]只是复制了数组外壳,但数组里的每个营业时间对象还是和props中的原始对象指向同一个内存地址。所以修改状态里的对象属性时,props里的原始数据也被同步修改了,自然无法在取消时恢复。
具体修复方案
初始化状态时深拷贝对象
把数组中的每个营业时间对象单独复制,彻底切断和props的引用关联:const [allOpeningHours, setAllOpeningHours] = useState( studio.openingHours.regularDays.map(day => ({ ...day })) // 深拷贝每个对象 )取消编辑时重置状态
给取消按钮添加重置逻辑,从props重新深拷贝数据恢复原始值:<Button onClick={() => { toggleEdit() // 重置状态为props原始值 setAllOpeningHours(studio.openingHours.regularDays.map(day => ({ ...day }))) }} variant={'outlined'} sx={{ mt: 1 }} fullWidth > Cancel </Button>优化子组件循环数据源
原来的循环用了studio.openingHours.regularDays,改成用状态allOpeningHours,逻辑更统一,也避免混淆:<Stack mt={3} spacing={2}> {allOpeningHours.map((hours, i) => ( // 用状态数据循环 <DayOfWeek key={i} // 添加key,避免React警告 dow={daysOfWeek[i]} openingHours={hours} edit={edit} i={i} setAllOpeningHours={setAllOpeningHours} allOpeningHours={allOpeningHours} /> ))} </Stack>
修改后的完整代码
const EditStudioHours: FC<{ studio: Studio }> = ({ studio }) => { const { value: edit, toggle: toggleEdit } = useBoolean(false) const { value: submitting, toggle: toggleSubmitting } = useBoolean(false) // 初始化时深拷贝每个营业时间对象 const [allOpeningHours, setAllOpeningHours] = useState( studio.openingHours.regularDays.map(day => ({ ...day })) ) return ( <Box> <Typography variant='h6' mt={2} gutterBottom> Set standard hours </Typography> <Typography fontWeight='light' fontSize={14}> Configure the standard operating hours of this studio </Typography> <Stack mt={3} spacing={2}> {/* 用状态allOpeningHours循环渲染子组件 */} {allOpeningHours.map((hours, i) => ( <DayOfWeek key={i} dow={daysOfWeek[i]} openingHours={hours} edit={edit} i={i} setAllOpeningHours={setAllOpeningHours} allOpeningHours={allOpeningHours} /> ))} </Stack> <Button variant={edit ? 'contained' : 'outlined'} onClick={() => { toggleEdit() }} fullWidth sx={{ mt: 2 }} disabled={!edit ? false : submitting} > {submitting ? ( <CircularProgress size={22} /> ) : edit ? ( 'Submit changes' ) : ( 'Edit' )} </Button> {edit && ( <Button onClick={() => { toggleEdit() // 取消时重置状态为props原始值 setAllOpeningHours(studio.openingHours.regularDays.map(day => ({ ...day }))) }} variant={'outlined'} sx={{ mt: 1 }} fullWidth > Cancel </Button> )} </Box> ) } export default EditStudioHours const DayOfWeek: FC<{ openingHours: { start: number; end: number } dow: string edit: boolean i: number setAllOpeningHours: React.Dispatch<React.SetStateAction<{ start: number; end: number }[]>> allOpeningHours: { start: number; end: number }[] }> = ({ openingHours, dow, edit, i, setAllOpeningHours, allOpeningHours }) => { const [open, setOpen] = useState(openingHours.end !== openingHours.start) const handleOpenClose = () => { if (open) { setAllOpeningHours((ps) => { const newHours = [...ps] // 这里修改的是状态中的拷贝对象,不会影响props newHours[i] = { ...newHours[i], start: 0, end: 0 } return newHours }) } setOpen(ps => !ps) } const handleStart = (e: React.ChangeEvent<HTMLInputElement>) => { setAllOpeningHours((prevState) => { const newHours = [...prevState] // 同样修改拷贝后的对象 newHours[i] = { ...newHours[i], start: Number(e.target.value) } return newHours }) } const handleEnd = (e: React.ChangeEvent<HTMLInputElement>) => { setAllOpeningHours((ps) => { const newHours = [...ps] newHours[i] = { ...newHours[i], end: Number(e.target.value) } return newHours }) } return ( <Box display='flex' alignItems='center' justifyContent={'space-between'}> <Box display={'flex'} alignItems='center'> <Typography width={150}>{dow}</Typography> <FormGroup> <FormControlLabel control={ <Switch disabled={!edit} checked={open} onChange={handleOpenClose} /> } label='Open' /> </FormGroup> </Box> {open && ( <Box display={'flex'} alignItems='center'> <TextField disabled={!edit} id={`${i}open`} select label='Open' value={allOpeningHours[i].start} type='number' sx={{ minWidth: 120 }} size='small' onChange={handleStart} > {openingOptions.map((option: { value: number; label: string }) => ( <MenuItem dense key={option.value} value={option.value}> {option.label} </MenuItem> ))} </TextField> <Typography mx={2}>TO</Typography> <TextField disabled={!edit} id={`${i}close`} select label='Close' value={allOpeningHours[i].end} type='number' sx={{ minWidth: 120 }} size='small' onChange={handleEnd} > {openingOptions.map((option: { value: number; label: string }) => ( <MenuItem dense key={option.value} value={option.value}> {option.label} </MenuItem> ))} </TextField> </Box> )} </Box> ) }
内容的提问来源于stack exchange,提问作者Olly
相关产品推荐
相关产品推荐

