自己动手封装一个React Native多级联动


Posted in Javascript onSeptember 19, 2018

背景

肯定是最近有一个项目,需要一个二级联动功能了!

本来想封装完整之后,放在github上面赚星星,但发现市面上已经有比较成熟的了,为什么我在开发之前没去搜索一下(项目很赶进度),泪崩啊,既然已经封装就来说说过程吧

任务开始

一. 原型图或设计图

在封装一个组件之前,首先你要知道组件长什么样子,大概的轮廓要了解

自己动手封装一个React Native多级联动

二. 构思结构

在封装之前,先在脑海里面想一下

1. 这个组件需要达到的功能是什么?

改变一级后,二级会跟着变化,改变二级,三级会变,以此类推,可以指定需要选中的项,可以动态改变每一级的值,支持按需加载

2. 暴露出来的API是什么?

// 已封装的组件(Pickers.js)
import React, { Component } from 'react'
import Pickers from './Pickers'

class Screen extends Component {
 constructor (props) {
  super(props)
  this.state = {
   defaultIndexs: [1, 0], // 指定选择每一级的第几项,可以不填不传,默认为0(第一项)
   visible: true, // 
   options: [ // 选项数据,label为显示的名称,children为下一级,按需加载直接改变options的值就行了
    {
     label: 'A',
     children: [
      {
       label: 'J'
      },
      {
       label: 'K'
      }
     ]
    },
    {
     label: 'B',
     children: [
      {
       label: 'X'
      },
      {
       label: 'Y'
      }
     ]
    }
   ]
  }
 }
 onChange(arr) { // 选中项改变时触发, arr为当前每一级选中项索引,如选中B和Y,此时的arr就等于[1,1]
  console.log(arr)
 }
 onOk(arr) { // 最终确认时触发,arr同上
  console.log(arr)
 }
 render() {
  return (
   <View style={styles.container}>
    <Pickers
     options={this.state.options}
     defaultIndexs={this.state.defaultIndexs}
     onChange={this.onChange.bind(this)}
     onOk={this.onOk.bind(this)}>
    </Pickers>
   </View>
  )
 }
}

API在前期,往往会在封装的过程中,增加会修改,根据实际情况灵活变通

3. 如何让使用者使用起来更方便?

用目前比较流行的数据结构和风格(可以借鉴其它组件),接口名称定义一目了然

4. 如何能适应更多的场景?

只封装功能,不封装业务

三. 开始写代码

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
 StyleSheet,
 View,
 Text,
 TouchableOpacity,
} from 'react-native'

class Pickers extends Component {
 static propTypes = {
  options: PropTypes.array,
  defaultIndexs: PropTypes.array,
  onClose: PropTypes.func,
  onChange: PropTypes.func,
  onOk: PropTypes.func,
 }

 constructor (props) {
  super(props)
  this.state = {
   options: props.options, // 选项数据
   indexs: props.defaultIndexs || [] // 当前选择的是每一级的每一项,如[1, 0],第一级的第2项,第二级的第一项
  }
  this.close = this.close.bind(this) // 指定this
  this.ok = this.ok.bind(this) // 指定this
 }
 close () { // 取消按钮事件
  this.props.onClose && this.props.onClose()
 }

 ok () { // 确认按钮事件
  this.props.onOk && this.props.onOk(this.state.indexs)
 }
 onChange () { // 选项变化的回调函数

 }
 renderItems () { // 拼装选择项组

 }

 render() {
  return (
   <View
    style={styles.box}>
    <TouchableOpacity
     onPress={this.close}
     style={styles.bg}>
     <TouchableOpacity
      activeOpacity={1}
      style={styles.dialogBox}>
      <View style={styles.pickerBox}>
       {this.renderItems()}
      </View>
      <View style={styles.btnBox}>
       <TouchableOpacity
        onPress={this.close}
        style={styles.cancelBtn}>
        <Text
         numberOfLines={1}
         ellipsizeMode={"tail"}
         style={styles.cancelBtnText}>取消</Text>
       </TouchableOpacity>
       <TouchableOpacity
        onPress={this.ok}
        style={styles.okBtn}>
        <Text
         numberOfLines={1}
         ellipsizeMode={"tail"}
         style={styles.okBtnText}>确认</Text>
       </TouchableOpacity>
      </View>
     </TouchableOpacity>
    </TouchableOpacity>
   </View>
  )
 }
}

