【问题标题】:React input onChange lag反应输入 onChange 滞后
【发布时间】:2018-06-12 14:09:32
【问题描述】:

我有一个带有onChange 事件处理程序的简单控制输入。

一切正常,handleChange 在每次击键时触发,问题是它非常慢

使用输入时有一个非常明显的延迟。是否有一些额外的代码我必须正确才能让它像正常输入一样工作?

我必须去抖动输入吗?

据我所知,文档中没有提到这个问题,我不知道我是否需要做一些额外的事情,或者我是否错误地使用了 onChange 回调。

handleChange = (event) => {
    this.setState({ itemNumber: event.target.value })
  }


<TextField
      id="Part #"
      label="Part #"
      value={this.state.itemNumber}
      onChange={this.handleChange}
      margin="normal"
    />

组件:

export class Dashboard extends Component {
  state = {
    report: '',
    selectedDate: new Date(),
    itemNumber: '',
  }

  static propTypes = {
    classes: object,
    headerTitle: string,
    userInfo: object,
  }

  static defaultProps = {
    classes: {},
    headerTitle: undefined,
    userInfo: {},
  }

  reportSelected = (event) => {
    this.setState(() => {
      return {
        report: event.target.value,
      }
    })
  }

  handleDateChange = (date) => {
    this.setState({ selectedDate: new Date(date) })
  }

  handleChange = (event) => {
    this.setState({ itemNumber: event.target.value })
  }

