ReactNative: 组件封装(如二级菜单组件)
一、简介
前面都是介绍关于RN基本的API组件和UI组件,这些组件在复杂的复合组件中都是以颗粒度的形式存在的,如何有效合理的利用它们进行封装,是十分有必要的。开发复合组件的好处有很多,最为明显的就是复用和独立功能模块。复合组件分为两种,一种是静态的,这种组件不具备重用的特征,由静态数据组成,开发静态页面即可,不考虑数据的传递。另一种就是动态组件,它可以通过接收外部传入的动态数据进行联动,达到组件复用的效果。
二、应用
动态组件有很多应用,最典型的例如二级菜单组件,样式固定,数据可变,通过一级目录的选择来联动刷新二级目录的数据。思路很简单,首先构建数据模型;其次,对要封装的组件进行粒度拆分,依次按照此粒度构建组件;接着,设定组件的属性接口;然后,设计组件渲染规则并分解渲染, 绑定事件;最后,引用封装的组件并传入数据模型即可。完整示例如下:
HeadList.js【粒度组件:头部标签】
import React, { Component } from 'react'; import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'; export default class HeadList extends Component{ render(){ let headData = this.props.data; let update = this.props.update; const count = headData.length; return ( <View style={style.flex}> { headData.map( function(item,i){ return ( <View style={[style.center,{flex:1/count}]} key={i}> <TouchableOpacity onPress={ () => {update(headData[i])}}> <Text style={style.head_text}> {item} </Text> </TouchableOpacity> </View> ) }) } </View> ) } } const style = StyleSheet.create({ flex: { flex: 1, flexDirection: 'row' }, head_text: { color: '#7B7B7B', fontSize: 20 }, center: { justifyContent: "center", alignItems: "center" } });
LeftList.js【粒度组件:一级目录】
import React, { Component } from 'react'; import { StyleSheet, View, ScrollView, TouchableOpacity, Text, Dimensions } from 'react-native'; const {width} = Dimensions.get('window'); export default class LeftList extends Component{ constructor(props){ super(props); this.state = { selectIndex:0 }; } updateState(index,update,leftData){ //触发回调函数 update(leftData[index]); //重新渲染cell颜色 this.setState({ selectIndex:index }) }; componentWillReceiveProps(nextProps): void { if (nextProps.shouldChangeTab) { //重新渲染cell颜色 this.setState({ selectIndex:0 }) } } render(){ let leftData = this.props.data; let update = this.props.update; let {selectIndex} = this.state; return ( <ScrollView style={style.container}> { leftData.map( (item,i) => { return ( <View key={i} style={[style.list_cell,style.center, selectIndex === i ? style.selectBgColor : style.normalBgColor]}> <TouchableOpacity onPress = { this.updateState.bind(this,i,update,leftData) } > <Text style={[style.list_text,style.list_margin]}> {item} </Text> </TouchableOpacity> </View> ) }) } </ScrollView> ) } } const style = StyleSheet.create({ container: { flex:1, width: width/2, backgroundColor:'#F2F2F2' }, list_text: { color: '#7B7B7B', fontSize: 18 }, list_margin: { marginLeft: 20 }, list_cell: { height: 60 }, center: { justifyContent: "center" }, selectBgColor: { backgroundColor:'#FFFFFF' }, normalBgColor: { backgroundColor:'#F2F2F2' } });
RightList.js【粒度组件:二级目录】
import React, { Component } from 'react'; import { ScrollView, StyleSheet, Text, TouchableOpacity, View, Dimensions } from 'react-native'; const {width} = Dimensions.get('window'); export default class RightList extends Component{ render(){ let rightData = this.props.data; return ( <ScrollView style={style.container}> { rightData.map( function(item,i){ return ( <View key={i} style={[style.list_cell,style.center]}> <TouchableOpacity> <Text style={[style.list_text,style.list_margin]}> {item} </Text> </TouchableOpacity> </View> ) }) } </ScrollView> ) } } const style = StyleSheet.create({ container: { flex:1, width: width/2, backgroundColor:'#FFFFFF' }, list_text: { color: '#7B7B7B', fontSize: 18 }, list_margin: { marginLeft: 20 }, list_cell: { height: 60 }, center: { justifyContent: "center" } });
MenuList.js【封装的复合组件】
import React, { Component } from 'react'; import { StyleSheet, View, Dimensions } from 'react-native'; import HeadList from './HeadList' import LeftList from "./LeftList"; import RightList from "./RightList"; const {height} = Dimensions.get('window'); let data = {}; let headData = []; let leftData = []; let rightData = []; export default class MenuList extends Component{ constructor(props){ super(props); data = props.data; //初始化头部数据 for (let item in data){ headData.push(item); } //初始化左侧数据 let defaultLValue = headData[0]; for (let item in data[defaultLValue]){ leftData.push(item); } //初始化右侧数据 let defaultRValue = leftData[0]; rightData = data[defaultLValue][defaultRValue]; //初始化state this.state = { shouldChangeTab: false, currentTab: defaultLValue, leftData : leftData, rightData : rightData }; } //函数回调,每次选择头部tab后,重新render forceUpdateAllUI = (ele) => { leftData = []; for (let item in data[ele]){ leftData.push(item); } let defaultRValue = leftData[0]; rightData = data[ele][defaultRValue]; this.setState({ shouldChangeTab:true, currentTab: ele, leftData: leftData, rightData: rightData }) }; //函数回调,每次选择左侧列表后,重新render forceUpdateRightListUI = (ele) => { rightData = data[this.state.currentTab][ele]; this.setState({ shouldChangeTab:false, rightData: rightData }) }; render(){ return ( <View style={style.container}> <View style={style.top}> <HeadList data={headData} update={this.forceUpdateAllUI}/> </View> <View style={style.bottom}> <LeftList data={this.state.leftData} shouldChangeTab={this.state.shouldChangeTab} update={this.forceUpdateRightListUI} /> <RightList data={this.state.rightData}/> </View> </View> ) } } const style = StyleSheet.create({ container: { flex: 1, height: height, borderTopWidth: 1, borderBottomWidth: 1, borderColor: '#ddd' }, top: { height: 60, borderBottomWidth: 1, borderColor:'#DFDFDF', backgroundColor:'#F5F5F5' }, bottom: { height: height-60, flexDirection:'row', backgroundColor: '#F5FCFF' } });
Index.ios.js【引用复合组件】
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react'; import { AppRegistry, StyleSheet, View, StatusBar } from 'react-native'; import MenuList from "./src/MenuList"; const data1 = { "全部区域": { "全部区域": ["全部区域"], "热门商圈": [ "虹桥地区", "徐家汇地区", "淮海路商业区", "静安寺地区", "上海火车站地区", "浦东陆家嘴金融贸易区", "四川北路商业区", "人民广场地区", "南翔,安亭汽车城" ], "热门行政区": [ "静安区", "徐汇区", "长宁区", "黄浦区", "虹口区", "宝山区", "闸北区" ] }, "地铁沿线":{ "地铁全线":["地铁全线"], "一号线":["莘庒站","外环路站","莲花路站","锦江乐园站","上海南站","漕宝路站"], "二号线":["浦东国际机场站","海天三路站","远东大道站","凌空路站"] } }; const data2 = { "Language":{ "All":["All"], "Web Front End":["HTML","CSS","JavaScript"], "Server":["Node.js","Java","Python","Ruby","Php"] }, "Tool":{ "All":["All"], "Apple":["Xcode"], "Other":["Sublime Text","WebStorm","Visual Studio Code"] } }; StatusBar.setHidden(true); export default class RNComponentPackage extends Component { render() { return ( <View style={styles.container}> <MenuList data={data1}/> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FFFFFF', } }); AppRegistry.registerComponent('RNComponentPackage', () => RNComponentPackage);
三、演示
程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!