选择项组的拼装是核心功能,单独提出一个函数(renderItems)来,方便管理和后期维护

renderItems () { // 拼装选择项组
  const items = []
  const { options = [], indexs = [] } = this.state
  const re = (arr, index) => { // index为第几级
   if (arr && arr.length > 0) {
    const childIndex = indexs[index] || 0 // 当前级指定选中第几项,默认为第一项
    items.push({
     defaultIndex: childIndex,
     values: arr //当前级的选项列表
    })
    if (arr[childIndex] && arr[childIndex].children) {
     const nextIndex = index + 1
     re(arr[childIndex].children, nextIndex)
    }
   }
  }
  re(options, 0) // re为一个递归函数
  return items.map((obj, index) => {
   return ( // PickerItem为单个选择项,list为选项列表,defaultIndex为指定选择第几项,onChange选中选项改变时回调函数,itemIndex选中的第几项,index为第几级,如(2, 1)为选中第二级的第三项
    <PickerItem
     key={index.toString()}
     list={obj.values}
     defaultIndex={obj.defaultIndex}
     onChange={(itemIndex) => { this.onChange(itemIndex, index)}}
     />
   )
  })
 }

PickerItem为单个选择项组件,react native中的自带Picker在安卓和IOS上面表现的样式是不一样的,如果产品要求一样的话,就在PickerItem里面改,只需提供相同的接口,相当于PickerItem是独立的,维护起来很方便

// 单个选项
class PickerItem extends Component {
 static propTypes = {
  list: PropTypes.array,
  onChange: PropTypes.func,
  defaultIndex: PropTypes.number,
 }

 static getDerivedStateFromProps(nextProps, prevState) { // list选项列表和defaultIndex变化之后重新渲染
  if (nextProps.list !== prevState.list ||
    nextProps.defaultIndex !== prevState.defaultIndex) {
   return {
    list: nextProps.list,
    index: nextProps.defaultIndex
   }
  }
  return null
 }

 constructor (props) {
  super(props)
  this.state = {
   list: props.list,
   index: props.defaultIndex
  }
  this.onValueChange = this.onValueChange.bind(this)
 }

 onValueChange (itemValue, itemIndex) {
  this.setState( // setState不是立即渲染
   {
    index: itemIndex
   },
   () => {
    this.props.onChange && this.props.onChange(itemIndex)
   })

 }

 render() {
  // Picker的接口直接看react native的文档https://reactnative.cn/docs/picker/
  const { list = [], index = 0 } = this.state
  const value = list[index]
  const Items = list.map((obj, index) => {
   return <Picker.Item key={index} label={obj.label} value={obj} />
  })
  return (
   <Picker
    selectedValue={value}
    style={{ flex: 1 }}
    mode="dropdown"
    onValueChange={this.onValueChange}>
    {Items}
   </Picker>
  )
 }
}

renderItems()中PickerItem的回调函数onChange

onChange (itemIndex, currentIndex) { // itemIndex选中的是第几项,currentIndex第几级发生了变化
  const indexArr = []
  const { options = [], indexs = [] } = this.state
  const re = (arr, index) => { // index为第几层,循环每一级
   if (arr && arr.length > 0) {
    let childIndex
    if (index < currentIndex) { // 当前级小于发生变化的层级, 选中项还是之前的项
     childIndex = indexs[index] || 0
    } else if (index === currentIndex) { // 当前级等于发生变化的层级, 选中项是传来的itemIndex
     childIndex = itemIndex
    } else { // 当前级大于发生变化的层级, 选中项应该置为默认0,因为下级的选项会随着上级的变化而变化
     childIndex = 0
    }
    indexArr[index] = childIndex
    if (arr[childIndex] && arr[childIndex].children) {
     const nextIndex = index + 1
     re(arr[childIndex].children, nextIndex)
    }
   }
  }
  re(options, 0)
  this.setState(
   {
    indexs: indexArr // 重置所有选中项,重新渲染
   },
   () => {
    this.props.onChange && this.props.onChange(indexArr)
   }
  )
 }

