React Native之如何写一个ListView

写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就写出来了。

一些注意事项

  1. React Native不支持随ListView滚动的Header,如果需要,请在每个section当中的第一个Item当中添加
  2. 如果ListView要下拉一下才显示出来,请设置ListView的属性removeClippedSubviews={false}
  3. 如果在iOS设备上ListView距离顶部导航栏有距离,请设置ListView的属性automaticallyAdjustContentInsets={false}