一、描述

rax-picker 是 rax 内置的选择组件,可以理解为 select 只不过在 weex 环境和 web 容器环境是不一样的,如果降级的话,则会默认为 select 样式。

关键属性只有两个,分别是 selectedValueonValueChange ,分别表示默认选中值和值变化的事件处理。

Picker 组件(可以理解为 import Picker from 'rax-picker')实际上不仅仅只有 Picker,还有一个子组件为 Picker.Item,其实称其为子组件并不是完全的准确,Item 是作为 Picker 的属性进行模块导出的。

二、源码

rax-picker 的源码挺有意思,并且也有点长,有差不多 160 行,主要分析几个关键的方法。

首先,rax-picker 组件的基本使用代码如下,其他的分析将基于下面的代码进行:

<View style={{ width: 750 }}>
   <Text>选择性别</Text>
   <Picker 
      selectedValue={'女'}
      onValueChange={(index, item) => {
         console.log('Picker', index, item);
        }}>
        <Picker.Item value={'男'} label={'男'} />
        <Picker.Item value={'女'} label={'女'} />
    </Picker>
</View>

1、getPickerData()

getPickerData() 是很关键的一个方法, 因为这个方法实际上是将 Picker 的子组件也就是 Picker.Item 进行转化。

看这个方法的源代码很有意思,其实在使用的时候 Picker.Item 根本没有实际的作用,他只是作为一种规范,目的是为了传递规范的 label 和 value。

也就是说,即使我们不适用 Picker.Item ,仅仅使用 <View> 组件也可以实现,我将在下面的实践的代码中使用这种方式来验证一下。

这个方法的关键部分是下面这部分代码,也是证明我上面所说的话的正确性,其实就是把 this.props.children 存放在 pickeItems 中,然后进行 map 计算,输出我们需要的格式的列表,并且在这个过程中,计算默认选中的值是哪一个。

pickerLabelList = pickerItems.map((item, index) => {
  let {value, label, color} = item.props;
  items.push({
    value: value,
    label: label || value,
  });
  if (value == selectedValue) {
    selectIndex = index;
    selectedLabel = label;
  }
  return label;
});

因此,getPickerData() 方法最终输出了 selectIndex(选中的 index),selectedLabel (选选中值),pickerLabelList(label 列表),items (内容数组,结构为 {value,label})。

我觉得 selectIndex 这个名称应该是 selectedIndex,也就是应该和 selectedLabel 一样的规范,可能是组件开发的一个失误。

2、getPickerDataByIndex(index, pickerData)

这个方法很简单,主要的目的是通过 index 获取一个 data 对象({label,value})。

这里我觉得参数上也是这个组件设计失误的一部分,很明显,采用的是参数传入的方式获取到 pickerData,而最终需要的无非是 pickerData 拥有一个数组(源码中使用了这种方式,表现为 pickerData.items)或者本身是个数组。

这个 Picker 组件都是无状态的,但是生命组件的时候却使用了 Component,而不是 PureComponent,如果想要无状态组件(一般通用性组件都建议是无状态的),则就应该正确声明,如果想借助 state 的便利性,就可以将 pickerData 存放在 state 中,可以通过 this.setState 去更新内容。

但是,rax-picker 却在 constructor 中将其作为了一个静态变量,无法理解这是一种什么样的设计思想。

  constructor(props) {
    super(props);
    let pickerData = this.getPickerData();
  }

3、getPickerLableByValue(value, pickerData)

这个方法其实和 getPickerDataByIndex 差不多,只不过,getPickerDataByIndex 是返回一个 data 对象,而这个方法是明确的只返回一个 label.

通过 value 获取 label 就不能想通过 index 获取 value 方便了,后者可以直接借助数组索引,而前者必须要遍历对象数组,进行匹配,从而返回 label。

4、handlePress()

rax-picker 组件一般触犯方式都是通过点击事件,然后弹出选择框,进行选择。

所以 handlePress 就是点击事件,然后弹出选择框来,对于 weex 环境 和 web 容器环境依旧是采用了不同的方式,这个原因很明显,也是我最能清楚为什么要分环境的原因,因为我之前遇到过 weex-picker 的坑。

这里不再重复了,因为 weex-picker 在 web 环境下无法直接引入使用,需要特殊的方式,具体请看:

如果是 weex 环境,直接引入 weex-picker 模块,并且将 onValueChange 作为 picker 组件的时间传入。

这个具体的内容可以看 weex-picker 模块:

而如果是 web 环境,由于 weex-picker 的降级兼容性问题,因此采用了 select 标签的方式去渲染内容。

5、渲染方法

style 不重复,因为主要就是样式,不是很重要。

下面渲染的中,如果是 weex 环境,借助了 <Touchable> 组件完成 Press 的触发,在 Press 触发的时候,执行 weex-picker 的 picker 的方法,因为 weex-picker 需要 picker 方法进行主动触发。

if (isWeex) {
  return (
    <TouchableHighlight {...this.props} onPress={this.handlePress} style={style}>
      <Text style={textStyle}>
        {selectedLabel}
      </Text>
    </TouchableHighlight>
  );
} else { 
  const pickerData = this.getPickerData();
  return (
    <select style={style} onChange={(e) => {
      this.handlePress(e.target.options.selectedIndex);
    }}>
      {
        pickerData.items.map((item, index) => {
          if (index == pickerData.selectIndex) {
            return <option selected="selected" value={item.value}>{item.label}</option>;
          } else {
            return <option value={item.value}>{item.label}</option>;
          }
        })
      }
    </select>
  );
}

三、实践

为了验证上面我对于 Picker.Item 无用的理论(无用只是说没有实际的进行渲染,只是进行规范,并不是没有用),我将不用 <Picker.Item> 进行内容填写,而是使用 <View>

代码如下,可以发现我的 Picker 的子组件内容使用的是 <View>

1、示例代码

import {createElement, Component} from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import Picker from 'rax-picker';
import styles from './App.css';

class App extends Component {
  state = {
    value:"女",
  }
  changeHandle = (data) => {
    this.setState({
      value:data
    });
  }
  render() {
    return (
      <View style={styles.app}>
        <Text>{this.state.value}</Text>
        <Picker 
          selectedValue={this.state.value} 
          onValueChange={this.changeHandle}
          style={styles.picker}
        >
        <View label={'男'} value={'男'}></View>
        <View label={'女'} value={'女'}></View>
        <View label={'其他'} value={'其他'}></View>
        </Picker> 
      </View>
    );
  }
}

export default App;

2、效果

在 web 容器下,实际上就是一个带样式的 select

GIF.gif

在 weex 容器下,调用的原生选择:

866667149987850847.jpg