Эффективно рендеринг большого количества полей формы Redux? - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть таблица сотрудников и их ежемесячное рабочее время изо дня в день. Здесь мы можем обновить все значения рабочих часов сотрудников.

Простая математика для текущего месяца: 30 дней x 50 сотрудников приведут к 1500 смонтированным полям формы Redux.

Каждый раз, когда поле монтируется, Redux Form отправляет действие для регистрации поля в хранилище Redux. Поэтому отправлено 1500 событий.

Отладка с помощью инструмента Chrome-> Performance. Я обнаружил, что весь процесс от монтажа до диспетчеризации и отрисовки полей занимает около ~ 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 или рендеринг большого количества полей одновременно является узким местом, и я должен выбрать другой подход (разбиение на страницы, отложенная загрузка и т. Д.)?

Ответы [ 2 ]

0 голосов
/ 08 ноября 2018

Существующие библиотеки форм с открытым исходным кодом, кажется, не работают должным образом, когда сталкиваются с такой сложностью. Мы реализовали ряд оптимизаций, создав собственную библиотеку форм, но возможно, что одну или несколько из этих идей вы сможете использовать с избыточной формой.

Мы монтируем множество форм и других присоединенных компонентов (порядка тысяч), которые все проверяют, когда они монтируются. Наша архитектура требовала одновременной установки всех компонентов. Каждому компоненту может потребоваться несколько раз обновить состояние при подключении. Это не быстро, но это оптимизация, которую мы использовали, чтобы заставить казаться быстрым для 1000+ элементов управления:

  • Каждый компонент монтируется таким образом, который проверяет, действительно ли ему действительно необходимо отправить что-то прямо сейчас или же он может подождать до позднего времени. Если значение в состоянии неверно или текущее значение недопустимо - оно все равно вызовет диспетчеризацию, в противном случае будут отложены другие обновления на более позднее время (например, когда пользователь фокусирует его).

  • Реализован пакетный редуктор, который может обрабатывать несколько действий за одну отправку. Это означает, что если компоненту необходимо выполнить несколько действий при монтировании, самое большее, он будет отправлять только 1 сообщение об отправке вместо одной отправки за действие.

  • Повторная визуализация отклика реагирования с использованием промежуточного программного обеспечения для редукционного пакета. Это означает, что реакция будет инициироваться для рендеринга реже, когда происходит большое обновление редукса. Мы запускаем прослушиватели на переднем и заднем фронте обновлений редуксов, что означает, что реакция будет обновляться при первой отправке и каждые 500-1000 мс после этого, пока не произойдет больше никаких обновлений. Это значительно улучшило производительность для нас, но в зависимости от того, как работают ваши элементы управления, это также может сделать его немного запаздывающим (см. Следующий пункт для решения)

  • Состояние местного управления. Каждый элемент управления формы имеет локальное состояние и немедленно реагирует на взаимодействие с пользователем, и будет лениво обновлять состояние избыточности, когда пользователь выходит из элемента управления. Это заставляет вещи казаться быстрыми и приводит к меньшему количеству обновлений. (редукционные обновления стоят дорого !!)

Я не уверен, что что-то из этого поможет вам, и я даже не уверен, что вышесказанное было разумным или рекомендованным, - но оно сработало очень хорошо для нас, и я надеюсь, что оно может дать вам некоторые идеи.

Кроме того, если вам не нужно монтировать меньше компонентов (нумерация страниц и т. Д.), Вам определенно следует сделать это вместо этого. Мы были ограничены в своем выборе некоторыми ранними архитектурными решениями и были вынуждены продолжать, и это то, что мы придумали.

0 голосов
/ 08 ноября 2018

Вы пробовали https://www.npmjs.com/package/react-virtualized Я использовал это в проекте, где я снимал дюжину событий. Список растет и растет, и этот компонент помог мне отрисовать их все. Я не уверен, как работает Redux Form, но если он основан на монтировании, то я думаю, что это хороший вариант.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...