React-native FlatList не реверсифицирует строку при изменении реквизита

У меня возникла проблема с новым компонентом FlatList. В частности, он не перезаписывает строки, даже если реквизит зависит от изменений строки.


Документы FlatList говорят, что:

Это PureComponent, что означает, что он не будет повторно отображать, если реквизит остается неглубоким. Убедитесь, что все ваше renderItem функция зависит от передается в качестве опоры, которая не является === после обновлений, в противном случае ваш пользовательский интерфейс может не обновляться при внесении изменений. Сюда входят данные prop и родительского компонента.

ВОПРОС

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

Я проверил методы componentWillReceiveProps как для компонентов списка, так и для строк, а список получает обновление просто отлично, но метод жизненного цикла строки никогда не вызывается.

Если я включаю случайное, бесполезное значение логического состояния в компоненте списка и переключаю его назад и вперед при обновлении реквизита, оно работает - но я не знаю почему?

state = { updated: false };

componentWillReceiveProps(nextProps) {
  this.setState(oldstate => ({
    updated: !oldstate.updated,
  }));
}

<FlatList
  data={this.props.items.allAnimalCategories.edges}
  renderItem={this._renderRow}
  horizontal={true}
  keyExtractor={(item, index) => item.node.id}
  randomUpdateProp={this.state.updated}
/>

КОД

Структура моего кода такова: у меня есть компонент контейнера со всей логикой и состоянием, который содержит компонент FlatList (презентационный, без состояния), который снова содержит пользовательскую презентационную строку.

Container
  Custom list component that includes the FlatList component
  (presentational, stateless) and the renderRow method
    Custom row (presentational, stateless)

Контейнер включает этот компонент:

 <CustomList
   items={this.props.viewer}
   onCategoryChosen={this._onCategoryChosen}
   selectedCategory={this.state.report.selectedCategory}
 />

CustomList:

class CustomList extends Component {
  _renderRow = ({ item }) => {
    return (
      <CustomListRow
        item={item.node}
        selectedCategory={this.props.selectedCategory}
        onPressItem={this.props.onCategoryChosen}
      />
    );
  };

  render() {
    return (
      <View style={_styles.container}>
        <FlatList
          data={this.props.items.categories.edges}
          renderItem={this._renderRow}
          horizontal={true}
          keyExtractor={(item, index) => item.node.id}
          randomUpdateProp={this.state.updated}
        />
      </View>
    );
  }

}

(данные поступают из реле)

Наконец строка:

render() {
    const idsMatch = this.props.selectedCategory.id == this.props.item.id;
    return (
      <TouchableHighlight onPress={this._onItemPressed}>
        <View style={_styles.root}>
          <View style={[
              _styles.container,
              { backgroundColor: this._getBackgroundColor() },
            ]}>
            {idsMatch &&
              <Image
                style={_styles.icon}
                source={require('./../../res/img/asd.png')}
              />}
            {!idsMatch &&
              <Image
                style={_styles.icon}
                source={require('./../../res/img/dsa.png')}
              />}
            <Text style={_styles.text}>
              {capitalizeFirstLetter(this.props.item.name)}
            </Text>
          </View>
          <View style={_styles.bottomView}>
            <View style={_styles.greyLine} />
          </View>
        </View>
      </TouchableHighlight>
    );
  }

Строка не такая интересная, но я включил ее, чтобы показать, что она полностью без гражданства и зависит от нее.

Состояние обновляется следующим образом:

_onCategoryChosen = category => {
    var oldReportCopy = this.state.report;
    oldReportCopy.selectedCategory = category;
    this.setState(Object.assign({}, this.state, { report: oldReportCopy }));
  };

Состояние выглядит следующим образом:

state = {
    ...
    report: defaultStateReport,
  };

const defaultStateReport = {
  selectedCategory: {
    id: 'some-long-od',
    name: '',
  },
  ...
};

Ответ 1

Проблема здесь заключается в том, что

  • Вы мутируете существующий срез состояния вместо создания мутированной копии

_onCategoryChosen = category => {
    var oldReportCopy = this.state.report; // This does not create a copy!
    oldReportCopy.selectedCategory = category;
    this.setState(Object.assign({}, this.state, { report: oldReportCopy }));
};

Это должно быть

_onCategoryChosen = category => {
    var oldReportCopy = Object.assign({}, this.state.report);
    oldReportCopy.selectedCategory = category;
    // setState handles partial updates just fine, no need to create a copy
    this.setState({ report: oldReportCopy });
};

  1. Реквизиты FlatList остаются теми же, ваша функция _renderRow может полагаться на поддержку selectedCategory, которая меняет (если не для первой ошибки), но компонент FlatList об этом не знает. Чтобы решить эту проблему, используйте extraData prop.

    <FlatList
      data={this.props.items.categories.edges}
      renderItem={this._renderRow}
      horizontal={true}
      keyExtractor={(item, index) => item.node.id}
      extraData={this.props.selectedCategory}
    />
    

Ответ 2

Просто вы можете решить эту проблему, передавая реквизиты в extraData в компоненте плоского списка следующим образом:

  <FlatList
    data={this.props.data}
    extraData={this.props}
    keyExtractor={this._keyExtractor}
    renderItem={this._renderItem}
  />

Ответ 3

Я согласен с Нимелрианом. Также, если ваше состояние Array, вы можете создать Array Object из этого состояния, выполнив:

 var oldReportCopy = Object.assign([], this.state.report);

Затем используйте метод .push(), чтобы добавить к нему новый объект, например:

oldReportCopy.push(selectedCategory);

Затем вы можете вернуть этот новый объект Array в состояние:

this.setState({ report: oldReportCopy });