You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中使用useState更新状态时如何避免修改props值?

解决编辑门店营业时间时props被意外修改的问题

你的问题根源在于浅拷贝导致的引用共享:初始化allOpeningHours时用[...studio.openingHours.regularDays]只是复制了数组外壳,但数组里的每个营业时间对象还是和props中的原始对象指向同一个内存地址。所以修改状态里的对象属性时,props里的原始数据也被同步修改了,自然无法在取消时恢复。

具体修复方案

  1. 初始化状态时深拷贝对象
    把数组中的每个营业时间对象单独复制,彻底切断和props的引用关联:

    const [allOpeningHours, setAllOpeningHours] = useState(
      studio.openingHours.regularDays.map(day => ({ ...day })) // 深拷贝每个对象
    )
    
  2. 取消编辑时重置状态
    给取消按钮添加重置逻辑,从props重新深拷贝数据恢复原始值:

    <Button
      onClick={() => {
        toggleEdit()
        // 重置状态为props原始值
        setAllOpeningHours(studio.openingHours.regularDays.map(day => ({ ...day })))
      }}
      variant={'outlined'}
      sx={{ mt: 1 }}
      fullWidth
    >
      Cancel
    </Button>
    
  3. 优化子组件循环数据源
    原来的循环用了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 00:15:38