【问题标题】:React native performance issue反应本机性能问题
【发布时间】:2018-08-25 20:06:56
【问题描述】:

我正在使用 coincap api 首先获取大约 1500+ 加密货币的数据,然后使用 Web-socket 更新加密货币的更新值。

我在这里使用 redux 来管理我的状态

在我的componentDidMount() 中,我正在调用 redux 操作 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);

          }
        }
      })
  }

现在,虽然这项工作有效,但问题是这会像地狱一样减慢我的应用程序,因为每当套接字发送新数据时,它必须渲染每个组件,因此触摸和搜索等事件需要大量时间来执行。 [更新] 事实证明,即使我删除了套接字连接,我的应用仍在渲染某些内容,请查看更新 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 我也从地图移动到平面列表。

[更新:2] 我发现在我的应用程序中发生了无休止的渲染,这可能使我的线程一直很忙(我的意思是无休止的&不必要地传递道具)。我在单独的Stackoverflow thread 上问了同样的问题,但没有得到适当的答复,而且由于它与性能有关,我想在这里放一个赏金。

请查看此帖子:infinite Render in React

[答案更新:] 虽然这里有很多很好的答案,但以防万一有人想了解它是如何工作的,您可能可以克隆我的存储库并返回 之前强>这个commit。我已经将提交与我的问题得到解决的地方联系起来(所以你可能需要回去看看我做错了什么)。此外,所有答案都非常有用且不难理解,因此您绝对应该阅读它们。

【问题讨论】:

  • 有打开和关闭套接字的原因吗?您是否有机会在服务器端 API 中进行更改?
  • @BilboBaggins 不! Coincap 提供套接字连接,所以我们无法更改。打开套接字是有原因的,但是这样想,我可以消除断开连接的方法。
  • 他在componentDidUpdate 中写了关于打开套接字的文章 - 问题已经在答案、cmets 和 repo 中解决(@KuchBhi 更新问题) - 但是你仍然在那里添加一个新的处理程序:P - 请参阅我的更新
  • 因为我认为每次打开套接字都是开销,如果您的应用程序失去连接等,您必须这样做。此外,我认为是这样,如果只有那些自最后一次调用,如果我只能获得那些,那么我只能更新那些渲染它的组件(不是反应专家),而不是整个列表。有没有办法这样做?只是在这里大声思考。
  • 你知道我刚刚读了答案 :) 我的错 .. :P

标签: reactjs react-native


【解决方案1】:

每次您的组件更新时,它都会启动一个新的套接字,这会导致内存泄漏,并会导致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>}
            />
        );
    }
}

【讨论】:

  • 一旦来自coincap.io/front的数据需要更新套接字数据因此我在componentDidUpdate中调用它并且由于套接字每秒都在发送数据因此我决定获取componentDidUpdate(这里不是专家,所以我可能在这里遗漏了什么?)
  • 只需要打开一次socket。然后,每次服务器发送新数据(大约每秒)时,都会自动调用 socket.on('trades', (tradeMsg) =&gt; {
  • basebase,知道了,但是你怎么称呼它呢?因为它是一个回调。您能否在您的示例中也包含演示?
  • 包含一个例子:在包含FlatList的组件中打开socket,在socket回调中更新状态
  • @basebase,我做了类似github.com/irohitb/Crypto/blob/master/src/container/… 的操作,而不是完全复制您的代码。在此,componentDidMount() { 这是一个空对象 var updateCoinData = [...this.props.cryptoLoaded]; 你知道为什么会发生这种情况吗?我这里需要使用redux(我猜这不是因为redux)。
【解决方案2】:

有许多标准方法可以提高 React 应用程序的性能,其中最常见的是:

  • 使用通常的反应优化(shouldComponentUpdate、PureComponent - 阅读文档)
  • 使用虚拟列表(限制数据的可见部分)

在这种情况下,我会添加:

在优化之前不要处理数据 - f.e.至少不需要格式化未更改的数据。您可以插入中间组件(优化层),仅在“原始数据”更改时将格式化数据传递/更新到&lt;CoinCard /&gt;

You might not need Redux at all(将数据存储在状态中)当数据在一个地方/简单结构中使用时。当然,您可以将 redux 用于其他全局共享应用状态(例如过滤选项)。

&lt;FlatList /&gt;(react-native),找找更合适的?

更新

部分代码在同一时间更改(repo),目前(08.09)仍然存在一个问题,可能导致内存泄漏。

您在每个 componentDidUpdate 调用(错误编码的条件)上调用 this.socket.on - 不断添加新的处理程序!

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;
        }
      }
    })
  }
}

小错误:在 reducer 中初始获取状态设置为 true。

搜索性能问题我会看&lt;CoinCard /&gt;

  • 使其成为 PureComponent;
  • increaseddecreased 不需要保存在强制进行不必要的渲染调用的状态;
  • 我会使用更新时间(不保存在状态中,只是在父项中作为 prop 传递并且仅用于更新的行,在上面代码中的 updateCoinData 内)并导出方向(仅检查 0 和符号)(已在 perc 中计算)仅适用于可见项目(来自渲染)和仅在时间限制内(渲染时间和数据更新道具之间的差异)。 setTimeout 也可以使用。
  • 最终删除 componentWillReceivePropscomponentDidUpdateshouldComponentUpdate 应该(高度?)提高性能;

