React Native 性能问题
我首先使用 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)}
/>
然后我有一个 web-socket 来像这样更新加密货币的值
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 我也从 map 移动到了 Flatlist。
[更新:2] 我发现我的应用程序内部 正在进行无休止的渲染 ,这可能使我的线程保持繁忙( 我的意思是它是无休止的并且不必要地传递道具 )。我在另一个 Stackoverflow 线程 上问了同样的问题,但没有收到适当的答复,由于它与性能有关,我考虑在这里悬赏。
请查看此线程: React 中的无限渲染
[答案更新:] 虽然这里有很多很棒的答案,但以防万一有人想了解它是如何工作的,您可以克隆我的存储库并返回到 之前 这个 提交 。我已将提交链接到解决问题的点(因此您可能需要返回并查看我做错了什么)。此外,所有答案都非常有用且不难理解,因此您一定要仔细阅读它们。
每次您的组件更新时,它都会启动一个新的套接字,这会导致内存泄漏,并会导致对同一数据多次调用
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>}
/>
);
}
}
有很多标准方法可以提高 React 应用的性能,最常见的是:
- 使用常用的 React 优化(shouldComponentUpdate、PureComponent - 阅读文档)
- 使用虚拟列表(限制数据的可见部分)
在这种情况下,我会补充:
优化之前不要处理数据
- 例如,格式化未更改的数据至少是不必要的。您可以插入中间组件(优化层),该组件仅在“原始数据”更改时将格式化的数据传递/更新到
<CoinCard />
。
当数据在一个地方/简单结构中使用时,您可能根本不需要 Redux (将数据存储在状态中)。当然,你可以将 redux 用于其他全局 共享 的应用程序状态(例如,过滤选项)。
使用
<FlatList />
(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。
为了查找性能问题,我会查看
<CoinCard />
:
- 使其成为 PureComponent;
-
increased
和decreased
不需要在强制进行不必要的渲染调用的状态下保存; -
我会使用更新时间(不保存在状态中,只是作为父级中的 prop 传递,并且仅适用于更新的行,在上面代码中的
updateCoinData
内)并得出差异的方向(仅检查 0 和符号)( 已在perc
中计算) 仅适用于可见项目 (来自渲染)和 仅在时间限制内 (渲染时间和数据更新 prop 之间的差异)。setTimeout
也可以使用。 -
最后删除
componentWillReceiveProps
、componentDidUpdate
和shouldComponentUpdate
应该(高度?)提高性能;
就像 Bhojendra Rauniyar 所说的那样,您应该在 CoinCard 中使用 shouldComponentUpdate。您可能还想更改您的 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>
)
}
}