React-native FlatList не перерисовывает строку при смене реквизита
У меня проблема с новым компонентом FlatList. В частности, он не перерисовывает свои строки, даже несмотря на то, что строка зависит от изменений.
Документы FlatList говорят, что:
Это PureComponent, что означает, что он не будет повторно визуализироваться, если реквизит остается мелко-равным. Убедитесь, что все, от чего зависит ваша функция renderItem, передается как опора, которая не === после обновлений, иначе ваш пользовательский интерфейс может не обновляться при изменениях. Это включает в себя реквизит данных и состояние родительского компонента.
ВОПРОС
Однако, видя, как я меняю идентификатор элемента selectedCategory - реквизита, который должен указывать, является ли строка "выбранной" или нет, - я считаю, что реквизит должен перегенерироваться. Я ошибаюсь?
Я проверил методы componentWillReceiveProps компонентов list и row, и список получает обновление просто отлично, но метод жизненного цикла строки никогда не вызывается.
Если я включу случайное бесполезное значение логического состояния в компонент списка и переключу его назад и вперед при обновлении реквизита, это будет работать - но я не знаю почему?
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: '',
},
...
};
9 ответов
Проблема здесь заключается в том, что
- Вы изменяете существующий фрагмент состояния вместо того, чтобы создавать измененную копию
_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 });
};
Реквизиты FlatList остаются прежними, ваши
_renderRow
функция может полагаться наselectedCategory
prop, который действительно изменяется (если не для первой ошибки), но компонент FlatList не знает об этом. Чтобы решить эту проблему, используйтеextraData
двигательный<FlatList data={this.props.items.categories.edges} renderItem={this._renderRow} horizontal={true} keyExtractor={(item, index) => item.node.id} extraData={this.props.selectedCategory} />
Просто вы можете решить эту проблему, передавая реквизиты в extraData в компоненте плоского списка следующим образом:
<FlatList
data={this.props.data}
extraData={this.props}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
В моем случае я просто сделал простую ошибку при использовании keyExtractor
Я изменился
keyExtractor={(item, index) => index.toString()}
Чтобы
keyExtractor={(item, index) => item.key}
Я наблюдал какой-то странный эффект после фильтрации моего списка, где реквизиты отфильтрованных компонентов рендерились вместо реквизитов нового компонента, я догадываюсь, что, поскольку я использовал индекс массива, а не уникальный ключ, я просто передавал старые реквизиты для нового компонента, хотя базовый компонент фактически менялся.
Я согласен с Нимелрианом. Также, если ваше состояние Array, вы можете создать Array Object из этого состояния, выполнив:
var oldReportCopy = Object.assign([], this.state.report);
Затем используйте метод.push(), чтобы добавить к нему новый объект, например:
oldReportCopy.push(selectedCategory);
Затем вы можете вернуть этот новый объект Array в состояние:
this.setState({ report: oldReportCopy });
Возможно, это не относится ни к кому другому, но я понял, что у меня проблемы только тогда, когда массив элементов, отображаемых FlatList, стал пустым. В моем случае мне просто нужно было вообще не отображать FlatList, а вместо этого отображать другое представление на его месте, и это, конечно же, устранило мою проблему с ним "без повторного рендеринга".
Посмотрите на строку №4
_onCategoryChosen = category => {
var oldReportCopy = this.state.report;
oldReportCopy.selectedCategory = category;
this.setState({ report: [...oldReportCopy] }); // Notice this line
};
это не сработало для меня
setTabData(tabD);
и это сработало для меня
setTabData([...tabD]);
В ответных хуках вы можете сделать что-то вроде этого:
const onPressLeaderSelect = (item, index) => {
let oldMemberCopy = Object.assign([], teamMemberArr); //'teamMemberArr' is local state
let objIndex = oldMemberCopy.findIndex((obj => obj.teamLeader == 1));
oldMemberCopy[objIndex].teamLeader = 0
oldMemberCopy[index].teamLeader = 1
console.log('onPressLeaderSelect', oldMemberCopy)
setteamMemberArr(oldMemberCopy)
}
это может быть полезно
FlatList
код
/**
* Multiple columns can only be rendered with `horizontal={false}` and will zig-zag like a `flexWrap` layout.
* Items should all be the same height - masonry layouts are not supported.
*/
numColumns?: number | undefined;