Реальная проблема с производительностью

Я использую совпадение 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

  1. Вы никогда не должны выполнять вызовы API в методе жизненного цикла компонента React componentWillMount(), вместо этого это должно быть сделано в componentDidMount().

    Ознакомьтесь с этой очень аккуратной статьей о методах жизненного цикла и о том, что нужно сделать, в каком методе: https://medium.com/@baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d.

Многим будет предложено использовать эту функцию, чтобы отправить запрос на получение данных и ожидать, что данные будут доступны до того, как исходный рендер будет готов. Это не так - пока запрос будет инициализирован перед рендером, он не сможет закончить до вызова рендера.

  1. Вместо создания сокета для обновления coinData вам может понадобиться использовать методы subscibe/unsubscribe redux.