Подтвердить что ты не робот

React: обновить один элемент в списке без повторного создания всех элементов

Скажем, у меня есть список из 1000 предметов. И я передаю его с помощью React, например:

class Parent extends React.Component {
  render() {
    // this.state.list is a list of 1000 items
    return <List list={this.state.list} />;
  }
}

class List extends React.Component {
  render() {
    // here we're looping through this.props.list and creating 1000 new Items
    var list = this.props.list.map(item => {
      return <Item key={item.key} item={item} />;
    });
    return <div>{list}</div>;
  }
}

class Item extends React.Component {
  shouldComponentUpdate() {
    // here I comparing old state/props with new
  }
  render() {
    // some rendering here...
  }
}

С относительно длинным списком() занимает около 10-20 мс, и я могу заметить небольшое отставание в интерфейсе.

Можно ли предотвратить восстановление объектов 1000 React каждый раз, когда мне нужно только обновить?

4b9b3361

Ответ 1

ИЗМЕНИТЬ

В моих собственных предложениях были рассмотрены возможные улучшения эффективности списков и не затрагивает вопрос об ограничении повторной рендеринга компонентов в результате изменения списка.

См. @xiaofan2406 для ответа на чистое решение исходного вопроса.


Библиотеки, которые помогают сделать рендеринг длинными списками более эффективными и легкими:

React Infinite

React-Virtualized

Ответ 2

Вы можете сделать это, используя любую библиотеку управления государством, чтобы ваш Parent не отслеживал this.state.list = > ваш List только повторно отображается, когда добавляется новый Item. И индивидуальный Item будет повторно рендерить, когда они будут обновлены.

Допустим, вы используете redux.

Ваш код станет примерно таким:

// Parent.js
class Parent extends React.Component {
  render() {        
    return <List />;
  }
}

// List.js
class List extends React.Component {
  render() {        
    var list = this.props.list.map(item => {
      return <Item key={item.key} uniqueKey={item.key} />;
    });
    return <div>{list}</div>;
  }
}

const mapStateToProps = (state) => ({
  list: getList(state)
});

export default connect(mapStateToProps)(List);


// Item.js
class Item extends React.Component {
  shouldComponentUpdate() {
  }
  render() {
  }
}

const mapStateToProps = (state, ownProps) => ({
  item: getItemByKey(ownProps.uniqueKey)
});

export default connect(mapStateToProps)(Item);

Конечно, вам нужно реализовать редуктор и два селектора getList и getItemByKey.

При этом вы List re-render будет запускаться при добавлении новых элементов или при изменении item.key (что вам не нужно)

Ответ 3

Один из способов избежать прокрутки списка компонентов в каждом рендере - сделать это вне функции рендеринга и сохранить его в переменной.

class Item extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return this.props.item != nextProps.item;
    }

    render() {
        return <li>{this.props.item}</li>;
    }
}

class List extends React.Component {
    constructor(props) {
        super(props);
        this.items = [];
        this.update = this.update.bind(this);
    }

    componentWillMount() {
        this.props.items.forEach((item, index) => { this.items[index] = <Item key={index} item={item} /> });
    }

    update(index) {

        this.items[index] = <Item key={index} item={'item changed'} />
        this.forceUpdate();
    }

    render() {
        return <div>
            <button onClick={() => { this.update(199); }}>Update</button>
            <ul>{this.items}</ul>
        </div>
    }
}

Ответ 4

Есть несколько вещей, которые вы можете сделать:

  • Когда вы создаете, убедитесь, что вы устанавливаете NODE_ENV для производства. например NODE_ENV=production npm run build или аналогичный. ReactJS выполняет множество проверок безопасности, если NODE_ENV не настроен на производство, например, проверки PropType. Выключение этих функций должно дать вам улучшение производительности в 2 раза для рендеринга React и жизненно важно для вашей сборки (хотя оставьте это во время разработки - эти проверки безопасности помогают предотвратить ошибки!). Вы можете обнаружить, что это достаточно хорошо для количества элементов, которые вам нужно поддерживать.
  • Если элементы находятся в прокручиваемой панели, и вы можете видеть только некоторые из них, вы можете настроить их только для визуализации видимого подмножества элементов. Это проще всего, когда элементы имеют фиксированную высоту. Основной подход состоит в том, чтобы добавить firstRendered/lastRendered реквизиты в ваше состояние списка (это первое включение и последнее исключение, конечно). В List.render отрисуйте заполнитель div (или tr, если применимо) правильной высоты (firstRendered * itemHeight), затем отобразите диапазон элементов [firstRendered, lastRendered), затем еще один наполнитель div с оставшейся высотой ((totalItems - lastRendered) * itemHeight). Убедитесь, что вы надели свои наполнители и элементы фиксированными ключами. Затем вам просто нужно обработать onScroll на прокручиваемом div и определить, какой правильный диапазон для рендеринга (как правило, вы хотите сделать приличное перекрытие сверху и снизу, также вы хотите только запустить setState, чтобы изменить диапазон, когда вы приближаетесь к краю его). Более безумной альтернативой является рендеринг и реализация собственной полосы прокрутки (которая, как мне кажется, принадлежит Facebook FixedDataTable - https://facebook.github.io/fixed-data-table/). Здесь есть много примеров этого общего подхода. https://react.rocks/tag/InfiniteScroll
  • Используйте подход боковой загрузки, используя библиотеку управления состоянием. Для больших приложений это в любом случае необходимо. Вместо того, чтобы передавать состояние Items сверху, каждый элемент получает свое собственное состояние либо из "глобального" состояния (как в классическом потоке), либо через контекст React (как в современных реализациях Flux, MobX и т.д.). Таким образом, когда элемент изменяется, только этот элемент необходимо повторно отобразить.

Ответ 5

Когда вы меняете свои данные, реагируйте на действия по умолчанию, чтобы отобразить все дочерние компоненты, и creat virtual dom, чтобы судить, какой компонент необходимо переименовать.

Итак, если мы можем сообщить, что знаете, что только один компонент должен быть реендер. Это может сэкономить время.

Вы можете использовать shouldComponentsUpdate в своем компоненте списка.

Если в этой функции вернет false, реакция не создаст витального dom, чтобы судить.

Я предполагаю, что ваши данные выглядят как [{name: 'name_1'}, {name: 'name_2'}]

class Item extends React.Component {
  // you need judge if props and state have been changed, if not
  // execute return false;
  shouldComponentUpdate(nextProps, nextState) { 
    if (nextProps.name === this.props.name) return false;

    return true;
  }
  render() {
    return (
      <li>{this.props.name}</li>
   )
  }
}

Как реагировать, просто визуализируйте то, что было изменено компонентом. Поэтому, если вы просто измените данные одного элемента, другие не будут делать рендеринг.