#reactjs #react-native
#reactjs #react-native
Вопрос:
У меня есть экран способов оплаты с простым списком и кнопкой удаления для каждого элемента:
const PaymentIndexScreen = ({ navigation }: Props): ReactElement => {
const [creditCards, setCreditCards] = useState<CreditCard[]>([]);
useEffect(() => navigation.addListener('focus', () => {
client.service('credit-cards').find().then((result) => {
setCreditCards(result.data.map((creditCardData) => {
const year = creditCardData.expirationDate.slice(2);
const month = creditCardData.expirationDate.slice(0, 2);
return {
...creditCardData,
expirationDate: `${month}/${year}`,
loading: false,
};
}));
});
}), [navigation]);
const handleDelete = (selectedCard): void => {
console.log(selectedCard);
selectedCard.loading = true;
Alert.alert(
'Delete?',
null,
[
{
text: 'Cancel'
},
{
text: 'Delete',
onPress: () => client.service('credit-cards').remove(selectedCard._id)
.then(() => setCreditCards(creditCards.filter((card) => card._id !== selectedCard._id)))
.catch(handleClientError)
,
},
],
);
};
return (
<ScreenWrapper>
<ScreenHeader
title="Payment"
navigation={navigation}
/>
<ScrollView>
<ScreenTitle label="Payment methods" />
{creditCards.length > 0
? creditCards.map((card) => (
<ListItem
key={card._id}
title={`●●●● ●●●● ●●●● ${card.lastDigits}`}
content={`Expire on ${card.expirationDate}.`}
onDelete={() => {handleDelete(card)}}
disabled={card.loading}
/>
))
: (
<Banner
iconName="credit-card"
text="No payment method"
/>
)}
</ScrollView>
<View>
<Button
title="Add"
onPress={() => navigation.navigate('Add')}
/>
</View>
</ScreenWrapper>
);
};
export default PaymentIndexScreen;
Действие удаления работает, за исключением изменения состояния загрузки.
Похоже, что изменение card.loading
свойства запускает какой-либо рендеринг.
Единственное решение, которое я вижу, это полностью заменить созданный массив (как я сделал для удаления) включенным обновленным элементом, но я нашел это очень тяжелым и мог бы (нет?) приводит к проблемам с производительностью.
Каков наилучший способ реализовать независимое состояние загрузки без перезаписи всего массива?
Спасибо!
Ответ №1:
Из вашего кода мне кажется, что подход к обработке состояния списка нуждается в исправлении при возникновении некоторых событий. Я считаю, что возможным решением было бы абстрагироваться от эксклюзивной функции, которая может обрабатывать состояние каждой строки.
Пример (Песочница):
const handleData = (item, newState) =>
setData((prevData) =>
prevData.map((prevItem) => {
if (item.index === prevItem.index) return { ...prevItem, ...newState };
return prevItem;
})
);
Комментарии:
1. Большое спасибо, песочница действительно помогла! Итак, нет другого выбора, кроме как переписать весь массив, понял.
2. Это простой подход к управлению состоянием. Существует другой подход к управлению состоянием с использованием Redux Immer ( github.com/immerjs/immer ), но я не думаю, что это будет полезно для вашего приложения прямо сейчас. Immer обрабатывает большую часть изменений дерева в состоянии под капотом.
Ответ №2:
React повторно отображает компонент при изменении его реквизитов или состояния. В вашем случае ни то, ни другое не меняется. Обратите внимание, что для того, чтобы react запускал изменения, состояние / реквизиты должны быть изменены неизменно.
В вашем случае вам нужно будет использовать setCreditCards
для изменения вашего массива в вашей handleDelete
функции.
setCreditCards(
creditCards.map(creditCard => {
if (creditCard._id === selectedCard._id) {
// return selectedCard with loading set to true
return {
...selectedCard,
loading: true,
};
// Return the card
}
return creditCard;
}),
);
Обычно это не вызывает проблем с производительностью, если только ваш список не действительно большой, а временная сложность равна O (n). В этом случае вы, вероятно, в любом случае рассмотрите отложенную загрузку или загрузку частичных данных.