4

我正在处理一个表单,该表单除其他字段外,还包含日历条目 - 日期和时间。代码如下:

  {dates.map((d, i) => (
      <div className="date-time-entry" key={i}>
        <div className="date-picker">
          <DatePicker
            selected={new Date(d.startDate)}
            dateFormat="dd MMM yyyy"
            onChange={date => {
              const newDates = [...dates]
              newDates[i].startDate = date
              setDates(newDates)
            }}
          />
          to
          <DatePicker
            selected={new Date(d.endDate)}
            dateFormat="dd MMM yyyy"
            onChange={date => {
              const newDates = [...dates]
              newDates[i].endDate = date
              setDates(newDates)
            }}
          />
          {i ? (
            <span
              onClick={() => {
                const newDates = dates.filter((d, k) => k !== i)
                setDates(newDates)
              }}
            >
              <FontAwesomeIcon icon={faTimes} />
            </span>
          ) : null}
        </div>
        {dates[i].times.map((t, j) => {
          return (
            <div className="time-picker" key={j}>
              at
              <input
                ref={(input) => { nameInput = input }}
                value={t.start}
                onChange={e => {
                  const newDates = [...dates]
                  newDates[i].times[j].start = e.target.value
                  setDates(newDates)
                }}
              />{' '}
              to{' '}
              <input
                ref={(input) => { nameInput = input }}
                value={t.end}
                onChange={e => {
                  const newDates = [...dates]
                  newDates[i].times[j].end = e.target.value
                  setDates(newDates)
                }}
              />
              <span
                onClick={() => {
                  const newTimes = dates[i].times.filter((t, k) => k !== j)
                  const newDates = [...dates]
                  newDates[i].times = newTimes
                  setDates(newDates)
                }}
              >
                <FontAwesomeIcon icon={faTimes} />
              </span>
            </div>
          )
        })}
        <div
          className="add-time"
          onClick={() => {
            const newDates = [...dates]
            newDates[i].times = [
              ...newDates[i].times,
              { start: '12:00', end: '13:00' }
            ]
            setDates(newDates)
          }}
        >
          Add Time
        </div>
      </div>
    )
  )}

然后是useState()钩子定义为:

const [dates, setDates] = useState([
  {
    startDate: new Date(),
    endDate: new Date(),
    times: [{ start: '12:00', end: '13:00' }]
  }
])

我遇到的问题是,每当调用 onChange - 每次击键都会发生重新渲染并且我的组件失去焦点。

我尝试使用onBluranddefaultValue而不是onChange,但也存在焦点问题 - 需要两次单击才能切换焦点,因为第一次被重新渲染消耗。

我还尝试添加超时来处理onBlur“丢失”的单击,如下所示:

<input
  defaultValue={t.start}
  onBlur={e => {
    const value = e.target.value
    timeout = setTimeout(() => {
      const newDates = [...dates]
      newDates[i].times[j].start = value
      setDates(newDates)
    }, 0)
  }}
  onFocus={() => {
    clearTimeout(timeout)
  }}
/>

最后一个版本不再失去焦点,而是失去状态更新。关于如何更好地处理这个问题,或者甚至修复我现有的代码的任何建议?

4

1 回答 1

0

我认为拥有以下代码并不是一个好主意:

{dates[i].times.map((t, j) => {
      return (
        <div className="time-picker" key{j}>
          at
          <input
            ref={(input) => { nameInput = input }}
            value={t.start}
            onChange={e => {
              const newDates = [...dates]
              newDates[i].times[j].start = e.target.value
              setDates(newDates)
            }}
          />{' '}
          to{' '}
          <input
            ref={(input) => { nameInput = input }}
            value={t.end}
            onChange={e => {
              const newDates = [...dates]
              newDates[i].times[j].end = e.target.value
              setDates(newDates)
            }}
          />
          <span
            onClick={() => {
              const newTimes = dates[i].times.filter((t, k) => k !== j)
              const newDates = [...dates]
              newDates[i].times = newTimes
              setDates(newDates)
            }}
          >
            <FontAwesomeIcon icon={faTimes} />
          </span>
        </div>
      )
    })}

每次,日期的任何更改都会再次重新渲染所有组件。您应该将每个时间选择器作为一个单独的组件,如下所示:

const changeHandler = (i, j, x, val) => {
  const newDates = [...dates]
  newDates[i].times[j][x] = val
  setDates(newDates)
}
const clickHandler = (i, j) => {
  const newTimes = dates[i].times.filter((t, k) => k !== j)
  const newDates = [...dates]
  newDates[i].times = newTimes
  setDates(newDates)
}
{dates[i].times.map((t, j) => {
  return (
    <MyTimePicker time={t} k={"time_" + j} changeHandler={( val,  x="start" ) => changeHandler(i, j, x, val)} clickHandler={() => clickHandler(i, j)}>
  )
 })}

在另一个文件“my-timepicker.js”中,你应该定义你的组件:

import React, { useRef } from "react";
const areEqual = (prevProps, nextProps) => {
  if(JSON.stringify(prevProps.time) == JSON.stringify(nextProps.time)) return true;
  return false;
}
const MyTimePicker = React.memo(({changeHandler, clickHandler, time }) => {
   const atInputRef = useRef();
   const toInputRef = useRef();
   return ( 
     <div className="time-picker">
       at
       <input
          ref={atRef}
          value={time.start}
          onChange={e => {
            changeHandler(e.target.value)
          }}
       />{' '}
       to{' '}
       <input
         ref={toRef}
         value={time.end}
         onChange={e => {
           changeHandler(e.target.value, "end")
         }}
       />
       <span
         onClick={clickHandler}
       >
         <FontAwesomeIcon icon={faTimes} />
       </span>
     </div>
   )
}, areEqual);
于 2020-03-08T22:16:19.190 回答