总结

市面上成熟的多级联动很多,如果对功能要求比较高的话,建议用成熟的组件,这样开发成本低,文档全,团队中其他人易接手。如果只有用到里面非常简单的功能,很快就可以开发好,建议自己开发,没必要引用一个庞大的包,如果要特殊定制的话,就只有自己开发。无论以上哪种情况,能理解里面的运行原理甚好

主要说明在代码里面,也可以直接拷贝完整代码看,没多少内容,如果需要获取对应值的话,直接通过获取的索引查对应值就行了

完整代码

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
 StyleSheet,
 View,
 Text,
 Picker,
 TouchableOpacity,
} from 'react-native'

// 单个选项
class PickerItem extends Component {
 static propTypes = {
  list: PropTypes.array,
  onChange: PropTypes.func,
  defaultIndex: PropTypes.number,
 }

 static getDerivedStateFromProps(nextProps, prevState) { // list选项列表和defaultIndex变化之后重新渲染
  if (nextProps.list !== prevState.list ||
    nextProps.defaultIndex !== prevState.defaultIndex) {
   return {
    list: nextProps.list,
    index: nextProps.defaultIndex
   }
  }
  return null
 }

 constructor (props) {
  super(props)
  this.state = {
   list: props.list,
   index: props.defaultIndex
  }
  this.onValueChange = this.onValueChange.bind(this)
 }

 onValueChange (itemValue, itemIndex) {
  this.setState( // setState不是立即渲染
   {
    index: itemIndex
   },
   () => {
    this.props.onChange && this.props.onChange(itemIndex)
   })

 }

 render() {
  // Picker的接口直接看react native的文档https://reactnative.cn/docs/picker/
  const { list = [], index = 0 } = this.state
  const value = list[index]
  const Items = list.map((obj, index) => {
   return <Picker.Item key={index} label={obj.label} value={obj} />
  })
  return (
   <Picker
    selectedValue={value}
    style={{ flex: 1 }}
    mode="dropdown"
    onValueChange={this.onValueChange}>
    {Items}
   </Picker>
  )
 }
}

// Modal 安卓上无法返回
class Pickers extends Component {
 static propTypes = {
  options: PropTypes.array,
  defaultIndexs: PropTypes.array,
  onClose: PropTypes.func,
  onChange: PropTypes.func,
  onOk: PropTypes.func,
 }
 static getDerivedStateFromProps(nextProps, prevState) { // options数据选项或指定项变化时重新渲染
  if (nextProps.options !== prevState.options ||
    nextProps.defaultIndexs !== prevState.defaultIndexs) {
   return {
    options: nextProps.options,
    indexs: nextProps.defaultIndexs
   }
  }
  return null
 }
 constructor (props) {
  super(props)
  this.state = {
   options: props.options, // 选项数据
   indexs: props.defaultIndexs || [] // 当前选择的是每一级的每一项,如[1, 0],第一级的第2项,第二级的第一项
  }
  this.close = this.close.bind(this) // 指定this
  this.ok = this.ok.bind(this) // 指定this
 }
 close () { // 取消按钮事件
  this.props.onClose && this.props.onClose()
 }

