写RN也有段时间了,今天抽空就对RN当中的ListView进行一个简单介绍,主要介绍使用当中的一些坑。
写一个简单的ListView
class MyComponent extends Component {
constructor() {
super();
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows(['row 1', 'row 2']),
};
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
/>
);
}
}
以上代码就可以绘制一个简单的ListView了,但是我们需要注意dataSource: ds.cloneWithRows(['row 1', 'row 2'])
这一行,因为我们的数据一般都是网络请求来的,所以我们一般可能会写成这样
listData = [];
constructor() {
super();
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows(this.listData),
};
}
***省略的网络请求***
listData = ['row 1', 'row 2'];
***省略的更新state***
然而你发现你的ListView并没有更新,还是一片空白,当我们把dataSource: ds.cloneWithRows(this.listData)
这一行改成dataSource: ds.cloneWithRows(this.listData.slice())
你会发现问题解决了,why?
这是因为这样只是一个浅拷贝,虽然数组内容发生了变化,但是ds.cloneWithRows
接收到的数据没有发生变,故页面会刷新。当我们用slice
时,给它返回一个新值,这样界面就会重新刷新渲染。
写一个带Section的ListView
要写一个带Section的ListView主要是针对数据源的改造。
DataSource构造函数可以接受下列四种参数(都是可选):
getRowData(dataBlob, sectionID, rowID);
getSectionHeaderData(dataBlob, sectionID);
rowHasChanged(prevRowData, nextRowData);
sectionHeaderHasChanged(prevSectionData, nextSectionData);
如果不提供getRowData和getSectionHeaderData方法,使用defaultGetRowData和defaultGetSectionHeaderData来提取数据,默认的提取函数可以处理的数据结构如下:
{ sectionID_1: { rowID_1: rowData1, ... }, ... }
或者
{ sectionID_1: [ rowData1, rowData2, ... ], ... }
或者
[ [ rowData1, rowData2, ... ], ... ]
我们用第二种数据结构来实现:
class MyComponent extends Component {
constructor() {
super();
listData = { "1": [ "11", "12"], "2": [ "21", "22"] };
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
sectionHeaderHasChanged: (s1, s2) => s1 != s2
});
this.state = {
dataSource: ds.cloneWithRowsAndSections(
this.listData,
Object.keys(this.listData),
Object.keys(this.listData).map(
(sectionId) => Object.keys(
this.listData[sectionId].slice()
)
)
),
};
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
/>
);
}
}
需要注意的是cloneWithRowsAndSections(dataBlob, sectionIdentities, rowIdentities)
方法当中的参数也需要是一个新值,所以rowIdentities
参数的数据也需要slice()
一下,回传一个新值。
当需要将sectionHeader的样式显示出来,还需要在render当中添加关于header渲染的代码
render() {
return (
<ListView
renderSectionHeader={
(sectionData, sectionID) => <Text>{sectionID}</Text>
}
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
/>
);
}
这样一个带section的ListView就写出来了。
一些注意事项
- React Native不支持随ListView滚动的Header,如果需要,请在每个section当中的第一个Item当中添加
- 如果ListView要下拉一下才显示出来,请设置ListView的属性
removeClippedSubviews={false}
- 如果在iOS设备上ListView距离顶部导航栏有距离,请设置ListView的属性
automaticallyAdjustContentInsets={false}