自己动手封装一个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 相关文章推荐
TBCompressor js代码压缩
Jan 05 Javascript
基于jquery的当鼠标滚轮到最底端继续加载新数据思路分享(多用于微博、空间、论坛 )
Oct 10 Javascript
浏览器中url存储的JavaScript实现
Jul 07 Javascript
JavaScript中Textarea滚动条不能拖动的解决方法
Dec 15 Javascript
javascript中json基础知识详解
Jan 19 Javascript
js上下视差滚动简单实现代码
Mar 07 Javascript
ES6中class类用法实例浅析
Apr 06 Javascript
Vue手把手教你撸一个 beforeEnter 钩子函数
Apr 24 Javascript
深入理解JS的事件绑定、事件流模型
May 13 Javascript
页面内锚点定位及跳转方法总结(推荐)
Apr 24 Javascript
如何换个角度使用VUE过滤器详解
Sep 11 Javascript
详解vue中在循环中使用@mouseenter 和 @mouseleave事件闪烁问题解决方法
Apr 07 Javascript
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
冰滴咖啡制作步骤
2021/03/03 冲泡冲煮
php strcmp使用说明
2010/04/22 PHP
PHP之COOKIE支持详解
2010/09/20 PHP
linux iconv方法的使用
2011/10/01 PHP
用php定义一个数组最简单的方法
2019/10/04 PHP
jQuery ctrl+Enter shift+Enter实现代码
2010/02/07 Javascript
js的逻辑运算符 ||
2010/05/31 Javascript
jquery复选框全选/取消示例
2013/12/30 Javascript
JS更改select内option属性的方法
2015/10/14 Javascript
微信小程序 石头剪刀布实例代码
2017/01/04 Javascript
详解js静态资源文件请求的处理
2017/08/01 Javascript
详解如何用babel转换es6的class语法
2018/04/03 Javascript
在 Vue.js中优雅地使用全局事件的方法
2019/02/01 Javascript
如何从零开始手写Koa2框架
2019/03/22 Javascript
浅谈一种让小程序支持JSX语法的新思路
2019/06/16 Javascript
js 图片懒加载的实现
2020/10/21 Javascript
[03:46]DOTA2英雄基础教程 维萨吉
2013/12/11 DOTA
在Python中关于中文编码问题的处理建议
2015/04/08 Python
Python处理字符串之isspace()方法的使用
2015/05/19 Python
python绘制铅球的运行轨迹代码分享
2017/11/14 Python
Python实现多进程共享数据的方法分析
2017/12/04 Python
Python删除n行后的其他行方法
2019/01/28 Python
Python-jenkins模块获取jobs的执行状态操作
2020/05/12 Python
python爬取”顶点小说网“《纯阳剑尊》的示例代码
2020/10/16 Python
英国虚拟主机服务商:eUKhost
2016/08/16 全球购物
美国最佳在线航班预订网站:LookupFare
2019/03/26 全球购物
美国基督教约会网站:ChristianCafe.com
2020/02/04 全球购物
总经理助理岗位职责
2013/11/08 职场文书
合同专员岗位职责
2013/12/18 职场文书
2014年班主任自我评价范文
2014/04/23 职场文书
学习教师敬业奉献模范事迹材料思想汇报
2014/09/19 职场文书
师德师风学习材料
2014/12/19 职场文书
给老婆的检讨书(搞笑版)
2015/05/06 职场文书
绿里奇迹观后感
2015/06/15 职场文书
html用代码制作虚线框怎么做? dw制作虚线圆圈的技巧
2022/12/24 HTML / CSS
HTML页面点击按钮关闭页面的多种方式
2022/12/24 HTML / CSS