 ok () { // 确认按钮事件
  this.props.onOk && this.props.onOk(this.state.indexs)
 }
 onChange (itemIndex, currentIndex) { // itemIndex选中的是第几项,currentIndex第几级发生了变化
  const indexArr = []
  const { options = [], indexs = [] } = this.state
  const re = (arr, index) => { // index为第几层,循环每一级
   if (arr && arr.length > 0) {
    let childIndex
    if (index < currentIndex) { // 当前级小于发生变化的层级, 选中项还是之前的项
     childIndex = indexs[index] || 0
    } else if (index === currentIndex) { // 当前级等于发生变化的层级, 选中项是传来的itemIndex
     childIndex = itemIndex
    } else { // 当前级大于发生变化的层级, 选中项应该置为默认0,因为下级的选项会随着上级的变化而变化
     childIndex = 0
    }
    indexArr[index] = childIndex
    if (arr[childIndex] && arr[childIndex].children) {
     const nextIndex = index + 1
     re(arr[childIndex].children, nextIndex)
    }
   }
  }
  re(options, 0)
  this.setState(
   {
    indexs: indexArr // 重置所有选中项,重新渲染
   },
   () => {
    this.props.onChange && this.props.onChange(indexArr)
   }
  )
 }
 renderItems () { // 拼装选择项组
  const items = []
  const { options = [], indexs = [] } = this.state
  const re = (arr, index) => { // index为第几级
   if (arr && arr.length > 0) {
    const childIndex = indexs[index] || 0 // 当前级指定选中第几项,默认为第一项
    items.push({
     defaultIndex: childIndex,
     values: arr //当前级的选项列表
    })
    if (arr[childIndex] && arr[childIndex].children) {
     const nextIndex = index + 1
     re(arr[childIndex].children, nextIndex)
    }
   }
  }
  re(options, 0) // re为一个递归函数
  return items.map((obj, index) => {
   return ( // PickerItem为单个选择项,list为选项列表,defaultIndex为指定选择第几项,onChange选中选项改变时回调函数
    <PickerItem
     key={index.toString()}
     list={obj.values}
     defaultIndex={obj.defaultIndex}
     onChange={(itemIndex) => { this.onChange(itemIndex, index)}}
     />
   )
  })
 }

 render() {
  return (
   <View
    style={styles.box}>
    <TouchableOpacity
     onPress={this.close}
     style={styles.bg}>
     <TouchableOpacity
      activeOpacity={1}
      style={styles.dialogBox}>
      <View style={styles.pickerBox}>
       {this.renderItems()}
      </View>
      <View style={styles.btnBox}>
       <TouchableOpacity
        onPress={this.close}
        style={styles.cancelBtn}>
        <Text
         numberOfLines={1}
         ellipsizeMode={"tail"}
         style={styles.cancelBtnText}>取消</Text>
       </TouchableOpacity>
       <TouchableOpacity
        onPress={this.ok}
        style={styles.okBtn}>
        <Text
         numberOfLines={1}
         ellipsizeMode={"tail"}
         style={styles.okBtnText}>确认</Text>
       </TouchableOpacity>
      </View>
     </TouchableOpacity>
    </TouchableOpacity>
   </View>
  )
 }
}

const styles = StyleSheet.create({
 box: {
  position: 'absolute',
  top: 0,
  bottom: 0,
  left: 0,
  right: 0,
  zIndex: 9999,
 },
 bg: {
  flex: 1,
  backgroundColor: 'rgba(0,0,0,0.4)',
  justifyContent: 'center',
  alignItems: 'center'
 },
 dialogBox: {
  width: 260,
  flexDirection: "column",
  backgroundColor: '#fff',
 },
 pickerBox: {
  flexDirection: "row",
 },
 btnBox: {
  flexDirection: "row",
  height: 45,
 },
 cancelBtn: {
  flex: 1,
  justifyContent: 'center',
  alignItems: 'center',
  borderColor: '#4A90E2',
  borderWidth: 1,
 },
 cancelBtnText: {
  fontSize: 15,
  color: '#4A90E2'
 },
 okBtn: {
  flex: 1,
  justifyContent: 'center',
  alignItems: 'center',
  backgroundColor: '#4A90E2',
 },
 okBtnText: {
  fontSize: 15,
  color: '#fff'
 },
})

