一、描述

rax-multirow 是 rax 的一个内置组件,主要是在移动端进行多列布局使用的。

rax 的文档地址:

文档中说,如果想减少嵌套,建议使用这种布局方式,但是这是相对的,因为本质上,rax-multirow 是基于 rax-grid 实现的,而 rax-grid 本质渲染的还是 <View>,所以我觉得,如果自己布局足够精简,同样没问题。文档强调的,其实是如果你不能精简的自己进行布局和样式,使用 <Multirow> 更方便。

文档可以强调了,rax-multirow 不适合作为长列表的容器:

长列表需求不要使用一个大的 MultiRow 组件进行统一布局,没有一个完整的大标签包裹性能会更好。

安装和引用:

安装

$ npm install rax-multirow --save

引用

import MultiRow from 'rax-multirow';

二、源码

上面提到了,rax-multirow 实际上是基于 rax-grid 实现的,而且我觉得很别扭的一点就是,这个组件的类名竟然是 List

能够接收的 props 如下,然而我在使用的时候,往往很少用到 rowStyle,而像 colStyle 往往在需要减少嵌套的时候使用,如果不使用可以,直接在 renderCell 中渲染的子组件中指定样式即可,无非多一个嵌套。

List.defaultProps = {
  colStyle: {},
  rowStyle: {},
  cells: 1,
  dataSource: [],
  renderCell: () => {}
};

rax-multirow 的核心方法是 getContent(),这个方法负责所有的内容输出:

  getContent() {
    let props = this.props,
      list = props.dataSource,
      count = props.cells,
      renderCell = props.renderCell;

    let grids = [];

    let gridDataArr = [];
    for (let i = 0; i < list.length; i++) {
      let index = Math.floor(i / count);
      if (i % count == 0) {
        gridDataArr[index] = [];
      }
      gridDataArr[index].push(<Col style={props.colStyle}>{renderCell(list[i], i)}</Col>);
      if (i % count == 0 && i != 0) {
        grids.push(<Row style={props.rowStyle}>{gridDataArr[index - 1]}</Row>);
      }
      if (i == list.length - 1) {
        grids.push(<Row style={props.rowStyle}>{gridDataArr[index]}</Row>);
      }
    };
    return <View>{grids}</View>;
  }

主要的处理过程,其实就是进行了一次循环,而通过 cells 进行的模运算能够控制每列渲染多少。

gridDataArr 是用来存放 <Col> 的数组,grids 是存放 <Row> 的数组,每次先进行 gridDataArrpush 操作,当满足模运算之后,将 <Col> 数组存放在一个 <Row> 中,最后循环结束后,进行 gridspush 操作。

三、组件应用

1、jsx

import {Component,createElement} from 'rax';
import MultiRow from 'rax-multirow';
import View from 'rax-view';
import Text from 'rax-text';

import styles from './index.css';

class TestMultiRow extends Component{

  state = {
      list:['A','B','C','D']    
  }
  renderHandle(item, index) {
    return (
     <View style={styles.item}>
        <Text style={styles.text}>{index} - {item}</Text>
     </View>
    );
  }
  render(){
    return (
      <View style={styles.app}>
        <MultiRow 
          dataSource={this.state.list}
          cells={3}
          renderCell={this.renderHandle}
        ></MultiRow>
      </View>
    );
  }

}

export default TestMultiRow;

2、css

.app{
  width: 750;
}

.item{
  align-items: center;
  justify-content: center;
  background-color: #000;
  height:200;
  border-color:#fff;
  border-width:1;
  border-style: solid;
}
.text{
  color:#fff;
}

3、效果

4.jpg

4、问题及解决

上面的效果显示了一个问题,那就是当最后一行数据的个数不足 cells 的时候,最后会占满一整行,上面的图片显示的是最后一行只有一个,如果最后一行有两个,会变成下面的就样式。

5.png

正常来说,我们期望的是下面的结果,也就是数据列表刚好满足 cells,最后一行也满足 cells 的个数。

6.jpg

那么当数据不足的时候,就需要手动去补全空数据,才能做到这种效果。

比如下面的方法,再通过模计算,可以得到最后一行的数据 和 cells 差多少,从而进行补全。

  getRenderList = () => {
    const {cells,list} =this.state; 
    const data = [];
    const d = cells - list.length % cells;
    list.forEach((item) => {
      data.push(item); 
    });
    for(let i=0;i<d;i++){
      data.push("");
    }
    return data;
  }

使用上面的方法返回的数据代替 dataSource={this.state.list} 传递的数据列表即可。

效果:

9.png

5、问题解决的优化

上面的解决方法,只需要一个 getRenderList 即可,其他的不需要变化。

不过上面虽然解决了最后一行的渲染问题,但是 Col 的样式也会起作用,而我们希望的,可能是不需要多余的数据渲染出样式,最好是能够隐藏掉的那种,因此上面的方式需要进行优化。

最终的效果如下:

aa.jpg

基本的思想是通过一个 EMPTY_DATA_FLAG 来标识空白数据,之后在 renderCell(item, index) 的时候,判断数据是否是 EMPTY_DATA_FLAG 来进行样式控制。

比如下面的方法:已经存在全局静态变量 const EMPTY_DATA_FLAG = 'EMPTY_DATA_FLAG';

  renderHandle(item, index) {
    return item === EMPTY_DATA_FLAG ?
        <View />
        :
        <View style={styles.item}>
          <Text style={styles.text}>{index} - {item}</Text>
        </View>
  }

最终,完整的组件使用代码如下

jsx 代码:

import {Component,createElement} from 'rax';
import MultiRow from 'rax-multirow';
import View from 'rax-view';
import Text from 'rax-text';

import styles from './index.css';

const EMPTY_DATA_FLAG = 'EMPTY_DATA_FLAG';

class TestMultiRow extends Component{
  componentWillMount(){
    this.getRenderList();
  }
  state = {
      list:['A','B','C','D'],
      cells:3    
  }
  getRenderList = () => {
    const {cells,list} =this.state; 
    const data = [];
    const d = cells - list.length % cells;
    list.forEach((item) => {
      data.push(item); 
    });
    for(let i=0;i<d;i++){
      data.push(EMPTY_DATA_FLAG);
    }
    return data;
  }
  renderHandle(item, index) {
    return item === EMPTY_DATA_FLAG ?
        <View />
        :
        <View style={styles.item}>
          <Text style={styles.text}>{index} - {item}</Text>
        </View>
  }
  render(){
    return (
      <View style={styles.app}>
        <MultiRow 
          dataSource={this.getRenderList()}
          cells={this.state.cells}
          renderCell={this.renderHandle}
        ></MultiRow>
      </View>
    );
  }

}

export default TestMultiRow;

css 代码:

.app{
  width: 750;
}

.item{
  align-items: center;
  justify-content: center;
  background-color: #000;
  height:200;
  border-color:#fff;
  border-width:1;
  border-style: solid;
}
.text{
  color:#fff;
}