当前位置:   article > 正文

【React】手写Antd Select联动效果组件_react项目antd select组件的三级联动

react项目antd select组件的三级联动

目录

需求

思考

需求拆分

解决


需求

1.联动select,而不是像Cascader那样【级联选择 Cascader - Ant Design】。

 2.传入对应options生成对应的联动select。

options示例数据

  1. const options = [
  2. {
  3. "label": "病区A",
  4. "value": "bingquA",
  5. "children": [{
  6. "label": "科室A-A",
  7. "value": "keshiAA"
  8. }, {
  9. "label": "科室A-B",
  10. "value": "keshiAB"
  11. }, {
  12. "label": "科室A-C",
  13. "value": "keshiAC"
  14. }]
  15. },
  16. {
  17. "label": "病区B",
  18. "value": "bingquB",
  19. "children": [{
  20. "label": "科室B-A",
  21. "value": "keshiBA"
  22. }, {
  23. "label": "科室B-B",
  24. "value": "keshiBB"
  25. }, {
  26. "label": "科室B-C",
  27. "value": "keshiBC"
  28. }]
  29. }
  30. ]

3.当组件用Form.Item包裹,在表单提交时也可以获取到所有select组件的value。

4.开发者能高效地定制化每一个下拉框地样式

思考

antd中的select中【选择器 Select - Ant Design】有省市联动的例子:

但是这个联动的例子是写死的,无法通过配置的形式直接生成(即传入后台请求回来的需要联动的数据直接生成两个甚至三个四个联动的select),组件复用性不强,并且当表单提交的时候无法获取到两个select组件的value。

参考antd的Cascader【级联选择 Cascader - Ant Design】达到了我的需求,但是样式不同

需求拆分

1.如何使得select1 selected的改变控制select2的options select2控制select3...... ?
2.如何处理后台返回的数据?
3.如何在表单提交时也可以获取到所有select组件的value?

4.如何制定一种配置规范方便开发者配置每一个下拉框的样式呢?

解决

1. 生成如下的数据结构,使得除了第一个select,其他的select依赖上一个select选中的value

  1. const optionsArr = [{
  2. "default": [{
  3. "label": "病区A",
  4. "value": "bingquA"
  5. }, {
  6. "label": "病区B",
  7. "value": "bingquB"
  8. }]
  9. }, {
  10. "bingquA": [{
  11. "label": "科室A-A",
  12. "value": "keshiAA"
  13. }, {
  14. "label": "科室A-B",
  15. "value": "keshiAB"
  16. }, {
  17. "label": "科室A-C",
  18. "value": "keshiAC"
  19. }],
  20. "bingquB": [{
  21. "label": "科室B-A",
  22. "value": "keshiBA"
  23. }, {
  24. "label": "科室B-B",
  25. "value": "keshiBB"
  26. }, {
  27. "label": "科室B-C",
  28. "value": "keshiBC"
  29. }]
  30. }]

 看代码

  1. // 存放所有下拉框的options
  2. const [optionsArr, setOptionsArr] = useState([])
  3. // 存放所有已经选中的下拉框value
  4. const [selectedArr, setSelectedArr] = useState([])
  5. // 第一个下拉框没有父级value,默认值default
  6. const DEFAULT_VALUE = "default"
  7. return (
  8. <Fragment>
  9. {
  10. optionsArr.map((item, index) => {
  11. return (
  12. <Select
  13. value={selectedArr[index]}
  14. onChange={(value) => handleChange(value, index)}
  15. // 除了第一个select外,其他的select依赖上一个select选中的value
  16. options={index === 0 ? item[DEFAULT_VALUE] : item[selectedArr[index - 1]]}
  17. />
  18. )
  19. })
  20. }
  21. </Fragment>
  22. )

2.处理后台返回的数据

  1. // 根据props中的options抽离出所有下拉框的options
  2. const getOptionsArr = (options, deep, optionsArr, fatherValue) => {
  3. // 根据deep动态决定要生成多少个下拉框
  4. if (optionsArr.length <= deep) optionsArr.push({})
  5. // wrapperArr中存储了 上一个select选中的value是fatherValue时,当前这个select的options
  6. const wrapperArr = []
  7. options.forEach(item => {
  8. const obj = {label: item.label, value: item.value}
  9. wrapperArr.push(obj)
  10. // 如果有children属性,则递归调用
  11. if (item.children) {
  12. // 当前的value即是children的fatherValue
  13. getOptionsArr(item.children, deep + 1, optionsArr, item.value)
  14. }
  15. })
  16. // optionsArr数组中的第deep个(从0开始)对象中 key为fatherValue,value是wrapperArr
  17. optionsArr[deep][fatherValue] = wrapperArr
  18. }