export default Pickers

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript getElementsByClassName函数
Apr 01 Javascript
基于jquery的lazy loader插件实现图片的延迟加载[简单使用]
May 07 Javascript
JQuery对class属性的操作实现按钮开关效果
Oct 11 Javascript
JavaScript支持的最大递归调用次数分析
Jun 24 Javascript
jQuery仅用3行代码实现的显示与隐藏功能完整实例
Oct 08 Javascript
Listloading.js移动端上拉下拉刷新组件
Aug 04 Javascript
AngularJs Understanding the Model Component
Sep 02 Javascript
Angular企业级开发——MVC之控制器详解
Feb 20 Javascript
vue页面离开后执行函数的实例
Mar 13 Javascript
vue interceptor 使用教程实例详解
Sep 13 Javascript
小程序云开发初探(小结)
Oct 24 Javascript
jQuery实现的网站banner图片无缝轮播效果完整实例
Jan 28 jQuery
vue中如何实现后台管理系统的权限控制的方法示例
Sep 19 #Javascript
5分钟快速掌握JS中var、let和const的异同
Sep 19 #Javascript
vue-cli整合vuex的时候,修改actions和mutations,实现热部署的方法
Sep 19 #Javascript
node.js环境搭建图文详解
Sep 19 #Javascript
老生常谈JavaScript获取CSS样式的方法(兼容各浏览器)
Sep 19 #Javascript
vue生命周期和react生命周期对比【推荐】
Sep 19 #Javascript
Vue瀑布流插件的使用示例
Sep 19 #Javascript
You might like
php数组函数序列之array_combine() - 数组合并函数使用说明
2011/10/29 PHP
php中\r \r\n \t的区别示例介绍
2014/02/08 PHP
PHP实现批量生成App各种尺寸Logo
2015/03/19 PHP
原生JS实现Ajax通过GET方式与PHP进行交互操作示例
2018/05/12 PHP
php使用gearman进行任务分发操作实例详解
2020/02/26 PHP
Centos7安装swoole扩展操作示例
2020/03/26 PHP
jQuery表格插件ParamQuery简单使用方法示例
2013/12/05 Javascript
JQuery实现简单的服务器轮询效果实例
2016/03/31 Javascript
自己封装的一个原生JS拖动方法(推荐)
2016/11/22 Javascript
Bootstrap table简单使用总结
2017/02/15 Javascript
使用 Angular RouteReuseStrategy 缓存(路由)组件的实例代码
2019/11/01 Javascript
小程序简单两栏瀑布流效果的实现
2019/12/18 Javascript
JS document内容及样式操作完整示例
2020/01/14 Javascript
js实现的订阅发布者模式简单示例
2020/03/14 Javascript
vue-cli+webpack项目打包到服务器后,ttf字体找不到的解决操作
2020/08/28 Javascript
JavaScript实现单点登录的示例
2020/09/23 Javascript
JavaScript TAB栏切换效果的示例
2020/11/05 Javascript
python发送邮件示例(支持中文邮件标题)
2014/02/16 Python
windows10系统中安装python3.x+scrapy教程
2016/11/08 Python
在cmder下安装ipython以及环境的搭建
2018/10/19 Python
在Python 字典中一键对应多个值的实例
2019/02/03 Python
Python登录系统界面实现详解
2019/06/25 Python
基于python框架Scrapy爬取自己的博客内容过程详解
2019/08/05 Python
使用 Python 在京东上抢口罩的思路详解
2020/02/27 Python
python进度条显示-tqmd模块的实现示例
2020/08/23 Python
Python 数据分析之逐块读取文本的实现
2020/12/14 Python
html5时钟实现代码
2010/10/22 HTML / CSS
HTML5实现获取地理位置信息并定位功能
2015/04/25 HTML / CSS
来自世界上最好大学的在线课程:edX
2018/10/16 全球购物
广州喜创信息技术有限公司JAVA软件工程师笔试题
2012/10/17 面试题
小学生元旦广播稿
2014/02/21 职场文书
2014年关于两会精神的心得体会
2014/03/17 职场文书
党员发展大会主持词
2015/07/03 职场文书
python自动化操作之动态验证码、滑动验证码的降噪和识别
2021/08/30 Python
MySQL中varchar和char类型的区别
2021/11/17 MySQL
JS轻量级函数式编程实现XDM三
2022/06/16 Javascript