首页 > 解决方案 > 反应输入 onChange 滞后

问题描述

我有一个带有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

解决方案


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

需要考虑的几点是:

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

推荐阅读