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

Исходное сообщение

У меня есть страница, которая отображает каждый элемент в массиве, содержащемся в состоянии редукции, с использованием Array.prototype.map. Массив изначально заполняется при вызове обратного вызова componentWillMount(), а затем добавляется, когда пользователь запрашивает дополнительные данные, т. е. когда пользователь прокручивает страницу до конца и если есть еще элементы для выборки. Я слежу за тем, чтобы состояние редукции не изменялось при добавлении массива с помощью Array.prototype.concat(). Однако, когда в массив добавляется больше элементов, кажется, что реакция повторно отображает каждый элемент в массиве, а не только новые элементы, несмотря на установку уникального ключа для каждого элемента. Мой исходный код для рендеринга компонентов выглядит следующим образом:

this.props.state.list.map((item, index) => {
  return (
    <Grid className={classes.column} key={item._id} item xs={6} sm={6} md={4}>
      <Card className={classes.card}>
        <CardActionArea className={classes.cardAction}>
          <div className={classes.cardWrapper}>
            <div className={classes.outer}>
              <div className={classes.bgWrapper}>
                <img
                  className={[classes.bg, "lazyload"].join(" ")}
                  data-sizes="auto"
                  data-src={genSrc(item.pictures[0].file)}
                  data-srcset={genSrcSet(item.pictures[0].file)}
                  alt={item.name}
                />
              </div>
            </div>
            <CardContent className={classes.cardContent}>
              <div className={classes.textWrapper}>
                <div className={classes.textContainer}>
                  <Typography
                    gutterBottom
                    className={[classes.text, classes.title].join(" ")}
                    variant="title"
                    color="inherit"
                  >
                    {item.name}
                  </Typography>
                  <Typography
                    className={[classes.text, classes.price].join(" ")}
                    variant="subheading"
                    color="inherit"
                  >
                    {item.minPrice === item.maxPrice
                      ? `$${item.minPrice.toString()}`
                      : `$${item.minPrice
                          .toString()
                          .concat(" - $", item.maxPrice.toString())}`}
                  </Typography>
                </div>
              </div>
            </CardContent>
          </div>
        </CardActionArea>
      </Card>
    </Grid>
  );
});

Вот мой редуктор, если вы думаете, что это может иметь какое-то отношение к этому:

import {
  FETCH_PRODUCTS_PENDING,
  FETCH_PRODUCTS_SUCCESS,
  FETCH_PRODUCTS_FAILURE
} from "../actions/products";

let defaultState = {
  list: [],
  cursor: null,
  calls: 0,
  status: null,
  errors: []
};

const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case FETCH_PRODUCTS_PENDING:
      return {
        ...state,
        status: "PENDING"
      };
    case FETCH_PRODUCTS_SUCCESS:
      return {
        ...state,
        calls: state.calls + 1,
        cursor: action.payload.data.cursor ? action.payload.data.cursor : null,
        list: action.payload.data.results
          ? state.list.concat(action.payload.data.results)
          : state.list,
        status: "READY"
      };
    case FETCH_PRODUCTS_FAILURE:
      return {
        ...state,
        status: "FAIL",
        call: state.calls + 1,
        errors: [...state.errors, action.payload]
      };
    default:
      return state;
  }
};

export default reducer;

Из ответов на вопросы, подобные моему, я прочитал, что установка метода shouldComponentUpdate() для каждого компонента, сгенерированного из массива, может помочь, но нет четких примеров того, как это можно сделать. Я был бы очень признателен за помощь. Спасибо!

Изменить 1

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

Редактировать 2

Вот весь код компонента, отображающего список.

class Products extends React.Component {

  componentDidMount() {
    const { fetchNext, fetchStart, filter } = this.props
    fetchStart(filter)

    window.onscroll = () => {
      const { errors, cursor, status } = this.props.state
      if (errors.length > 0 || status === 'PENDING' || cursor === null) return
      if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
        fetchNext(filter, cursor)
      }
    }
  }

  render() {
    const { classes, title } = this.props

    return (
      <div className={classes.container}>
        <Grid container spacing={0}>
          {
            this.props.state.status && this.props.state.status === 'READY' && this.props.state.list &&
            this.props.state.list.map((item, index) => {
              return (
                <Grid className={classes.column} key={item._id} item xs={6} sm={6} md={4}>
                  <Card className={classes.card}>
                    <CardActionArea className={classes.cardAction}>
                      <div className={classes.cardWrapper}>
                        <div className={classes.outer}>
                          <div className={classes.bgWrapper}>
                            <img className={[classes.bg, 'lazyload'].join(' ')} data-sizes='auto' data-src={genSrc(item.pictures[0].file)} data-srcset={genSrcSet(item.pictures[0].file)} alt={item.name} />
                          </div>
                        </div>
                        <CardContent className={classes.cardContent}>
                          <div className={classes.textWrapper}>
                            <div className={classes.textContainer}>
                              <Typography gutterBottom className={[classes.text, classes.title].join(' ')} variant="title" color='inherit'>
                                {item.name}
                              </Typography>
                              <Typography className={[classes.text, classes.price].join(' ')} variant='subheading' color='inherit'>
                                {item.minPrice === item.maxPrice ? `$${item.minPrice.toString()}` : `$${item.minPrice.toString().concat(' - $', item.maxPrice.toString())}`}
                              </Typography>
                            </div>
                          </div>
                        </CardContent>
                      </div>
                    </CardActionArea>
                  </Card>
                </Grid>
              )
            })
          }
        </Grid>
      </div>
    )
  }
}

