上一篇文章记录了在RN中如何响应Hover事件,现在,我想仿照RN中<TouchableHighlight>标签实现<HoverBoard>,目的是为了不在具体UI代码中进行业务逻辑代码的频繁copy,最终的效果基本如下:
<FlatList
data={[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]}
keyExtractor={() => ++i}
numColumns={6}
style={{paddingHorizontal: 15,}}
renderItem={({item}) => (
<HoverBoard
onHoverChanged={this._onHoverChanged}
>
<Image source={require('./img/icon.jpg')} style={{width: 150, height: 150}}/>
<Title ref="text"/>
</HoverBoard>
)}
/>
<HoverBoard>在加载之后会计算每个子节点的实际位置,然后监听Hover事件,在发现自身状态发生变化时,会回调“this._onHoverChanged”方法来更新界面。
简单说一下实现背景,在Android中有很多的监听器,可以很简单的实现类似FlatList组件中子节点状态的回调,但是看了一段时间的RN或者说JavaScript,发现这件事情做起来好像有些麻烦(好吧,我承认目前对JS还不太在行。。)。
我为什么一定要这样做呢,简略的说一下探索的过程,最初我采用类似下面的方式实现FlatList的子节点:
<View>
<Image source={require('../img/icon.jpg')} style={{width: len, height: len}}/>
<Text ref="text" style={styles.text}>
({this.state.left}, {this.state.right}), ({this.state.top}, {this.state.bottom})
</Text>
</View>
后来发现,在现有的设备上,子节点每次整体刷新需要接近20ms的时间,这真的是无法忍受。因为毕竟只是实现Demo,所以我尝试单独刷新<Text>标签,在经过了一番折腾之后,发现这样做确实让效率提高了十倍以上。
从以往的开发经验中可以得到一点:子节点的布局肯定不会如上面那样简单,换句话说,它肯定是一个"Component"。
我肯定不愿意让多余的业务逻辑插入到这个"View"中,阅读了一下<TouchableHighlight>的源码,发现渲染部分的代码是酱婶的:
render: function() {
return (
<View
... ...>
{React.cloneElement(
React.Children.only(this.props.children),
{
ref: CHILD_REF,
}
)}
{... ...}
</View>
);
}
经过了一番折腾,发现这段代码的意思应该是将源代码中的该组件的子节点克隆并转换成界面上UI元素,“React.Children.only”方法目前还不是非常理解,大致理解为修改单独元素的属性。
在折腾代码的时候,发现自己对于“this.refs”和“this.props.children”的区别闹不明白了,现在的理解是“this.refs”代表的应该是界面显示之后的节点对象,而“this.props.children”应该是指源代码中书写的该标签的子节点集,好吧,我知道这么说也还是有点绕。
最终通过下面的代码回调到外部:
_hoverListener = (msg) => {
let {x: hx, y: hy} = msg;
let inHover = hx >= this.left && hx <= this.right && hy >= this.top && hy <= this.bottom;
const isChanged = this._inHover != inHover;
if (isChanged) {
this.props.onHoverChanged && this.props.onHoverChanged(this.refs.text, inHover);
this._inHover = inHover;
}
}