未验证 提交 b08297ce 编写于 作者: X xiaoyatong 提交者: GitHub

fix: picker组件适配Taro的实现,修改为原生支持,解决数据量大时卡顿问题。 (#885)

上级 61ae8695
......@@ -83,11 +83,11 @@ const DatePickerDemo = () => {
},
})
const minDate = new Date(2020, 0, 1)
const maxDate = new Date(2025, 10, 1)
const minDate = new Date(2023, 0, 1)
const maxDate = new Date(2125, 10, 1)
const [desc1, setDesc1] = useState('2012年 01月 01日')
const [desc2, setDesc2] = useState('05-10')
const [desc3, setDesc3] = useState('2022-05-10 10:10')
const [desc3, setDesc3] = useState('2023-05-10 10:10')
const [desc4, setDesc4] = useState('10:10:00')
const [desc5, setDesc5] = useState('2020年 05月 10日 10:10')
const [desc6, setDesc6] = useState('10:10:00')
......
......@@ -371,16 +371,16 @@ export default App;
| title | 设置标题 | string | - |
| listData | 列表数据 | Array | `[]` |
| defaultValueData | 默认选中 | Array | `[]` |
| threeDimensional`v1.2.2` | 是否开启3D效果 | boolean | `true` |
| swipeDuration`v1.3.0` | 快速滑动时惯性滚动的时长,单位 ms | string \| number | `1000` |
| threeDimensional | 是否开启3D效果 | boolean | `true` |
| swipeDuration | 快速滑动时惯性滚动的时长,单位 ms | string \| number | `1000` |
## listData 数据结构
| 参数 | 说明 | 类型 | 默认值 |
|--------------|----------------------------------|--------|------------------|
| text`v1.2.2` | 选项的文字内容 | string \| number | |
| value`v1.2.2` | 选项对应的值,且唯一 | string \| number | |
| children`v1.2.2` | 用于级联选项 | Array | - |
| text | 选项的文字内容 | string \| number | |
| value | 选项对应的值,且唯一 | string \| number | |
| children | 用于级联选项 | Array | - |
## Events
......@@ -388,8 +388,7 @@ export default App;
| 字段 | 说明 | 回调参数 |
|----------------------| ----- | ----- |
| onConfirm | 点击确认按钮时候回调 | 返回选中值 value,选中值对象 |
| onChoose`v1.2.2 废弃` | 每一列值变更时调用 | 依次返回this、改变的列数,改变值,当前选中值 |
| onChange`v1.2.2` | 每一列值变更时调用 | 改变的列数,改变值 value,当前选中值 |
| onChange | 每一列值变更时调用 | 改变的列数,改变值 value,当前选中值 |
| onCloseUpdate | 联动时,关闭时回调 | 当前选中值,依次返回this |
| onClose | 关闭时触发 | 返回选中值 value,选中值对象 |
......
......@@ -89,45 +89,45 @@
align-items: center;
justify-content: space-between;
padding: $picker-bar-button-padding;
}
.nut-picker__cancel-btn {
color: $picker-cancel-color;
font-size: $picker-bar-cancel-font-size;
}
.nut-picker__cancel-btn {
color: $picker-cancel-color;
font-size: $picker-bar-cancel-font-size;
}
.nut-picker__confirm-btn {
color: $picker-ok-color;
font-size: $picker-bar-ok-font-size;
}
.nut-picker__confirm-btn {
color: $picker-ok-color;
font-size: $picker-bar-ok-font-size;
}
.nut-picker__title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
color: $picker-bar-title-color;
font-size: $picker-bar-title-font-size;
font-weight: $picker-bar-title-font-weight;
}
.nut-picker__title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
color: $picker-bar-title-color;
font-size: $picker-bar-title-font-size;
font-weight: $picker-bar-title-font-weight;
}
.nut-picker__panel {
display: flex;
position: relative;
}
.nut-picker-indicator {
position: absolute;
top: 108px;
height: $picker-item-height;
width: 100%;
border: $picker-item-active-line-border;
border-left: 0;
border-right: 0;
color: $picker-item-text-color;
font-size: $picker-item-text-font-size;
z-index: 3;
}
.nut-picker-indicator {
position: absolute;
top: 108px;
height: $picker-item-height;
width: 100%;
border: $picker-item-active-line-border;
border-left: 0;
border-right: 0;
color: $picker-item-text-color;
font-size: $picker-item-text-font-size;
z-index: 3;
}
.nut-picker-list {
......@@ -136,83 +136,89 @@
height: $picker-list-height;
overflow: hidden;
text-align: center;
}
.nut-picker-list-panel {
transform-style: preserve-3d;
}
.nut-picker-list-panel {
transform-style: preserve-3d;
}
.nut-picker-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: $picker-mask-bg-img;
background-position: top, bottom;
background-size: 100% 108px;
background-repeat: no-repeat;
z-index: 3;
}
.nut-picker-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: $picker-mask-bg-img;
background-position: top, bottom;
background-size: 100% 108px;
background-repeat: no-repeat;
z-index: 3;
}
.nut-picker-content,
.nut-picker-roller {
position: absolute;
top: 108px;
width: 100%;
height: $picker-item-height;
}
.picker-view-panel {
height: $picker-list-height;
flex-grow: 1;
}
.nut-picker-content {
background: #fff;
z-index: 2;
overflow: hidden;
}
.nut-picker-content,
.nut-picker-roller {
position: absolute;
top: 108px;
width: 100%;
height: $picker-item-height;
}
.nut-picker-item,
.nut-picker-roller-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: $picker-item-height;
line-height: $picker-item-height;
color: $picker-item-text-color;
font-size: $picker-item-text-font-size;
text-align: center;
}
.nut-picker-content {
background: #fff;
z-index: 2;
overflow: hidden;
}
.nut-picker-item {
font-size: 16px;
background: #fff;
}
.nut-picker-item,
.nut-picker-roller-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: $picker-item-height;
line-height: $picker-item-height;
color: $picker-item-text-color;
font-size: $picker-item-text-font-size;
text-align: center;
}
.nut-picker-roller {
z-index: 1;
transform-style: preserve-3d;
.nut-picker-item {
font-size: 16px;
background: #fff;
}
.nut-picker-roller-item {
backface-visibility: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
position: absolute;
top: 0;
width: 100%;
color: $gray1;
}
.nut-picker-roller {
z-index: 1;
transform-style: preserve-3d;
}
.nut-picker-roller-item-title {
display: block;
width: 100%;
height: 36px;
line-height: 36px;
color: $gray1;
font-size: $picker-bar-title-font-size;
}
.nut-picker-roller-item {
backface-visibility: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
position: absolute;
top: 0;
width: 100%;
color: $gray1;
}
.nut-picker-roller-item-hidden {
visibility: hidden;
opacity: 0;
}
}
.nut-picker-roller-item-title {
display: block;
width: 100%;
height: 36px;
line-height: 36px;
text-align: center;
color: $gray1;
font-size: $picker-bar-title-font-size;
}
.nut-picker-roller-item-hidden {
visibility: hidden;
opacity: 0;
}
.nut-picker-placeholder {
......
......@@ -5,7 +5,8 @@ import React, {
RefObject,
ForwardRefRenderFunction,
} from 'react'
import { View } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { View, PickerView, PickerViewColumn } from '@tarojs/components'
import Popup from '@/packages/popup/index.taro'
import PickerSlot from './pickerSlot.taro'
import useRefs from '@/utils/useRefs'
......@@ -75,15 +76,90 @@ const InternalPicker: ForwardRefRenderFunction<unknown, Partial<PickerProps>> =
const [refs, setRefs] = useRefs()
const [columnsList, setColumnsList] = useState<PickerOption[][]>([]) // 格式化后每一列的数据
const b = bem('picker')
const isConfirmEvent = useRef(false)
// 级联数据格式化
const formatCascade = (
columns: PickerOption[],
values: (number | string)[]
) => {
const formatted: PickerOption[][] = []
let cursor: PickerOption = {
text: '',
value: '',
children: columns,
}
let columnIndex = 0
while (cursor && cursor.children) {
const options: PickerOption[] = cursor.children
const value = values[columnIndex]
let index = options.findIndex(
(columnItem) => columnItem.value === value
)
if (index === -1) index = 0
cursor = cursor.children[index]
columnIndex++
formatted.push(options)
}
return formatted
}
// 每一列的类型
const columnsType = () => {
const firstColumn: PickerOption | PickerOption[] = listData[0]
if (firstColumn) {
if (Array.isArray(firstColumn)) {
return 'multiple'
}
if ('children' in firstColumn) {
return 'cascade'
}
}
return 'single'
}
// 传入的数据格式化
const normalListData = () => {
const type = columnsType()
switch (type) {
case 'multiple':
return listData
case 'cascade':
// 级联数据处理
return formatCascade(listData as PickerOption[], chooseValueData)
default:
return [listData]
}
}
const init = () => {
// const data: (string | number)[] = []
const normalData: PickerOption[][] = normalListData() as PickerOption[][]
setColumnsList(normalData)
// normalData.length > 0 &&
// normalData.map((item) => {
// item[0] && data.push(item[0].value)
// return item
// })
// 为何要重置呢?
// if (!defaultValueData && chooseValueData.length === 0) {
// setchooseValueData([...data])
// }
}
// 列表格式修改
useEffect(() => {
init()
}, [listData])
// 默认值修改
useEffect(() => {
if (
defaultValueData &&
defaultValueData.length !== 0 &&
defaultValueData.toString() !== chooseValueData.toString()
defaultValueData.toString() !== chooseValueData.toString() &&
!currentValue.length
) {
const data = [...defaultValueData]
setchooseValueData(data)
......@@ -91,46 +167,6 @@ const InternalPicker: ForwardRefRenderFunction<unknown, Partial<PickerProps>> =
}
}, [defaultValueData])
// 选中值进行修改
useEffect(() => {
onChange && onChange(columnIndex, chooseValueData, selectedOptions())
if (isConfirmEvent.current) {
isConfirmEvent.current = false
onConfirm && onConfirm(chooseValueData, selectedOptions())
}
}, [chooseValueData])
// 列表格式修改
useEffect(() => {
init()
}, [listData])
const closeActionSheet = () => {
onClose && onClose(chooseValueData, selectedOptions())
onCloseUpdate &&
onCloseUpdate(chooseValueData, selectedOptions(), pickerRef)
}
// 点击确定
const confirm = () => {
let movings = false
refs.forEach((_ref: any) => {
if (_ref.moving) movings = true
_ref.stopMomentum()
})
if (movings) {
isConfirmEvent.current = true
} else {
onConfirm && onConfirm(chooseValueData, selectedOptions())
}
onClose && onClose(chooseValueData, selectedOptions())
setTimeout(() => {
isConfirmEvent.current = false
}, 0)
}
const selectedOptions = () => {
const optins: PickerOption[] = []
columnsList.map((column: PickerOption[], index: number) => {
......@@ -143,13 +179,27 @@ const InternalPicker: ForwardRefRenderFunction<unknown, Partial<PickerProps>> =
} else {
column[0] && optins.push(column[0])
}
return column
})
return optins
}
// 选中值进行修改
useEffect(() => {
Taro.getEnv() !== 'WEB' && setCurrentValue(defaultValuesConvert())
onChange && onChange(columnIndex, chooseValueData, selectedOptions())
if (isConfirmEvent.current) {
isConfirmEvent.current = false
onConfirm && onConfirm(chooseValueData, selectedOptions())
}
}, [chooseValueData])
const closeActionSheet = () => {
onClose && onClose(chooseValueData, selectedOptions())
onCloseUpdate &&
onCloseUpdate(chooseValueData, selectedOptions(), pickerRef)
}
// 选择每一列的数据
const chooseItem = (option: PickerOption, columnIndex: number) => {
if (option && Object.keys(option).length) {
......@@ -165,15 +215,13 @@ const InternalPicker: ForwardRefRenderFunction<unknown, Partial<PickerProps>> =
chooseValueData[index + 1] = cursor.children[0].value
setchooseValueData([...chooseValueData])
index++
const cc = cursor.children[0]
cursor = cc
cursor = cursor.children[0]
}
// 当前改变列的下一列 children 值为空
if (cursor && cursor.children) {
chooseValueData[index + 1] = ''
setchooseValueData([...chooseValueData])
}
setColumnsList(normalListData() as PickerOption[][])
} else {
setchooseValueData((data) => {
......@@ -191,80 +239,23 @@ const InternalPicker: ForwardRefRenderFunction<unknown, Partial<PickerProps>> =
}
}
}
// 传入的数据格式化
const normalListData = () => {
const type = columnsType()
switch (type) {
case 'multiple':
return listData
case 'cascade':
// 级联数据处理
return formatCascade(listData as PickerOption[], chooseValueData)
default:
return [listData]
}
}
// 每一列的类型
const columnsType = () => {
const firstColumn: PickerOption | PickerOption[] = listData[0]
if (firstColumn) {
if (Array.isArray(firstColumn)) {
return 'multiple'
}
if ('children' in firstColumn) {
return 'cascade'
}
}
return 'single'
}
// 级联数据格式化
const formatCascade = (
columns: PickerOption[],
defaultValues: (number | string)[]
) => {
const formatted: PickerOption[][] = []
let cursor: PickerOption = {
text: '',
value: '',
children: columns,
}
let columnIndex = 0
while (cursor && cursor.children) {
const options: PickerOption[] = cursor.children
const value = defaultValues[columnIndex]
let index = options.findIndex(
(columnItem) => columnItem.value === value
)
if (index === -1) index = 0
cursor = cursor.children[index]
columnIndex++
formatted.push(options)
}
return formatted
}
const init = () => {
const data: (string | number)[] = []
const normalData: PickerOption[][] = normalListData() as PickerOption[][]
setColumnsList(normalData)
normalData.length > 0 &&
normalData.map((item) => {
item[0] && data.push(item[0].value)
return item
})
if (!defaultValueData && chooseValueData.length === 0) {
setchooseValueData([...data])
// 点击确定
const confirm = () => {
let movings = false
refs.forEach((_ref: any) => {
if (_ref.moving) movings = true
_ref.stopMomentum()
})
if (movings) {
isConfirmEvent.current = true
} else {
onConfirm && onConfirm(chooseValueData, selectedOptions())
}
onClose && onClose(chooseValueData, selectedOptions())
setTimeout(() => {
isConfirmEvent.current = false
}, 0)
}
const renderToolbar = () => {
......@@ -280,6 +271,58 @@ const InternalPicker: ForwardRefRenderFunction<unknown, Partial<PickerProps>> =
</div>
)
}
const [currentValue, setCurrentValue] = useState<number[]>([])
const [pickingStatus, setPickingStatus] = useState(false)
const defaultValuesConvert = () => {
const defaultIndexs: number[] = []
if (chooseValueData.length > 0) {
chooseValueData.forEach((value, index) => {
for (let i = 0; i < columnsList[index].length; i++) {
if (columnsList[index][i].value === value) {
defaultIndexs.push(i)
break
}
}
})
} else if (columnsList && columnsList.length > 0) {
columnsList.forEach((item) => {
defaultIndexs.push(0)
item.length > 0 && chooseValueData.push(item[0].value)
})
}
return defaultIndexs
}
const pickerStart = () => {
setPickingStatus(true)
}
const pickerEnd = () => {
setPickingStatus(false)
}
const pickerChange = (data: any) => {
const prevDefaultValue = currentValue
let changeIndex = 0
console.log('chage', data)
// 判断变化的是第几个
const list = data.detail.value
for (let i = 0, len = list.length; i < len; i++) {
if (prevDefaultValue[i] !== list[i]) {
changeIndex = i
break
}
}
// 选择的是哪个 option
chooseItem(
columnsList[changeIndex][data.detail.value[changeIndex]],
changeIndex
)
}
return (
<Popup
visible={isVisible}
......@@ -296,23 +339,52 @@ const InternalPicker: ForwardRefRenderFunction<unknown, Partial<PickerProps>> =
>
{renderToolbar()}
<div className={b('panel')} ref={pickerRef}>
{columnsList?.map((item, index) => {
return (
<PickerSlot
ref={setRefs(index)}
defaultValue={chooseValueData?.[index]}
listData={item}
threeDimensional={threeDimensional}
chooseItem={(value: PickerOption, index: number) =>
chooseItem(value, index)
}
swipeDuration={swipeDuration}
key={index}
keyIndex={index}
itemShow={isVisible}
/>
)
})}
{Taro.getEnv() === 'WEB' ? (
columnsList?.map((item, index) => {
return (
<PickerSlot
ref={setRefs(index)}
defaultValue={chooseValueData?.[index]}
listData={item}
threeDimensional={threeDimensional}
chooseItem={(value: PickerOption, index: number) =>
chooseItem(value, index)
}
swipeDuration={swipeDuration}
key={index}
keyIndex={index}
itemShow={isVisible}
/>
)
})
) : (
<PickerView
ref={pickerRef}
value={currentValue}
immediateChange
onPickStart={pickerStart}
onChange={pickerChange}
onPickEnd={pickerEnd}
className="picker-view-panel"
>
{columnsList?.map((column, index) => {
return (
<PickerViewColumn key={`col${index}`}>
{column.map((item, index) => {
return (
<View
key={item.value || index}
className="nut-picker-roller-item-title"
>
<>{item.text || item}</>
</View>
)
})}
</PickerViewColumn>
)
})}
</PickerView>
)}
</div>
</View>
</Popup>
......
......@@ -43,7 +43,6 @@ const InternalPickerSlot: ForwardRefRenderFunction<
const INERTIA_TIME = 300
const INERTIA_DISTANCE = 15
const [currIndex, setCurrIndex] = useState(1)
// let lineSpacing = 36
const lineSpacing = useRef(36)
const [touchTime, setTouchTime] = useState(0)
......@@ -52,7 +51,7 @@ const InternalPickerSlot: ForwardRefRenderFunction<
const moving = useRef(false)
let timer: number | undefined
const listbox = useRef<any>(null)
const listRef = useRef<any>(null)
const rollerRef = useRef<any>(null)
const pickerSlotRef = useRef<any>(null)
......@@ -79,10 +78,8 @@ const InternalPickerSlot: ForwardRefRenderFunction<
if (type !== 'end') {
nTime = 0
}
setTouchTime(nTime)
setTouchDeg(deg)
setScrollDistance(translateY)
}
......@@ -105,7 +102,6 @@ const InternalPickerSlot: ForwardRefRenderFunction<
}deg`
setTransform(endMove, type, time, deg)
setCurrIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1)
} else {
let deg = 0
......@@ -114,7 +110,6 @@ const InternalPickerSlot: ForwardRefRenderFunction<
// picker 滚动的最大角度
const maxDeg = (listData.length + 1) * rotation
const minDeg = 0
deg = Math.min(Math.max(currentDeg, minDeg), maxDeg)
if (minDeg < deg && deg < maxDeg) {
......@@ -159,7 +154,6 @@ const InternalPickerSlot: ForwardRefRenderFunction<
} else {
setMove(move, 'end')
}
setTimeout(() => {
touch.reset()
}, 0)
......@@ -225,7 +219,7 @@ const InternalPickerSlot: ForwardRefRenderFunction<
}
const getReference = async () => {
const refe = await getRectByTaro(listbox?.current)
const refe = await getRectByTaro(listRef?.current)
lineSpacing.current = refe.height ? refe.height : 36
modifyStatus(true)
}
......@@ -317,9 +311,8 @@ const InternalPickerSlot: ForwardRefRenderFunction<
)
})}
</div>
<div className="nut-picker-mask" />
<div className="nut-picker-indicator" ref={listbox} />
<div className="nut-picker-indicator" ref={listRef} />
</div>
)
}
......
......@@ -31,7 +31,6 @@ const InternalPickerSlot: ForwardRefRenderFunction<
} = props
const touch = useTouch()
const DEFAULT_DURATION = 200
// 触发惯性滑动条件:
// 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move
......@@ -46,7 +45,6 @@ const InternalPickerSlot: ForwardRefRenderFunction<
const moving = useRef(false)
let timer: number | undefined
const listRef = useRef<any>(null)
const rollerRef = useRef<any>(null)
const pickerSlotRef = useRef<any>(null)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册