person elemetrics    schedule 26.09.2018    source источник
comment
Если состояние изменяется, компонент и все его дочерние элементы перерисовываются. Это так нормально. Но рендеринг не означает, что эти компоненты будут снова размонтированы/установлены или уничтожены/воссозданы. Это произойдет только с измененными частями. Это связано с согласованием. Итак, рендеринг не так дорог, как манипулирование DOM. Но если вы уверены, что рендеринг всех этих компонентов приводит к некоторой потере производительности, вы можете проверить shouldComponentUpdate (будьте осторожны, иногда прикосновение к нему приводит к снижению производительности) или вы можете использовать PureComponent.   -  person devserkan    schedule 26.09.2018
comment
Привет @devserkan спасибо за ваш комментарий. Я думаю, что DOM повторно заполняется элементами массива, которые уже существовали, когда добавляются новые элементы. Когда я тестирую страницу в Chrome и Firefox, существующие элементы исчезают и появляются снова при загрузке новых элементов. Это тоже ожидаемое поведение?   -  person elemetrics    schedule 26.09.2018
comment
Если это так, то я должен узнать больше :) Я думал, что в этом случае происходит только повторный рендеринг, а не все манипуляции с DOM. Я знаю, что React добавляет или удаляет дочерние элементы, но я не думал, что в этом случае изменится вся DOM.   -  person devserkan    schedule 26.09.2018
comment
Я тоже этого ожидаю. Я почти уверен, что я что-то делаю не так. В любом случае еще раз спасибо за ваши комментарии   -  person elemetrics    schedule 26.09.2018
comment
Когда вы говорите «исчезнуть» и «появиться снова», вы имеете в виду, что во время загрузки они из видимых становятся исчезнувшими? Делаете ли вы что-нибудь с полем состояния вашего редуктора, из-за чего элементы списка не отображаются? Может быть полезно опубликовать весь код компонента, а не только рендеринг списка.   -  person TLadd    schedule 26.09.2018
comment
@TLadd Да, они появляются и исчезают. Инспектор страниц Chrome также показывает, что все существующие элементы исчезают и появляются снова вместе с обновленным содержимым. Я также предоставил код для всего компонента в самом последнем редактировании сообщения.   -  person elemetrics    schedule 26.09.2018
comment
вы можете написать pure Component, чтобы просто отображать элементы карты и передавать элементы в качестве реквизита этому компоненту и смотреть, происходит ли то же самое. для получения дополнительной информации reactjs.org/docs/optimizing-performance.html   -  person aravind_reddy    schedule 26.09.2018
comment
вы поняли или должны добавить пример в качестве ответа?   -  person aravind_reddy    schedule 26.09.2018
comment
@elemetrics Я предполагаю, что fetchNext в конечном итоге запускает FETCH_PRODUCTS_PENDING? Это приведет к тому, что список не будет отображаться из-за условия this.props.state.status && this.props.state.status === 'READY'. Кажется, вы можете полностью удалить эту проверку, и всплывающее окно при загрузке должно быть исправлено.   -  person TLadd    schedule 26.09.2018
comment
@aravind_reddy спасибо за предложение. Я пытаюсь сделать это сейчас. Если я правильно вас понял, мне следует написать отдельный компонент реакции для частей, которые рендерятся функцией карты. Скажем, я создаю компонент с именем: Item, затем в методе рендеринга моего родительского компонента я просто делаю что-то вроде: list.map(item =› { ‹Item key={item._id} /› }) ?   -  person elemetrics    schedule 26.09.2018
comment
@TLAdd ваше предложение имеет определенную ценность. Я постараюсь не устанавливать состояние PENDING и дам вам знать. Это довольно упущение с моей стороны. Спасибо.   -  person elemetrics    schedule 26.09.2018
comment
@elemetrics точно использует чистый компонент для компонента Item, но просто помните, что pure component выполняет только shallow comparison, если у вас есть вложенные объекты, вы должны попытаться реализовать componentShouldUpdate самостоятельно. если вы понимаете, что я имею в виду   -  person aravind_reddy    schedule 26.09.2018
comment
@aravind_reddy Ага. Я понял. Спасибо еще раз.   -  person elemetrics    schedule 26.09.2018
comment
@TLadd, ты попал. Это определенно была проверка статуса PENDING, из-за которой весь список исчезал между выборками. Большое спасибо.   -  person elemetrics    schedule 26.09.2018


Ответы (1)


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

Изменение следующего из

    {
      this.props.state.status && this.props.state.status === 'READY' && this.props.state.list &&
        this.props.state.list.map((item, index) => {
          return <Item />
        }
    }

к этому, тем самым удалив проверку состояния завершенной выборки, решил мою проблему.

    {
      this.props.state.list &&
        this.props.state.list.map((item, index) => {
          return <Item />
        }
    }
person elemetrics    schedule 26.09.2018