【讨论】:

  • 1. 你所说的“优化前不要处理数据”是什么意思,你能详细解释一下吗?举个例子? 2。我需要 redux,因为我打算在其他地方以及我的应用程序中使用它。 3 感谢您提及&lt;FlatList /&gt;
  • [更新答案]:-> 1 this.socket.on('trades', (tradeMsg) =&gt; { 有一个回调,我应该在哪里打电话? 性能问题我会看&lt;CoinCard /&gt;,我无法理解第二部分/点。能否请您也提供示例?
  • 一些语句与早期代码相关(例如,发布 repo 链接或代码更新之前的 redux)。格式化 1000 条记录的数据与仅格式化可见记录(来自传递的原始数据) - 在 renderItem 中执行此操作可解决此问题。 socket.on(检查触发了多少次)已经在我上面的代码中修复(componentDidUpdate 中的适当条件)。 &lt;CoinCard /&gt; 有不必要的状态更改(强制额外的 render),而没有状态(render 中的条件)很简单。
【解决方案3】:

就像 Bhojendra Rauniyar 说的,你应该在 CoinCard 中使用 shouldComponentUpdate。您可能还想更改您的 FlatList,您缩小的示例在 ScrollView 中有 FlatList,这会导致 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>
        )
    }
}

【讨论】:

  • 我这样做了,但仍然一次又一次地无休止地重新渲染一些东西。你可以检查这个 -> github.com/irohitb/Crypto
  • 为您创建了一个拉取请求。修复: - 在 componentDidMount() 中仅设置单个套接字回调,就像我在之前的回答中所说 - 使用 componentWillReceiveProps() 用 redux 数据填充 this.updateCoinData 一次
  • 非常感谢您的回答。您的更新有效,因此我对其进行了市场回答,但我仍然很好奇,因为当您说 在 componentDidMount() 中仅设置一个套接字回调您将生命周期从 componentDidUpdate 更改为 componentDidMount() 那么怎么办你是这个意思吗?另外,请相应地更新您在此帖子上的答案
  • componentDidUpdate 每次 redux 更新 props 时都会被调用。如果你在那里设置this.socket.on('trades', (tradeMsg) =&gt; {}),它每次都会添加一个新的回调。然后当有新消息到达时,它会被每个先前分配的回调处理,每个回调都会通过 redux 存储触发新的更新。因此,您只想在第一次加载视图时设置一次回调。
【解决方案4】:

在渲染 Flatlist 时,您应该考虑使用 PureComponent 或仅在需要时使用 shouldComponentUpdate 挂钩来更新。

来自doc

如果您的应用程序呈现长数据列表(数百或数千行),我们建议使用称为“窗口化”的技术。这种技术在任何给定时间仅渲染一小部分行,并且可以显着减少重新渲染组件所需的时间以及创建的 DOM 节点的数量。

深入了解这个performance guide

如果您仍想深入了解一下,那么我建议您查看以下线程:

FlatList and VirtualizedList Scroll performance is laggy after 30+ rows

Performance problems with react when using a big list

【讨论】:

  • 首先,非常感谢您的回答。我已经实现了 PureComponent,我现在正在阅读和实现 windowing :) 我现在主要的好奇心/担心是找到导致不必要渲染的原因。你能*请*浏览这个帖子吗[这个帖子的更新 2]stackoverflow.com/questions/52213596/infinite-render-in-react
  • CoinCard 应该实现 PureComponent 或使用 shouldComponentUpdate。
  • 在我的硬币卡里面我做了这样的事情shouldComponentUpdate(nextProps, nextState) { if (this.props == nextProps || this.state == nextState) { console.log("inside should compoenent not to update") return false; } else { console.log("Component Would update") return true; } } 现在我的控制台充满了无尽的console.log("inside should compoenent not to update")
  • 没有有趣的话题,所以我的帖子中没有链接。
  • 做!感谢您的帮助。
【解决方案5】:
  1. 你不应该在 React 的 componentWillMount() 生命周期方法中进行 API 调用,而应该在 componentDidMount() 中进行。

    查看这篇关于生命周期方法以及应该在哪种方法中完成的非常简洁的文章: https://medium.com/@baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d

许多人会尝试使用此函数来发送获取数据的请求,并期望数据在初始渲染准备好之前可用。情况并非如此 — 虽然请求将在渲染之前初始化,但在调用渲染之前它无法完成。

  1. 您可能希望使用 redux 订阅/取消订阅方法,而不是创建套接字来更新 coinData。

【讨论】:

  • ComponentDidMount 也触发了无限触发,但我阅读了同一篇文章并将其更改为 componentDidMount。您能否包括 redux 订阅/取消订阅方法的示例?另外,如果您也可以查看此线程,我将不胜感激? stackoverflow.com/questions/52213596/infinite-render-in-react(我在当前帖子的update 2中提到了这一点)
  • 我已经检查了另一个问题,将克隆 repo 并查看发生了什么。同时,您可以查看 Dan Abramov 编写的关于订阅的简短教程 - egghead.io/lessons/…
猜你喜欢
  • 2018-04-03
  • 2020-03-20
  • 2016-11-10
  • 2019-10-10
  • 1970-01-01
  • 2022-01-04
  • 2020-02-04
  • 2017-11-19
  • 2021-03-15
相关资源
最近更新 更多