浅析Listview组件02之刷新数据

前言:最简demo做出来了,开始增加玩法。首先是刷新数据,一般情况下,这种列表型的内容,都是会变化的。有些是因为用户的筛选或者其他操作,导致列表发生了变化。有些是因为数据本身就是从网络服务器获取的,当服务器发生了变化,这边获取的具体数据也会发生变化,从而需要让内容也发生变化。那么,怎么让Listview的界面因为数据的变化而变化呢?

修改dataSource刷新界面

继续先看个demo:demo v2地址
数据改变后刷新界面其实很简单,在React-native中,凡是要涉及到刷新界面的,一般都会和state打交道(另外还有个是父组件传props)。我们的最简demo中,一开始就设置了一个叫做dataSource的state,使用setState修改这个dataSource,我们的界面就会发生变化。比如这样:

1
2
3
4
5
6
7
changeds(){
console.log('ds data changing...');
data2 =[1,2,3];
this.setState(
{dataSource:ds.cloneWithRows(data2)}
);
}

我创建了一个按钮,给了个方法changeds,在这个方法中,先给了个新数组data2,然后调用setSate方法对dataSource重新赋值。此时界面就会刷新,这里重新赋值的方法是

1
ds.cloneWithRows(data2)

如果你把这个方法修改为:

1
this.state.dataSource.cloneWithRows(data2)

也是有效的,见demo v3地址。第二个按钮就是用的这个方法,界面效果看起来貌似没有任何区别。那你肯定要问,这两个真的没区别吗?哪个才是最好的?要解释这个,就需要扯到之前没扯到的rowHasChanged方法了。

rowHasChanged方法

先把代码贴出来:

1
2
3
4
5
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) =>{
return r1 !== r2
}
});

相信大家刚接触这个ListView的时候,都会对这个rowHasChanged方法有很大的疑惑。这个方法有啥用?里面一定要这么写吗?为了知道它的秘密,我给这个方法加了点东西。

1
2
3
4
5
6
7
8
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) =>{
if(r1 !== r2){
console.log('rowHasChanged!')
}else {
console.log('rowHasNotChanged')}
return r1 !== r2
}});

就是在之前的基础上,添加了一个判断输出log语句,看它啥时候会调用,调用时会输出什么。这些内容在我的demov3中已经添加好了,同时还给按钮和renderRow方法也写了log语句,测试结果是这样的:
首先按下第一个按钮,界面由1000,2000,3000变成了1,2,3 log语句输出为:

1
2
3
4
ds data changing...
renderRow is 1
renderRow is 2
renderRow is 3

然后重新reload这个app,按下第二个按钮,输出为:

1
2
3
4
5
6
7
state ds data changing...
rowHasChanged!
rowHasChanged!
rowHasChanged!
renderRow is 1
renderRow is 2
renderRow is 3

再次reload这个app,先按第一个按钮,再按第二个按钮,输出为:

1
2
3
4
5
6
7
8
ds data changing...
renderRow is 1
renderRow is 2
renderRow is 3
state ds data changing...
rowHasNotChanged
rowHasNotChanged
rowHasNotChanged

从测试结果我们看出,调用ds.cloneWithRows(data2)给state赋值(第一个按钮)并刷新界面,并不会触发rowHasChanged方法。它只有在调用this.state.dataSource.cloneWithRows(data2)给state赋值(第二个按钮)刷新时,才会被触发。而且,这个触发会根据data数据的变化与否返回不同的值,同时影响是否重新渲染每行的组件。你看我们的第三次测试,按第一个按钮用data2的值刷新页面后,再按第二个按钮,rowHasChanged返回了和第二次测试不一样的值,同时组件并没有被刷新。
我们都知道,ListView是一个消耗性能的大户,因为他需要同时刷新n个组件。如果能够在更新数据的时候,先比较一下新旧数据的不同,只刷新改变的那一行组件,可以极大的降低因刷新更多组件带来的性能消耗。通过上面的测试我们知道这个rowHasChanged方法就是用来判断新旧数据的异同的,就是用来优化Listview性能的。所以我们使用ListView最好使用

1
this.state.dataSource.cloneWithRows(data2)

来刷新state。这里我只是用比较简单的测试来给大家讲了为什么要用rowHasChanged,那个r1和r2就代表不同时期的同一行的数据,两者相等就代表不用刷新,返回false,两者不等就返回true,让本行组件刷新。至于再细节一点,有兴趣的朋友还是去翻翻源代码吧。

this.state.dataSource.cloneWithRows()的坑

我们刚知道ListView刷新界面的正确方式,就是使用this.state.dataSource.cloneWithRows(data)方法来更新数据,但这个方法有个坑。使用不慎会出大问题,我给大家演示一下。
刚才我更新数据是用的完全不同的两个data数组,我现在不这么干,我就直接修改第一个data的内容,然后用这个data来更新界面,见demo v4链接
这个demo在v3的基础上又加了一个按钮,按钮把data的第一个数给改了,然后setState刷新。

1
2
3
4
5
6
7
changeds3(){
console.log('state ds data changing problem...');
data[0] = 1 ;
this.setState(
{dataSource:this.state.dataSource.cloneWithRows(data)}
);
}

你会发现虽然按钮上方显示data本身的已经变成 1 2000 3000 了。但最上面我们展示ListView的地方完全没变化。这个坑就在这里,使用this.state.dataSource.cloneWithRows(data)更新数据的时候,参数data不能是原有数组对象上修改,必须是一个新的数组。当然,如果你使用ds.cloneWithRows方法来刷新界面,其参数是可以用原数组对象的,但这样用又没法提升ListView的性能。

总结

  • 刷新数据界面,使用setSate方法更新dataSource数据源。
  • dataSource数据使用this.state.dataSource.cloneWithRows方法赋值
  • this.state.dataSource.cloneWithRows方法的参数必须是新的数组对象