  render () {
    const { classes, headerTitle, userInfo } = this.props
    return (
      <div className={classes.dashboard}>
        <HeaderTitle title="Dashboard" />
        <Helmet>
          <title>{headerTitle}</title>
        </Helmet>

        { userInfo.isAuthorized &&
          <Grid container direction={'row'} justify={'center'} className={classes.formContainer}>
            <Grid item xs={12} sm={12} md={12} lg={6} xl={5}>
              <form className={classes.form}>
                <FormControl className={classes.presetReportsInput}>
                  <InputLabel htmlFor="reports">Preset Reports</InputLabel>
                  <Select
                    value={this.state.report}
                    onChange={this.reportSelected}
                  >
                    <MenuItem value="">
                      <em>None</em>
                    </MenuItem>
                    {presetReports.getReportList().map(report => (
                      <MenuItem value={report.name} key={report.name}>
                        {report.name}
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>

                { (this.state.report === 'Inventory Snapshot' ||
                   this.state.report === 'Weekly Fill Rate' ||
                   this.state.report === 'Open Orders' ||
                   this.state.report === 'Weekly Shipments') &&
                   <div>
                     <Grid container spacing={8} direction={'row'}>
                       <Grid item>
                         <MuiPickersUtilsProvider utils={MomentUtils}>
                           <DatePicker
                             className={classes.datePicker}
                             margin="normal"
                             keyboard
                             format="DD/MM/YYYY"
                             disableFuture
                             autoOk
                             mask={value => (value ? [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/] : [])}
                             value={this.state.selectedDate}
                             onChange={this.handleDateChange}
                             disableOpenOnEnter
                             animateYearScrolling={false}
                           />
                         </MuiPickersUtilsProvider>
                       </Grid>

                       <Grid item>
                         <TextField
                           id="Part #"
                           label="Part #"
                           value={this.state.itemNumber}
                           onChange={this.handleChange}
                           margin="normal"
                         />
                       </Grid>
                     </Grid>

                     <Button variant="raised" color="primary" style={{ marginTop: 10 }}>
                       Search
                     </Button>
                   </div>
                }

                { this.state.report === '' &&
                  <div>
                    <TextField
                      id="queryField"
                      label="Run a Query"
                      className={classes.queryField}
                      helperText=""
                      margin="normal"
                      multiline
                      rows="5"
                    />

                    <Grid container direction={'row'} justify={'flex-end'}>
                      <Grid item>
                        <Button variant="raised" color="primary">
                          Export
                        </Button>
                      </Grid>
                      <Grid item>
                        <Button variant="raised" color="primary">
                          Save Query
                        </Button>
                      </Grid>
                    </Grid>
                  </div>
                }
              </form>
            </Grid>

            { this.state.report === 'Inventory Snapshot' &&
              <Grid container className={classes.table}>
                <Grid item xs={12} sm={12} md={12} lg={12} xl={12}>
                  <InventoryReport />
                </Grid>
              </Grid>
            }
          </Grid>
        }
      </div>
    )
  }
}

const styles = {
  dashboard: {},
  formContainer: {
    margin: 0,
    width: '100%',
  },
  presetReportsInput: {
    width: '100%',
    margin: '20% 0 0 0',
  },
  queryField: {
    width: '100%',
    margin: '20% 0 0 0',
  },
  table: {
    margin: '50px 0 10px 0',
  },
  datePicker: {
    marginTop: 32,
  },
}

const mapStateToProps = state => {
  const { layout } = state
  const { headerTitle } = layout
  return {
    headerTitle: headerTitle,
  }
}

export default connect(mapStateToProps)(withStyles(styles)(Dashboard))

我正在查看 chrome 中 react devtools 中的状态更新,并且在输入字符和状态更新之间至少有 500 毫秒的延迟,对于更快的输入来说更长的时间。为什么 setState 这么慢?有什么解决方法可以让这个表单表现得像一个普通的 Web 表单?

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    setState 本身并不慢,只有当您的渲染变得非常昂贵时才会开始引起问题。

    需要考虑的几点是:

    • 您的主要组件看起来相当大,并且在每次击键时都重新渲染,因此可能会导致性能问题。尝试将其分解为更小的组件。
    • 确保在主组件的render 方法中渲染的子组件不会被不必要地重新渲染。 React Developer Toolswhy-did-you-render 可以指出那些不必要的重新渲染。切换到PureComponent、无状态组件或使用shouldComponentUpdate 会有所帮助。
    • 虽然您无法避免在此处重新渲染(因为您的表单输入需要使用新的状态值重新渲染),但通过分解成更小的组件,您可以使用shouldComponentUpdate 让 React 知道组件的输出是否不受影响通过当前状态或道具的变化,避免不必要的重新渲染。
    • 如果您使用功能组件:
      • 使用useMemo 来防止重新计算昂贵的操作或组件,除非某些依赖项发生了变化。
      • 使用 useCallback 返回回调的记忆版本,这样依赖引用相等的子组件就不会不必要地重新渲染
      • 如果您的功能组件在给定相同道具的情况下呈现相同的结果,请使用React.memo,以防止不必要的重新呈现。使用React.memo 的第二个参数来自定义memoization 的行为(类似于shouldComponentUpdate
    • 切换到生产版本以获得更好的性能
    • 切换到uncontrolled components 并让DOM 自己处理输入组件(这是您描述的“正常Web 表单”行为)。当您需要访问表单的值时,您可以使用ref 来访问底层 DOM 节点并直接从中读取值。这应该消除调用setState 并因此重新渲染的需要

    【讨论】:

    【解决方案2】:

    当一个输入中的更改触发整个组件重新渲染时,这对于大型表单来说尤其是一个问题,并且在 redux 中也是如此,尽管 redux 似乎在这里没有干扰。

    如果您希望您的输入受到控制并具有完全相同的行为,那么您总是可以这样做

    <input
      className="form-control"
      type="text"
      name="name"
      value={form.name}
      onBlur={onChangeHandler}
    />
    

    这只会触发模糊事件并防止每次更改时重新渲染。这很有用,因为当您单击任何其他按钮来处理数据时,可以保证您将获得更新的状态。 如果您的逻辑需要与输入数据相关的即时验证/处理,这将无济于事。

    另外,重要的是我应该提到,有时这无法与非 HTML5 原生的其他组件一起使用,因为它们可能会阻止基于 value 属性的重新渲染

    注意:请阅读 onBlur 事件here

    【讨论】:

      【解决方案3】:

      这似乎是一个微不足道的响应,但是 - 确保您的控制台已关闭。控制台打开时,受控组件有明显的滞后!

      【讨论】:

        【解决方案4】:

        您可以使用https://reactjs.org/docs/perf.html 来分析您的应用。您是否有大量可以重新渲染的组件?可能需要在组件中添加一些 componentShouldUpdate() 方法以防止无用的重新渲染。

        【讨论】:

          【解决方案5】:

          我最近遇到了同样的问题,我有一个 redux 商店和一个描述,在描述输入中的每次击键后,商店都必须更新,我试图在lodash 中去抖动,但它没有用,所以我创建了一个设置超时函数来像这样简单地更新存储状态,

           setTimeout(() => {
            console.log("setting");
            this.props.addDescription(this.state.description);
          }, 200);
          

          我从描述组件自己的状态中获取描述输入字段值,并且每当它重新呈现时,我使用componentDidMount() 从商店获取最新更新的描述值。

          【讨论】:

            【解决方案6】:

            如果您正在使用具有太多重新渲染依赖项的大型父组件 我建议您在子组件的 useEffect 中处理重新渲染 或者如果它强制使用父级中的所有状态更新 在子组件中使用debounce

            【讨论】:

              【解决方案7】:

              我在开发模式下也遇到过类似的问题,即输入缓慢,但功能组件和挂钩。生产还可以,但显然打开生产看起来不像是方法,如果它在开发模式下如此缓慢,则很有可能存在代码问题。因此解决方案是将输入状态与其余组件隔离开来。组件使用的状态应该仅适用于该组件。实际上它甚至不是一个解决方案,而是事情应该如何反应。

              【讨论】:

                【解决方案8】:

                @ᴘᴀɴᴀʏɪᴏᴛɪs 提供的选项很好,但对我的用例来说没有帮助。我通过在设置状态之前添加去抖动来解决它。

                const delaySetStateWithDelay = () => {
                    if (lastRequest) {
                      window.clearTimeout(lastRequest);
                    }
                    lastRequest = setTimeout(() => {
                      setStateData('Data I Need To Set'
                    }, 500);
                  };
                

                【讨论】:

                  【解决方案9】:

                  前段时间我遇到了同样的问题,必须将来自父组件的状态复制到本地对象,然后将新的本地对象引用到我的输入中。

                  如果您在保存表单之前不需要在其他任何地方使用新状态,我认为您可以使用以下解决方案。

                    const { selectedCustomer } = props;
                    const [localCustomer, setLocalCustomer] = useState({ name: 'Default' });
                  
                    useEffect(() => {
                      setLocalCustomer({ ...selectedCustomer });
                    }, [selectedCustomer]);
                  
                    const handleChangeName = (e) => {
                      setLocalCustomer({ ...localCustomer, name: e.target.value });
                    };
                  

                  然后在我的文本字段中使用它。

                  <StyledTextField
                   fullWidth
                   type='text'
                   label='Name'
                   value={localCustomer.name}
                  </StyledTextField>
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-08-27
                    • 2021-12-30
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-11-06
                    • 2018-10-22
                    • 2017-12-06
                    相关资源
                    最近更新 更多