3.从props中获取Form.Item中的提供的onChange方法,当下拉框选项改变的时候调用

  1. // 下拉框被改变
  2. const handleChange = (val, index) => {
  3. const originArr = [...selectedArr]
  4. originArr[index] = val
  5. // 当父级下拉框被改变,子孙级下拉框选中的值需要清空
  6. for (let i = index + 1; i < originArr.length; i++) originArr[i] = undefined
  7. setSelectedArr(originArr)
  8. // 如果外面有用Form.Item包裹则调用传来的onChange方法,使得在表单提交的时候能获取到这个Form.Item的值
  9. onChange && onChange(originArr)
  10. }

4.传入如下配置信息

  1. props: {
  2. placeholders: ["病区", "科室"],
  3. allowClear: true,
  4. style: {
  5. widths: ["140px", "200px"],
  6. marginRight: '10px'
  7. }
  8. }

代表第一个下拉框默认值为”病区“,宽度为140px,第二个下拉框默认值为”科室“,宽度为200px,

它们都允许清除清除,右外边距为10px

对配置信息的处理函数如下

  1. // 根据配置信息config与对应下标index获取select的配置信息
  2. // 比如config为style,如果是正常的属性例如width则直接用,如果是widths=[100, 110, 120]则第一个下拉框width=100第二个110
  3. const getConfigProps = (config, index = 0) => {
  4. return Object.keys(config).reduce((prev, cur) => {
  5. if (cur.match(/[s]$/) && config[cur] instanceof Array) { // key以s结尾 value是数组
  6. const key = cur.substring(0, cur.length - 1) // 获得真实的prop
  7. if (config[cur].length <= index) { // 错误处理
  8. console.error(`联动下拉框组件,参数配置有误 ,${cur}参数个数小于生成的下拉框个数,当前索引为${index}~`)
  9. } else {
  10. prev[key] = config[cur][index]
  11. }
  12. } else {
  13. prev[cur] = config[cur]
  14. }
  15. return prev
  16. }, {})
  17. }
  1. return (
  2. <>
  3. {
  4. optionsArr.map((item, index) => {
  5. return (
  6. <Select
  7. key={index}
  8. value={selectedArr[index]}
  9. onChange={(value) => handleChange(value, index)}
  10. // 除了第一个select外,其他的select依赖上一个select选中的value
  11. options={index === 0 ? item[DEFAULT_VALUE] : item[selectedArr[index - 1]]}
  12. // placeholders包含所有select的默认值
  13. placeholder={otherProps.placeholders && otherProps.placeholders[index]}
  14. // otherProps包含select的所有其他配置信息
  15. {...getConfigProps(otherProps, index)}
  16. // 低效的写法(对比):style={{...otherProps.style, width: (otherProps.widths) ? (otherProps.widths && otherProps.widths[index]) : otherProps.style.width }}
  17. style={{...getConfigProps(otherProps.style, index)}}
  18. />
  19. )
  20. })
  21. }
  22. </>
  23. )

