javascript - 高效渲染大量 Redux 表单字段?

标签 javascript reactjs redux react-redux redux-form

我有一张员工表以及他们每月的工作时间。在这里我们可以批量更新所有员工的工时值。

当月的简单计算:30 天 x 50 名员工将导致 1500 个已安装的 Redux 表单字段。

每次挂载 Field 时,Redux Form 都会调度一个在 Redux 存储中注册 Field 的操作。因此调度了 1500 个事件。

使用Chrome->性能工具进行调试,我发现从挂载到调度再到渲染Fields的整个过程大约需要4秒:

Performance cost

上述性能得分基于我使用 React、Redux、Redux Form、Reselect 创建的以下简单工作示例:

/* ----------------- SAMPLE DATA GENERATION - START ----------------- */
const generateEmployees = length =>
  Array.from({ length }, (v, k) => ({
    id: k + 1,
    name: `Emp ${k + 1}`,
    hours: [8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8]
  }))

const employees = generateEmployees(50)
/* ----------------- SAMPLE DATA GENERATION - END ------------------- */

/* --------------------- INITIALIZATION - START --------------------- */
const { reduxForm, Field, reducer: formReducer } = ReduxForm
const { createStore, combineReducers, applyMiddleware } = Redux
const { Provider, connect } = ReactRedux
const { createSelector } = Reselect

// Creating the Reducers
const employeesReducer = (state = employees) => state
const reducers = {
  form: formReducer,
  employees: employeesReducer
}

// Custom logger.
// The idea here is log all the dispatched action,
// in order to illustrate the problem with many registered fields better.
const logger = ({ getState }) => {
  return next => action => {
    console.log("Action: ", action)
    return next(action)
  }
}

const reducer = combineReducers(reducers)
const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
  applyMiddleware(logger)
)
/* --------------------- INITIALIZATION - END ----------------------- */

const renderEmployees = employees =>
  employees.map(employee => {
    return (
      <tr key={employee.id}>
        <td>{employee.id}</td>
        <td>{employee.name}</td>
        {employee.hours.map((hour, day) => (
          <td key={day}>
            <Field component="input" name={`${employee.id}_${day}`} />
          </td>
        ))}
      </tr>
    )
  })

const FormComponent = ({ employees, handleSubmit }) => {
  return (
    <form onSubmit={handleSubmit}>
      <h2>Employees working hours for November (11.2018)</h2>
      <p>
        <button type="submit">Update all</button>
      </p>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            {Array.from({ length: 30 }, (v, k) => (
              <th key={k + 1}>{`${k + 1}.11`}</th>
            ))}
          </tr>
        </thead>
        {renderEmployees(employees)}
      </table>
    </form>
  )
}

const Form = reduxForm({
  form: "workingHours",
  onSubmit: submittedValues => {
    console.log({ submittedValues })
  }
})(FormComponent)

const getInitialValues = createSelector(
  state => state.employees,
  users =>
    users.reduce((accumulator, employee) => {
      employee.hours.forEach(
        (hour, day) => (accumulator[`${employee.id}_${day}`] = hour)
      )

      return accumulator
    }, {})
)

const mapStateToProps = state => ({
  employees: state.employees,
  initialValues: getInitialValues(state)
})

const App = connect(mapStateToProps)(Form)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
)
table {
  border-collapse: collapse;
  text-align: center;
}

table, th, td {
  border: 1px solid black;
}

th {
  height: 50px;
  padding: 0 15px;
}

input {
  width: 20px;
  text-align: center;
}
<script src="https://unpkg.com/react@15.5.4/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.5.4/dist/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.4/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-form/6.7.0/redux-form.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/3.0.1/reselect.js"></script>

<div id="root">
    <!-- This element's contents will be replaced with your component. -->
</div>

那么我是否对 Redux Form 做了一些低效且错误的事情,或者一次渲染大量字段是瓶颈,我应该采取不同的方法(分页、延迟加载等)?

最佳答案

现有的开源表单库在遇到这种复杂性时似乎无法很好地工作。我们通过构建自己的表单库实现了许多优化,但您可能可以将这些想法中的一个或多个与 redux 表单一起使用。

我们安装了大量的表单和其他 redux 附加组件(数千个),这些组件在安装时都会进行验证。我们的架构要求立即安装所有组件。每个组件在安装时可能需要多次更新状态。这并不快,但我们使用这些优化来使 1000 多个控件看起来快速:

  • 每个组件的安装方式都会检查它是否确实需要立即分派(dispatch)某些内容,或者是否可以等到以后再分派(dispatch)。如果状态中的值错误,或者当前值无效 - 它仍然会导致调度,否则它将推迟稍后的其他更新(例如当用户聚焦它时)。

  • 实现了一个批量缩减程序,可以在一次调度中处理多个操作。这意味着如果组件确实需要在挂载时执行多个操作,则最多只会发送 1 条调度消息,而不是每个操作发送一次调度。

  • 使用 redux 批处理中间件来消除 react 重新渲染。这意味着当发生大的 redux 更新时,react 将被触发以更少的频率进行渲染。我们在 redux 更新的前沿和下降沿触发监听器,这意味着 React 将在第一次调度时更新,此后每 500-1000 毫秒更新一次,直到不再发生更新。这为我们提高了很多性能,但是根据您的控件的工作方式,这也会使它看起来有点滞后(请参阅下一项以获取解决方案)

  • 本地控制状态。每个表单控件都有一个本地状态,并立即响应用户交互,并且当用户跳出控件时会延迟更新 redux 状态。这使得事情看起来很快并且导致 redux 更新更少。 (redux 更新很昂贵!!)

我不确定这些是否会对您有所帮助,我什至不确定上述内容是否明智或值得推荐 - 但它对我们来说非常有效,我希望它能给您一些想法。

此外,如果您可以安装更少的组件(分页等),那么您绝对应该这样做。我们的选择受到一些早期架构选择的限制,被迫继续前进,这就是我们想到的。

关于javascript - 高效渲染大量 Redux 表单字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53192260/

相关文章:

reactjs - 如何将附加参数传递给 useSelector

javascript - 使用 html 和 javascript 停止和启动服务

javascript - 等待某物停止来触发功能

javascript - 如何从 QML 连接 C++ 对象的销毁信号?

javascript - React - 变量未正确显示在页面中

javascript - 可以从 redux-toolkit 中的另一个 Action 调度 slice 的 Action 吗?

javascript - NVD3/D3 改变y轴最小值

reactjs - 使用 react-router-dom 5 防止有条件地返回

reactjs - 如何乐观地更新 ListView 中的项目

redux - 如何使用 redux-observable 在 dom 元素上添加事件监听器