背景
肯定是最近有一个项目, 需要一个二级联动功能了!
本来想封装完整之后, 放在 GitHub 上面赚星星, 但发现市面上已经有比较成熟的了, 为什么我在开发之前没去搜索一下 (项目很赶进度), 泪崩啊, 既然已经封装就来说说过程吧
任务开始
一. 原型图或设计图
在封装一个组件之前, 首先你要知道组件长什么样子, 大概的轮廓要了解
二. 构思结构
在封装之前, 先在脑海里面想一下
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
来源: http://www.jb51.net/article/147692.htm