总体代码

  1. import React, { Fragment, memo, useState, useEffect } from "react"
  2. import { Select } from "antd"
  3. const ZCSelectCascader = (props) => {
  4. // onChange和value是由Form.Item提供的,options是一个包含{label,value,children}的数组,otherProps获取用户传入的其他配置信息
  5. const { options, onChange, value, ...otherProps } = props
  6. // 存放所有下拉框的options
  7. const [optionsArr, setOptionsArr] = useState([])
  8. // 存放所有已经选中的下拉框value
  9. const [selectedArr, setSelectedArr] = useState([])
  10. // 第一个下拉框没有父级value,默认值default
  11. const DEFAULT_VALUE = "default"
  12. // 设置Form.Item的initialValue或Form的initialValues会改变value,select的value实际受控于selectedArr,因此要setSelectedArr(value)
  13. useEffect(() => {
  14. setSelectedArr(value)
  15. }, [value])
  16. // 根据props中的options抽离出所有下拉框的options
  17. const getOptionsArr = (options, deep, optionsArr, fatherValue) => {
  18. // 根据deep动态决定要生成多少个下拉框
  19. if (optionsArr.length <= deep) optionsArr.push({})
  20. // wrapperArr中存储了 上一个select选中的value是fatherValue时,当前这个select的options
  21. const wrapperArr = []
  22. options.forEach(item => {
  23. const obj = {label: item.label, value: item.value}
  24. wrapperArr.push(obj)
  25. // 如果有children属性,则递归调用
  26. if (item.children) {
  27. // 当前的value即是children的fatherValue
  28. getOptionsArr(item.children, deep + 1, optionsArr, item.value)
  29. }
  30. })
  31. // optionsArr数组中的第deep个(从0开始)对象中 key为fatherValue,value是wrapperArr
  32. optionsArr[deep][fatherValue] = wrapperArr
  33. }
  34. // 下拉框被改变
  35. const handleChange = (val, index) => {
  36. const originArr = [...selectedArr]
  37. originArr[index] = val
  38. // 当父级下拉框被改变,子孙级下拉框选中的值需要清空
  39. for (let i = index + 1; i < originArr.length; i++) originArr[i] = undefined
  40. setSelectedArr(originArr)
  41. // 如果外面有用Form.Item包裹则调用传来的onChange方法,使得在表单提交的时候能获取到这个Form.Item的值
  42. onChange && onChange(originArr)
  43. }
  44. useEffect(() => {
  45. // options是通过发送网络请求获取的,一开始可能传的是空值
  46. if (options) {
  47. const originArr = []
  48. // 调用该方法生成符合要求的数组
  49. getOptionsArr(options, 0, originArr, DEFAULT_VALUE)
  50. setOptionsArr(originArr)
  51. // 初始化selectedArr,数组的长度和optionsArr一致
  52. setSelectedArr(new Array(originArr.length).fill())
  53. }
  54. }, [options])
  55. // 根据配置信息config与对应下标index获取select的配置信息
  56. // 比如config为style,如果是正常的属性例如width则直接用,如果是widths=[100, 110, 120]则第一个下拉框width=100第二个110
  57. const getConfigProps = (config, index = 0) => {
  58. return Object.keys(config).reduce((prev, cur) => {
  59. if (cur.match(/[s]$/) && config[cur] instanceof Array) { // key以s结尾 value是数组
  60. const key = cur.substring(0, cur.length - 1) // 获得真实的prop
  61. if (config[cur].length <= index) { // 错误处理
  62. console.error(`联动下拉框组件,参数配置有误 ,${cur}参数个数小于生成的下拉框个数,当前索引为${index}~`)
  63. } else {
  64. prev[key] = config[cur][index]
  65. }
  66. } else {
  67. prev[cur] = config[cur]
  68. }
  69. return prev
  70. }, {})
  71. }
  72. return (
  73. <>
  74. {
  75. optionsArr.map((item, index) => {
  76. return (
  77. <Select
  78. key={index}
  79. value={selectedArr[index]}
  80. onChange={(value) => handleChange(value, index)}
  81. // 除了第一个select外,其他的select依赖上一个select选中的value
  82. options={index === 0 ? item[DEFAULT_VALUE] : item[selectedArr[index - 1]]}
  83. // placeholders包含所有select的默认值
  84. placeholder={otherProps.placeholders && otherProps.placeholders[index]}
  85. // otherProps包含select的所有其他配置信息
  86. {...getConfigProps(otherProps, index)}
  87. // 低效的写法(对比):style={{...otherProps.style, width: (otherProps.widths) ? (otherProps.widths && otherProps.widths[index]) : otherProps.style.width }}
  88. style={{...getConfigProps(otherProps.style, index)}}
  89. />
  90. )
  91. })
  92. }
  93. </>
  94. )
  95. }
  96. export default memo(ZCSelectCascader)

调用该组件的示例代码

  1. import React from 'react'
  2. import SelectCascader from '../../components/ZCAntdComponents/SelectCascader'
  3. const options = [
  4. {
  5. "label": "病区A",
  6. "value": "bingquA",
  7. "children": [{
  8. "label": "科室A-A",
  9. "value": "keshiAA"
  10. }, {
  11. "label": "科室A-B",
  12. "value": "keshiAB"
  13. }, {
  14. "label": "科室A-C",
  15. "value": "keshiAC"
  16. }]
  17. },
  18. {
  19. "label": "病区B",
  20. "value": "bingquB",
  21. "children": [{
  22. "label": "科室B-A",
  23. "value": "keshiBA"
  24. }, {
  25. "label": "科室B-B",
  26. "value": "keshiBB"
  27. }, {
  28. "label": "科室B-C",
  29. "value": "keshiBC"
  30. }]
  31. }
  32. ]
  33. const props = {
  34. placeholders: ["病区", "科室"],
  35. allowClear: true,
  36. options,
  37. style: {
  38. widths: ["140px", "200px"],
  39. marginRight: '10px'
  40. }
  41. }
  42. export default function Test() {
  43. return <SelectCascader {...props} />
  44. }

示例代码的效果

 

欢迎在评论区提问或指正错误!

如果觉得本文对您有帮助,点个赞支持一下再走吧,感谢!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/151787
推荐阅读
相关标签
  

闽ICP备14008679号