Реальная проблема с производительностью
Я использую совпадение api, чтобы сначала извлечь данные о 1500+ криптовой валюте, а затем Web-socket для обновления обновленного значения криптовалюты.
Я использую redux для управления своим состоянием здесь
Inside My componentDidMount()
, я называю перевождь действие fetchCoin
, который извлекает значение монеты
componentDidMount() {
this.props.fetchCoin()
}
И затем return
я делаю что-то вроде этого
<FlatList
data={this.state.searchCoin ? displaySearchCrypto : this.props.cryptoLoaded}
renderItem={({ item }) => (
<CoinCard
key={item["short"]}
coinShortName = {item["short"]}
coinName = {item["long"]}
coinPrice = {item["price"].toFixed(2)}
percentChange = {item["perc"].toFixed(2)}
/>
Тогда у меня есть веб-сокет, который обновляет значение криптовалюта, как это
componentDidUpdate() {
if (this.state.updateCoinData || this.updateCoinData.length < 1 ) {
this.updateCoinData = [...this.props.cryptoLoaded];
this.setState({updateCoinData: true})
}
this.socket.on('trades', (tradeMsg) => {
for (let i=0; i< this.updateCoinData.length; i++) {
if (this.updateCoinData[i]["short"] == tradeMsg.coin ) {
//Search for changed Crypto Value
this.updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
this.updateCoinData[i]["price"] = tradeMsg['message']['msg']['price']
//Update the crypto Value state in Redux
this.props.updateCrypto(this.updateCoinData);
}
}
})
}
Теперь, несмотря на эту проблему, проблема в том, что это замедляет мое приложение, как ад, когда всякий раз, когда сокет отправляет новые данные, он должен отображать каждый компонент и, следовательно, события, такие как touch и search, требуют много времени для выполнения. [Update] Оказывается, мое приложение ничего не делает. Если я удалю подключение сокетов, проверьте обновление 2
[Вопрос:] Что мне делать, чтобы улучшить производительность приложения? (Что-то вроде не использования состояния или использования DOM для обновления моего приложения и т.д.).
[Обновление 1:] Я использую https://github.com/irohitb/Crypto. Эти два файла - js, где происходит вся логика. Https://github.com/irohitb/Crypto/blob/master/src/container/cryptoContainer.js https://github.com/irohitb/Crypto/blob/master/src/components/CoinCard.js Я также перехожу из карты в Flatlist.
[Update: 2] Я обнаружил, что внутри моего приложения происходит бесконечная визуализация, которая, вероятно, удерживает мой поток занятым (я имею в виду, что он бесконечен и излишне пропускает реквизит). Я задал один и тот же вопрос в отдельном qaru.site/info/16304129/... но не получил надлежащего ответа, и поскольку он связан с производительностью, я подумал о том, чтобы наложить на него щедрость.
Пожалуйста, проверьте эту тему: бесконечный Render in React
[Ответ Обновление:] В то время как есть много больших ответов здесь, на всякий случай, если кто хочет понять, как это работает, Вы могли бы клонировать мой репозиторий и вернуться до этого совершить. Я связал фиксацию с тем моментом, когда мои проблемы были решены (так что вам, возможно, потребуется вернуться и посмотреть, что я делаю неправильно). Кроме того, все ответы были очень полезны и не трудно понять, поэтому вам обязательно нужно пройти через них.
Ответы
Ответ 1
Как сказал Bhojendra Rauniyar, вы должны использовать shouldComponentUpdate в CoinCard. Вероятно, вы также захотите изменить свой FlatList, ваш уменьшенный образец имеет FlatList в ScrollView, это приводит к тому, что FlatList будет полностью расширяться, тем самым одновременно отображая все элементы.
class cryptoTicker extends PureComponent {
componentDidMount() {
this.socket = openSocket('https://coincap.io');
this.props.fetchCoin()
this.props.CurrencyRate()
this.socket.on('trades', (tradeMsg) => {
for (let i=0; i< this.updateCoinData.length; i++) {
if (this.updateCoinData[i]["short"] == tradeMsg.coin ) {
//Search for changed Crypto Value
this.updateCoinData["short"] = tradeMsg["message"]["msg"]["short"]
this.updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
this.updateCoinData[i]["price"] = tradeMsg["message"]['msg']['price']
//Update the crypto Value state in Redux
this.props.updateCrypto(this.updateCoinData);
}
}
})
}
componentWillReceiveProps(newProps){
// Fill with redux data once
if (this.updateCoinData.length < 1 && newProps.cryptoLoaded) {
this.updateCoinData = [...newProps.cryptoLoaded];
}
}
render() {
return (
<View style={{height: '100%'}}>
<Header/>
<FlatList
style={{flex:1}}
data={this.props.cryptoLoaded}
keyExtractor={item => item.short}
initialNumToRender={50}
windowSize={21}
removeClippedSubviews={true}
renderItem={({item, index}) => (
<CoinCard
index={index}
{...item}
/>
)}
/>
</View>
)
}
}
class CoinCard extends Component {
shouldComponentUpdate(nextProps) {
return this.props.price !== nextProps.price || this.props.perc !== nextProps.perc
}
render() {
console.log("here: " + this.props.index);
return (
<View>
<Text> {this.props.index} = {this.props.long} </Text>
</View>
)
}
}
Ответ 2
Каждый раз, когда ваш компонент обновляет, он запускает новый сокет, который приводит к утечке памяти и вызывает this.props.updateCrypto(updateCoinData);
которые будут вызываться несколько раз для одних и тех же данных. Это можно устранить, открыв сокет в componentDidMount()
и закрыв его в componentWillUnmount()
.
Вы также можете буферизовать несколько обновлений записей и менять данные FlatList за один раз каждые две секунды.
Редактировать рабочий пример (App.js):
import React, { Component } from 'react';
import { Text, View, FlatList } from 'react-native';
import SocketIOClient from 'socket.io-client';
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.currencies = {};
this.state = {
currenciesList: [],
}
}
componentDidMount() {
this.socket = SocketIOClient('https://coincap.io');
this.socket.on('trades', (tradeMsg) => {
const time = new Date();
// Store updates to currencies in an object
this.currencies[tradeMsg.message.msg.short] = {
...tradeMsg.message.msg,
time: time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds(),
};
// Create a new array from all currencies
this.setState({currenciesList: Object.values(this.currencies)})
});
}
componentWillUnmount() {
this.socket.disconnect();
}
render() {
return (
<FlatList
data={this.state.currenciesList}
extraData={this.state.currenciesList}
keyExtractor={(item) => item.short}
renderItem={({item}) => <View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Text style={{flex: 1}}>{item.time}</Text>
<Text style={{flex: 1}}>{item.short}</Text>
<Text style={{flex: 1}}>{item.perc}</Text>
<Text style={{flex: 1}}>{item.price}</Text>
</View>}
/>
);
}
}
Ответ 3
Существует множество стандартных способов повышения эффективности реагирования приложений, наиболее распространенных:
- использовать обычную оптимизацию реакции (shouldComponentUpdate, PureComponent - читать документы)
- использовать виртуальные списки (ограничить видимые части данных)
В этом случае я бы добавил:
Не обрабатывайте данные перед оптимизацией - данные форматирования, которые не были изменены, по крайней мере, не нужны. Вы можете вставить промежуточный компонент (слой оптимизации), который будет передавать/обновлять форматированные данные в <CoinCard/>
только при изменении исходных данных.
Вам может вообще не понадобиться Redux (хранить данные в состоянии), когда данные используются в одном месте/в простой структуре. Конечно, вы можете использовать redux для других общедоступных состояний приложения (параметры фильтрации).
Используйте <FlatList/>
(реагировать-родной), ищите более подходящую?
ОБНОВИТЬ
Некоторый код был изменен в среднем времени (репо), в это время (08.09) один вопрос все еще существует и, вероятно, вызывает утечку памяти.
Вы вызываете this.socket.on
на каждом вызове componentDidUpdate
(неправильно закодированные условия) - постоянно добавляете новый обработчик!
componentDidUpdate() {
// call all ONLY ONCE afer initial data loading
if (!this.state.updateCoinData && !this.props.cryptoLoaded.length) {
this.setState({updateCoinData: true}) // block condition
this.socket.on('trades', (tradeMsg) => {
// slice() is faster, new array instance
// let updateCoinData = [...this.props.cryptoLoaded];
let updateCoinData = this.props.cryptoLoaded.slice();
for (let i=0; i<updateCoinData.length; i++) {
//Search for changed Crypto Value
if (updateCoinData[i]["short"] == tradeMsg.coin ) {
// found, updating from message
updateCoinData[i]["long"] = tradeMsg["message"]["msg"]["long"]
updateCoinData[i]["short"] = tradeMsg["message"]["msg"]["short"]
updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
updateCoinData[i]["mktcap"] = tradeMsg['message']['msg']["mktcap"]
updateCoinData[i]["price"] = tradeMsg['message']['msg']['price']
//Update the crypto Value state in Redux
this.props.updateCrypto(updateCoinData);
// record found and updated, no more looping needed
break;
}
}
})
}
}
Незначительные ошибки: начальные состояния выборки заданы как true в редукторах.
В поисках проблем с производительностью я бы посмотрел на <CoinCard/>
:
- сделать его PureComponent;
-
increased
и decreased
не требуются для сохранения в состоянии, которое вынуждает ненужные вызовы; - Я бы использовал время обновления (не сохранено в состоянии, просто передано как prop в родительском и только для обновленных строк, в
updateCoinData
в коде выше) и выводит направление (только для проверки 0 и только знак) разности (уже рассчитанной в perc
) только для видимые объекты (от рендера) и только в течение срока (разница между временем рендеринга и поддержкой обновления данных). setTimeout
можно использовать. - наконец, удаление
componentWillReceiveProps
, componentDidUpdate
и shouldComponentUpdate
должно (сильно?) повысить производительность;
Ответ 4
При рендеринге Flatlist вы должны рассмотреть возможность использования PureComponent
или использование shouldComponentUpdate
для обновления, только если это необходимо.
Из документа:
Если ваше приложение отображает длинные списки данных (сотни или тысячи строк), мы рекомендуем использовать метод, известный как "оконный". Этот метод только отображает небольшое подмножество ваших строк в любой момент времени и может значительно сократить время, необходимое для повторной обработки компонентов, а также количества созданных узлов DOM.
Взять глубокий путь к этому руководству по производительности.
Если вам по-прежнему нужен какой-то расширенный интерфейс, я рекомендую вам изучить следующие темы:
FlatList и VirtualizedList Производительность прокрутки отстает после строк 30+
Проблемы с производительностью при реагировании при использовании большого списка
Ответ 5
-
Вы никогда не должны выполнять вызовы API в методе жизненного цикла компонента React componentWillMount(), вместо этого это должно быть сделано в componentDidMount().
Ознакомьтесь с этой очень аккуратной статьей о методах жизненного цикла и о том, что нужно сделать, в каком методе: https://medium.com/@baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d.
Многим будет предложено использовать эту функцию, чтобы отправить запрос на получение данных и ожидать, что данные будут доступны до того, как исходный рендер будет готов. Это не так - пока запрос будет инициализирован перед рендером, он не сможет закончить до вызова рендера.
- Вместо создания сокета для обновления coinData вам может понадобиться использовать методы subscibe/unsubscribe redux.