diff --git a/src/app.tsx b/src/app.tsx index 3024735..6d2e71d 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -7,14 +7,14 @@ class App extends Taro.Component { pages: [ 'pages/Home', // @index('./pages/*[!Home].tsx', (pp, cc) => `'${pp.path.replace(/^\.\//, '')}',`) - // 'pages/DatePicker', - // 'pages/ECharts', + 'pages/DatePicker', + 'pages/ECharts', 'pages/Picker', 'pages/PickerView', 'pages/Popup', - // 'pages/SinglePicker', + 'pages/SinglePicker', 'pages/Sticky', - // 'pages/TimePicker', + 'pages/TimePicker', // 'pages/Transition', // @endindex ], diff --git a/src/components/DatePicker/index.tsx b/src/components/DatePicker/index.tsx index ca0d998..ca8b66e 100644 --- a/src/components/DatePicker/index.tsx +++ b/src/components/DatePicker/index.tsx @@ -1,10 +1,10 @@ import dayjs from 'dayjs' -import MPicker from '../Picker' -import Taro from '@tarojs/taro' -import { CascadedData } from '../PickerView' -import { component } from '../component' -import { MDatePickerProps } from './props' +import MPicker, { MPickerProps } from '../Picker' +import Taro, { useEffect, useState } from '@tarojs/taro' +import { functionalComponent } from '../component' +import { MDatePickerDefaultProps, MDatePickerProps } from './props' import { memoize } from 'vtils' +import { MPickerCascadedData } from '../Picker/types' const getDaysInMonth = memoize( (month: number, year: number) => { @@ -16,162 +16,130 @@ const getDaysInMonth = memoize( }, ) -/** - * 日期选择器组件。 - * - * @example - * - * ```jsx - * this.setState({ selectedDate })}> - * 选择日期 - * - * ``` - */ -class MDatePicker extends component({ - props: MDatePickerProps, - state: { - localData: [] as CascadedData, - localSelectedIndexes: [] as number[], - }, -}) { - lastUpdateAt: number = 0 - - lastSelectedIndexes: number[] = [] +function MDatePicker(props: MDatePickerProps) { + const [data, setData] = useState([]) + const [selectedIndexes, setSelectedIndexes] = useState([]) - componentDidMount() { - this.updateLocalState(this.props) - } + useEffect( + () => { + let reject: boolean | void = false - componentWillReceiveProps(nextProps: MDatePicker['props']) { - // perf: 极短时间内的行为应是由子组件触发的父组件更新 - if (this.lastUpdateAt && (Date.now() - this.lastUpdateAt < 60)) { - this.setState({ - localSelectedIndexes: this.lastSelectedIndexes, - }) - } else { - this.updateLocalState(nextProps) - } - } + const startDate = dayjs(props.startDate) + const startYear = startDate.year() + const startMonth = startDate.month() + 1 + const startDay = startDate.date() - updateLocalState(props: MDatePicker['props']) { - let reject: boolean | void = false + const endDate = dayjs(props.endDate) + const endYear = endDate.year() + const endMonth = endDate.month() + 1 + const endDay = endDate.date() - const startDate = dayjs(props.startDate) - const startYear = startDate.year() - const startMonth = startDate.month() + 1 - const startDay = startDate.date() + const useRawYearValue = props.formatYear == null + const useRawMonthValue = props.formatMonth == null + const useRawDayValue = props.formatDay == null - const endDate = dayjs(props.endDate) - const endYear = endDate.year() - const endMonth = endDate.month() + 1 - const endDay = endDate.date() - - const useRawYearValue = props.formatYear == null - const useRawMonthValue = props.formatMonth == null - const useRawDayValue = props.formatDay == null - - const yearList: CascadedData = [] - const selectedIndexes: number[] = [] - for (let year = startYear; year <= endYear; year++) { - reject = props.filterYear && props.filterYear({ year: year }) - if (reject !== true) { - if (year === props.selectedDate[0]) { - selectedIndexes[0] = yearList.length - } - const monthList: CascadedData = [] - yearList.push({ - label: String(useRawYearValue ? year : props.formatYear({ year })), - value: year, - children: monthList, - }) - const months = year === endYear ? endMonth : 12 - for (let month = (year === startYear ? startMonth : 1); month <= months; month++) { - reject = props.filterMonth && props.filterMonth({ - year: year, - month: month, + const yearList: MPickerCascadedData = [] + const selectedIndexes: number[] = [] + for (let year = startYear; year <= endYear; year++) { + reject = props.filterYear && props.filterYear({ year: year }) + if (reject !== true) { + if (year === props.selectedDate[0]) { + selectedIndexes[0] = yearList.length + } + const monthList: MPickerCascadedData = [] + yearList.push({ + label: String(useRawYearValue ? year : props.formatYear({ year })), + value: year, + children: monthList, }) - if (reject !== true) { - if (month === props.selectedDate[1]) { - selectedIndexes[1] = monthList.length - } - const dayList: CascadedData = [] - monthList.push({ - label: String(useRawMonthValue ? month : props.formatMonth({ year, month })), - value: month, - children: dayList, + const months = year === endYear ? endMonth : 12 + for (let month = (year === startYear ? startMonth : 1); month <= months; month++) { + reject = props.filterMonth && props.filterMonth({ + year: year, + month: month, }) - const days = year === endYear && month === endMonth ? endDay : getDaysInMonth(month, year) - for (let day = (year === startYear && month === startMonth ? startDay : 1); day <= days; day++) { - reject = props.filterDay && props.filterDay({ - year: year, - month: month, - day: day, + if (reject !== true) { + if (month === props.selectedDate[1]) { + selectedIndexes[1] = monthList.length + } + const dayList: MPickerCascadedData = [] + monthList.push({ + label: String(useRawMonthValue ? month : props.formatMonth({ year, month })), + value: month, + children: dayList, }) - if (reject !== true) { - if (day === props.selectedDate[2]) { - selectedIndexes[2] = dayList.length - } - dayList.push({ - label: String(useRawDayValue ? day : props.formatDay({ year, month, day })), - value: day, + const days = year === endYear && month === endMonth ? endDay : getDaysInMonth(month, year) + for (let day = (year === startYear && month === startMonth ? startDay : 1); day <= days; day++) { + reject = props.filterDay && props.filterDay({ + year: year, + month: month, + day: day, }) - } else { - reject = false + if (reject !== true) { + if (day === props.selectedDate[2]) { + selectedIndexes[2] = dayList.length + } + dayList.push({ + label: String(useRawDayValue ? day : props.formatDay({ year, month, day })), + value: day, + }) + } else { + reject = false + } } + selectedIndexes[2] = selectedIndexes[2] == null ? 0 : selectedIndexes[2] + } else { + reject = false } - selectedIndexes[2] = selectedIndexes[2] == null ? 0 : selectedIndexes[2] - } else { - reject = false } + selectedIndexes[1] = selectedIndexes[1] == null ? 0 : selectedIndexes[1] + } else { + reject = false } - selectedIndexes[1] = selectedIndexes[1] == null ? 0 : selectedIndexes[1] - } else { - reject = false } - } - selectedIndexes[0] = selectedIndexes[0] == null ? 0 : selectedIndexes[0] + selectedIndexes[0] = selectedIndexes[0] == null ? 0 : selectedIndexes[0] - this.setState({ - localData: yearList, - localSelectedIndexes: selectedIndexes, - }) - } + setData(yearList) + setSelectedIndexes(selectedIndexes) + }, + [ + props.startDate, + props.endDate, + props.filterYear, + props.filterMonth, + props.filterDay, + props.formatYear, + props.formatMonth, + props.formatDay, + ], + ) - handleConfirm: MPicker['props']['onConfirm'] = selectedIndexes => { - const { localData } = this.state + const handleConfirm: MPickerProps['onConfirm'] = selectedIndexes => { const selectedDate: number[] = [] - let list = localData - const n = Math.min(selectedIndexes.length, 3) - for (let i = 0; i < n; i++) { + for ( + let i = 0, + n = Math.min(selectedIndexes.length, 3), + list = data; + i < n; + i++ + ) { if (!list[selectedIndexes[i]]) break selectedDate.push(list[selectedIndexes[i]].value) list = list[selectedIndexes[i]].children if (!list) break } - this.lastUpdateAt = Date.now() - this.lastSelectedIndexes = selectedIndexes - this.props.onConfirm(selectedDate) + props.onConfirm(selectedDate) } - render() { - const { - localData, - localSelectedIndexes, - } = this.state - - return !localData.length ? this.props.children : ( - - {this.props.children} - - ) - } + return !data.length ? props.children : ( + + {props.children} + + ) } -export default MDatePicker +export default functionalComponent(MDatePickerDefaultProps)(MDatePicker) diff --git a/src/components/DatePicker/props.ts b/src/components/DatePicker/props.ts index ddc7271..2145e03 100644 --- a/src/components/DatePicker/props.ts +++ b/src/components/DatePicker/props.ts @@ -1,109 +1,116 @@ -import { MPickerProps } from '../Picker/props' +import { createProps, RequiredProp } from '../component' +import { MPickerDefaultProps } from '../Picker/props' import { noop, omit } from 'vtils' -import { RequiredProp } from '../component' const currentYear = new Date().getFullYear() -export const MDatePickerProps = { - ...omit(MPickerProps, ['data', 'selectedIndexes', 'onConfirm']), +export const MDatePickerDefaultProps = { + ...omit( + MPickerDefaultProps, + ['data', 'selectedIndexes', 'onConfirm'], + ), - /** - * 开始日期。可以是: - * - * - 字符串: `2019-2-03` - * - ISO 8601 字符串:`2019-07-10T08:30:19.710Z` - * - Unix 时间戳: `1554916837` - * - * @default `${currentYear - 10}-1-1` - */ - startDate: `${currentYear - 10}-1-1` as string | number, + ...createProps({ + /** + * 开始日期。可以是: + * + * - 字符串: `2019-2-03` + * - ISO 8601 字符串:`2019-07-10T08:30:19.710Z` + * - Unix 时间戳: `1554916837` + * + * @default `${currentYear - 10}-1-1` + */ + startDate: `${currentYear - 10}-1-1` as string | number, - /** - * 结束日期。可以是: - * - * - 字符串: `2019-2-03` - * - ISO 8601 字符串:`2019-07-10T08:30:19.710Z` - * - Unix 时间戳: `1554916837` - * - * @default `${currentYear + 10}-12-31` - */ - endDate: `${currentYear + 10}-12-31` as string | number, + /** + * 结束日期。可以是: + * + * - 字符串: `2019-2-03` + * - ISO 8601 字符串:`2019-07-10T08:30:19.710Z` + * - Unix 时间戳: `1554916837` + * + * @default `${currentYear + 10}-12-31` + */ + endDate: `${currentYear + 10}-12-31` as string | number, - /** - * 格式化年份。 - * - * @default params => params.year - */ - formatYear: undefined as (params: { - /** 年 */ - year: number, - }) => string | number, + /** + * 格式化年份。 + * + * @default params => params.year + */ + formatYear: undefined as (params: { + /** 年 */ + year: number, + }) => string | number, - /** - * 格式化月份。 - * - * @default params => params.month - */ - formatMonth: undefined as (params: { - /** 年 */ - year: number, - /** 月 */ - month: number, - }) => string | number, + /** + * 格式化月份。 + * + * @default params => params.month + */ + formatMonth: undefined as (params: { + /** 年 */ + year: number, + /** 月 */ + month: number, + }) => string | number, - /** - * 格式化天数。 - * - * @default params => params.day - */ - formatDay: undefined as (params: { - /** 年 */ - year: number, - /** 月 */ - month: number, - /** 日 */ - day: number, - }) => string | number, + /** + * 格式化天数。 + * + * @default params => params.day + */ + formatDay: undefined as (params: { + /** 年 */ + year: number, + /** 月 */ + month: number, + /** 日 */ + day: number, + }) => string | number, - /** - * 年份过滤器,返回 `true` 可过滤掉传入的年份。 - */ - filterYear: undefined as (params: { - /** 年 */ - year: number, - }) => boolean | void, + /** + * 年份过滤器,返回 `true` 可过滤掉传入的年份。 + */ + filterYear: undefined as (params: { + /** 年 */ + year: number, + }) => boolean | void, - /** - * 月份过滤器,返回 `true` 可过滤掉传入的月份。 - */ - filterMonth: undefined as (params: { - /** 年 */ - year: number, - /** 月 */ - month: number, - }) => boolean | void, + /** + * 月份过滤器,返回 `true` 可过滤掉传入的月份。 + */ + filterMonth: undefined as (params: { + /** 年 */ + year: number, + /** 月 */ + month: number, + }) => boolean | void, - /** - * 日期过滤器,返回 `true` 可过滤掉传入的日期。 - */ - filterDay: undefined as (params: { - /** 年 */ - year: number, - /** 月 */ - month: number, - /** 日 */ - day: number, - }) => boolean | void, + /** + * 日期过滤器,返回 `true` 可过滤掉传入的日期。 + */ + filterDay: undefined as (params: { + /** 年 */ + year: number, + /** 月 */ + month: number, + /** 日 */ + day: number, + }) => boolean | void, - /** - * 选中的日期。 - */ - selectedDate: [] as any as RequiredProp, + /** + * 选中的日期。 + */ + selectedDate: [] as any as RequiredProp, - /** - * 点击确定事件。 - * - * @default () => {} - */ - onConfirm: noop as any as RequiredProp<(selectedDate: number[]) => void>, + /** + * 点击确定事件。 + * + * @default () => {} + */ + onConfirm: noop as any as RequiredProp<(selectedDate: number[]) => void>, + }), } + +export type MDatePickerProps = typeof MDatePickerDefaultProps diff --git a/src/components/ECharts/index.tsx b/src/components/ECharts/index.tsx index bf59e5d..606c32a 100644 --- a/src/components/ECharts/index.tsx +++ b/src/components/ECharts/index.tsx @@ -1,38 +1,39 @@ -import Taro from '@tarojs/taro' +import Taro, { useEffect, useRef } from '@tarojs/taro' import { Canvas } from '@tarojs/components' import { CanvasAdapter, wrapTouch } from './utils' -import { component } from '../component' -import { MEChartsProps } from './props' +import { functionalComponent } from '../component' +import { MEChartsDefaultProps, MEChartsProps } from './props' const canvasId = 'canvas' -export default class MECharts extends component({ - props: MEChartsProps, -}) { - chart: any +function MECharts(props: MEChartsProps) { + const chart = useRef(null) - componentDidMount() { - const { getECharts, options } = this.props - const echarts = getECharts() - const ctx = Taro.createCanvasContext(canvasId, this.$scope) - const canvas = new CanvasAdapter(ctx) - ;(echarts as any).setCanvasCreator(() => canvas) - Taro.createSelectorQuery() - .in(this.$scope) - .select('.m-echarts__canvas') - .boundingClientRect(res => { - this.chart = (echarts as any).init(canvas, null, res) - this.chart.setOption(options) - canvas.setChart(this.chart) - }) - .exec() - } + useEffect( + () => { + const { getECharts, options } = props + const echarts = getECharts() + const ctx = Taro.createCanvasContext(canvasId, this.$scope) + const canvas = new CanvasAdapter(ctx) + ;(echarts as any).setCanvasCreator(() => canvas) + Taro.createSelectorQuery() + .in(this.$scope) + .select('.m-echarts__canvas') + .boundingClientRect(res => { + chart.current = (echarts as any).init(canvas, null, res) + chart.current.setOption(options) + canvas.setChart(chart.current) + }) + .exec() + }, + [], + ) - handleTouchStart(e: any) { - if (this.props.disableTouch) return - if (this.chart && e.touches.length > 0) { + function handleTouchStart(e: any) { + if (props.disableTouch) return + if (chart.current && e.touches.length > 0) { const touch = e.touches[0] - const handler = this.chart.getZr().handler + const handler = chart.current.getZr().handler handler.dispatch('mousedown', { zrX: touch.x, zrY: touch.y, @@ -45,11 +46,11 @@ export default class MECharts extends component({ } } - handleTouchMove(e: any) { - if (this.props.disableTouch) return - if (this.chart && e.touches.length > 0) { + function handleTouchMove(e: any) { + if (props.disableTouch) return + if (chart.current && e.touches.length > 0) { const touch = e.touches[0] - const handler = this.chart.getZr().handler + const handler = chart.current.getZr().handler handler.dispatch('mousemove', { zrX: touch.x, zrY: touch.y, @@ -58,11 +59,11 @@ export default class MECharts extends component({ } } - handleTouchEnd(e: any) { - if (this.props.disableTouch) return - if (this.chart) { + function handleTouchEnd(e: any) { + if (props.disableTouch) return + if (chart.current) { const touch = e.changedTouches ? e.changedTouches[0] : {} - const handler = this.chart.getZr().handler + const handler = chart.current.getZr().handler handler.dispatch('mouseup', { zrX: touch.x, zrY: touch.y, @@ -75,18 +76,18 @@ export default class MECharts extends component({ } } - render() { - const { className } = this.props - - return ( - - {this.props.children} - - ) - } + return ( + + {props.children} + + ) } + +export { MEChartsProps } + +export default functionalComponent(MEChartsDefaultProps)(MECharts) diff --git a/src/components/ECharts/props.ts b/src/components/ECharts/props.ts index 096a953..c2cd2bc 100644 --- a/src/components/ECharts/props.ts +++ b/src/components/ECharts/props.ts @@ -1,8 +1,8 @@ import * as ECharts from 'echarts' +import { createProps, RequiredProp } from '../component' import { noop } from 'vtils' -import { RequiredProp } from '../component' -export const MEChartsProps = { +export const MEChartsDefaultProps = createProps({ /** * 获取 `ECharts` 对象的函数。 * @@ -27,4 +27,6 @@ export const MEChartsProps = { * @default false */ disableTouch: false as boolean, -} +}) + +export type MEChartsProps = typeof MEChartsDefaultProps diff --git a/src/components/NavigationBar/index.tsx b/src/components/NavigationBar/index.tsx index 782834c..3e491a8 100644 --- a/src/components/NavigationBar/index.tsx +++ b/src/components/NavigationBar/index.tsx @@ -2,7 +2,7 @@ import './index.scss' import Taro, { useState } from '@tarojs/taro' import { functionalComponent } from '../component' import { last } from 'vtils' -import { NavigationBarDefaultProps, NavigationBarProps } from './props' +import { MNavigationBarDefaultProps, MNavigationBarProps } from './props' import { useCustomNavigationBarFullHeight, useDidEnter, useDidLeave } from '../../hooks' import { View } from '@tarojs/components' @@ -10,7 +10,7 @@ function onlyPath(url: string) { return url ? url.split('?')[0].replace(/^\/+/, '') : '' } -function NavigationBar(props: NavigationBarProps) { +function MNavigationBar(props: MNavigationBarProps) { const { setCustomNavigationBarFullHeight, resetCustomNavigationBarFullHeight } = useCustomNavigationBarFullHeight() const [state, setState] = useState({ verticalPadding: 0 as number, @@ -115,6 +115,6 @@ function NavigationBar(props: NavigationBarProps) { ) } -export { NavigationBarProps } +export { MNavigationBarProps } -export default functionalComponent(NavigationBarDefaultProps)(NavigationBar) +export default functionalComponent(MNavigationBarDefaultProps)(MNavigationBar) diff --git a/src/components/NavigationBar/props.ts b/src/components/NavigationBar/props.ts index 4bdde5c..98707a9 100644 --- a/src/components/NavigationBar/props.ts +++ b/src/components/NavigationBar/props.ts @@ -1,6 +1,6 @@ import { createProps, RequiredProp } from '../component' -export const NavigationBarDefaultProps = createProps({ +export const MNavigationBarDefaultProps = createProps({ /** * 小程序主页的绝对路径,可带参数。 * @@ -25,4 +25,4 @@ export const NavigationBarDefaultProps = createProps({ backgroundColor: 'auto' as string, }) -export type NavigationBarProps = typeof NavigationBarDefaultProps +export type MNavigationBarProps = typeof MNavigationBarDefaultProps diff --git a/src/components/Picker/index.tsx b/src/components/Picker/index.tsx index d785d4c..0321646 100644 --- a/src/components/Picker/index.tsx +++ b/src/components/Picker/index.tsx @@ -1,137 +1,101 @@ import MPickerView from '../PickerView' import MPopup from '../Popup' -import Taro from '@tarojs/taro' -import { component } from '../component' -import { MPickerProps } from './props' +import Taro, { useEffect, useRef, useState } from '@tarojs/taro' +import { functionalComponent } from '../component' +import { MPickerDefaultProps, MPickerProps } from './props' import { View } from '@tarojs/components' -/** - * 选择器组件。 - */ -class MPicker extends component({ - props: MPickerProps, - state: { - localVisible: false as boolean, - localSelectedIndexes: [], - }, -}) { - canClose: boolean = true +function MPicker(props: MPickerProps) { + const [visible, setVisible] = useState(false) + const [selectedIndexes, setSelectedIndexes] = useState([]) + const canClose = useRef(false) - componentWillMount() { - const { selectedIndexes } = this.props - this.setState({ - localSelectedIndexes: selectedIndexes, - }) - } - - componentWillReceiveProps({ selectedIndexes }: MPicker['props']) { - this.setState({ - localSelectedIndexes: selectedIndexes, - }) - } + useEffect( + () => { + setSelectedIndexes(props.selectedIndexes) + }, + [props.selectedIndexes], + ) - handleTriggerClick = () => { - this.setState(_ => ({ - localVisible: !_.localVisible, - })) + function handleTriggerClick() { + setVisible(!visible) } - handleVisibleChange: MPopup['props']['onVisibleChange'] = visible => { - const { selectedIndexes } = this.props - this.setState({ - localVisible: visible, - ...(visible ? {} as any : { localSelectedIndexes: selectedIndexes }), - }) + function handleVisibleChange(visible: boolean) { + setVisible(visible) + if (!visible) { + setSelectedIndexes(props.selectedIndexes) + } } - handlePickStart = () => { - this.canClose = false + function handlePickStart() { + canClose.current = false } - handlePickEnd = () => { - this.canClose = true + function handlePickEnd() { + canClose.current = true } - handlePickChange: MPickerView['props']['onChange'] = selectedIndexes => { - this.setState({ - localSelectedIndexes: selectedIndexes, - }) + function handlePickChange(selectedIndexes: number[]) { + setSelectedIndexes(selectedIndexes) } - handleCancelClick = () => { - if (!this.canClose) return - this.setState({ - localSelectedIndexes: this.props.selectedIndexes, - localVisible: false, - }, () => { - this.props.onCancel() - }) + function handleCancelClick() { + if (canClose.current) { + setSelectedIndexes(props.selectedIndexes) + setVisible(false) + props.onCancel() + } } - handleConfirmClick = () => { - if (!this.canClose) return - this.setState({ - localVisible: false, - }, () => { - this.props.onConfirm(this.state.localSelectedIndexes.slice()) - }) + function handleConfirmClick() { + if (canClose.current) { + setVisible(false) + props.onConfirm(selectedIndexes.slice()) + } } - render() { - const { - maskClosable, - noCancel, - cancelText, - confirmText, - title, - disabled, - className, - // ...props // TODO: 尚不可用 - } = this.props - - const { - localVisible, - localSelectedIndexes, - } = this.state - - return disabled ? this.props.children : ( - - - {this.props.children} - - - - - - {cancelText} - - - {title} - - - {confirmText} - + return props.disabled ? props.children : ( + + + {props.children} + + + + + + {props.cancelText} + + + {props.title} + + + {props.confirmText} - - - - ) - } + + + + + ) } -export default MPicker +export * from './types' + +export { MPickerProps } + +export default functionalComponent(MPickerDefaultProps)(MPicker) diff --git a/src/components/Picker/props.ts b/src/components/Picker/props.ts index 4967193..e6b9bdc 100644 --- a/src/components/Picker/props.ts +++ b/src/components/Picker/props.ts @@ -1,56 +1,62 @@ -import { MPickerViewProps } from '../PickerView/props' +import { createProps, RequiredProp } from '../component' +import { MPickerViewDefaultProps } from '../PickerView/props' import { noop, omit } from 'vtils' -import { RequiredProp } from '../component' - -export const MPickerProps = { - ...omit(MPickerViewProps, ['onChange', 'onPickEnd', 'onPickStart']), - - /** - * 是否可点击遮罩关闭。 - * - * @default true - */ - maskClosable: true as boolean, - - /** - * 标题。 - * - * @default '' - */ - title: '' as string, - - /** - * 是否无取消按钮。 - * - * @default false - */ - noCancel: false as boolean, - - /** - * 取消文字。 - * - * @default '取消' - */ - cancelText: '取消' as string, - - /** - * 确定文字。 - * - * @default '确定' - */ - confirmText: '确定' as string, - - /** - * 点击取消事件。 - * - * @default () => {} - */ - onCancel: noop as () => void, - - /** - * 点击确定事件。 - * - * @default () => {} - */ - onConfirm: noop as any as RequiredProp<(selectedIndexes: number[]) => void>, + +export const MPickerDefaultProps = { + ...omit( + MPickerViewDefaultProps, + ['onChange', 'onPickEnd', 'onPickStart'], + ), + ...createProps({ + /** + * 是否可点击遮罩关闭。 + * + * @default true + */ + maskClosable: true as boolean, + + /** + * 标题。 + * + * @default '' + */ + title: '' as string, + + /** + * 是否无取消按钮。 + * + * @default false + */ + noCancel: false as boolean, + + /** + * 取消文字。 + * + * @default '取消' + */ + cancelText: '取消' as string, + + /** + * 确定文字。 + * + * @default '确定' + */ + confirmText: '确定' as string, + + /** + * 点击取消事件。 + * + * @default () => {} + */ + onCancel: noop as () => void, + + /** + * 点击确定事件。 + * + * @default () => {} + */ + onConfirm: noop as any as RequiredProp<(selectedIndexes: number[]) => void>, + }), } + +export type MPickerProps = typeof MPickerDefaultProps diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts new file mode 100644 index 0000000..3be2f0c --- /dev/null +++ b/src/components/Picker/types.ts @@ -0,0 +1,28 @@ +import { MPickerViewCascadedData, MPickerViewCascadedItem, MPickerViewCascadedList, MPickerViewData, MPickerViewItem, MPickerViewList, MPickerViewNormalData, MPickerViewNormalItem, MPickerViewNormalList } from '../PickerView/types' + +/** 普通条目 */ +export type MPickerNormalItem = MPickerViewNormalItem + +/** 普通列表 */ +export type MPickerNormalList = MPickerViewNormalList + +/** 普通数据 */ +export type MPickerNormalData = MPickerViewNormalData + +/** 级联条目 */ +export type MPickerCascadedItem = MPickerViewCascadedItem + +/** 级联列表 */ +export type MPickerCascadedList = MPickerViewCascadedList + +/** 级联数据 */ +export type MPickerCascadedData = MPickerViewCascadedData + +/** 条目 */ +export type MPickerItem = MPickerViewItem + +/** 列表 */ +export type MPickerList = MPickerViewList + +/** 数据 */ +export type MPickerData = MPickerViewData diff --git a/src/components/SinglePicker/index.tsx b/src/components/SinglePicker/index.tsx index 02242ca..d0698f6 100644 --- a/src/components/SinglePicker/index.tsx +++ b/src/components/SinglePicker/index.tsx @@ -1,56 +1,45 @@ import MPicker from '../Picker' -import Taro from '@tarojs/taro' -import { component } from '../component' -import { Data, Item, MSinglePickerProps } from './props' -import { NormalData } from '../PickerView' - -export { Item, Data } - -/** - * 单项选择器组件。 - */ -class MSinglePicker extends component({ - props: MSinglePickerProps, - state: { - localData: [] as NormalData, - localSelectedIndexes: [] as number[], - }, -}) { - componentWillMount() { - const { data, selectedIndex } = this.props - this.setState({ - localData: [data], - localSelectedIndexes: [selectedIndex], - }) - } +import Taro, { useEffect, useState } from '@tarojs/taro' +import { functionalComponent } from '../component' +import { MPickerData } from '../Picker/types' +import { MPickerProps } from '../Picker/props' +import { MSinglePickerDefaultProps, MSinglePickerProps } from './props' - componentWillReceiveProps({ data, selectedIndex }: MSinglePicker['props']) { - this.setState({ - localData: [data], - localSelectedIndexes: [selectedIndex], - }) - } +function MSinglePicker(props: MSinglePickerProps) { + const [data, setData] = useState([]) + const [selectedIndexes, setSelectedIndexes] = useState([]) - handleConfirm: MPicker['props']['onConfirm'] = selectedIndexes => { - this.props.onConfirm(selectedIndexes[0]) - } + useEffect( + () => { + setData([props.data]) + }, + [props.data], + ) + + useEffect( + () => { + setSelectedIndexes([props.selectedIndex]) + }, + [props.selectedIndex], + ) - render() { - const { - localData, - localSelectedIndexes, - } = this.state - - return ( - - {this.props.children} - - ) + const handleConfirm: MPickerProps['onConfirm'] = selectedIndexes => { + props.onConfirm(selectedIndexes[0]) } + + return ( + + {props.children} + + ) } -export default MSinglePicker +export * from './types' + +export { MSinglePickerProps } + +export default functionalComponent(MSinglePickerDefaultProps)(MSinglePicker) diff --git a/src/components/SinglePicker/props.ts b/src/components/SinglePicker/props.ts index bd54b41..3eeae28 100644 --- a/src/components/SinglePicker/props.ts +++ b/src/components/SinglePicker/props.ts @@ -1,28 +1,32 @@ -import { MPickerProps } from '../Picker/props' +import { createProps, RequiredProp } from '../component' +import { MPickerDefaultProps } from '../Picker/props' +import { MSinglePickerData } from './types' import { noop, omit } from 'vtils' -import { NormalItem } from '../PickerView' -import { RequiredProp } from '../component' -export type Item = NormalItem -export type Data = Item[] +export const MSinglePickerDefaultProps = { + ...omit( + MPickerDefaultProps, + ['data', 'selectedIndexes', 'onConfirm'], + ), -export const MSinglePickerProps = { - ...omit(MPickerProps, ['data', 'selectedIndexes', 'onConfirm']), + ...createProps({ + /** + * 选项数据。 + */ + data: [] as any as RequiredProp, - /** - * 选项数据。 - */ - data: [] as any as RequiredProp, + /** + * 选中条目的索引。 + */ + selectedIndex: 0 as any as RequiredProp, - /** - * 选中条目的索引。 - */ - selectedIndex: 0 as any as RequiredProp, - - /** - * 点击确定事件。 - * - * @default () => {} - */ - onConfirm: noop as any as RequiredProp<(selectedIndex: number) => void>, + /** + * 点击确定事件。 + * + * @default () => {} + */ + onConfirm: noop as any as RequiredProp<(selectedIndex: number) => void>, + }), } + +export type MSinglePickerProps = typeof MSinglePickerDefaultProps diff --git a/src/components/SinglePicker/types.ts b/src/components/SinglePicker/types.ts new file mode 100644 index 0000000..cfcd843 --- /dev/null +++ b/src/components/SinglePicker/types.ts @@ -0,0 +1,7 @@ +import { MPickerNormalItem } from '../Picker/types' + +/** 条目 */ +export type MSinglePickerItem = MPickerNormalItem + +/** 数据 */ +export type MSinglePickerData = MPickerNormalItem[] diff --git a/src/components/TimePicker/index.tsx b/src/components/TimePicker/index.tsx index 947c6fd..57a445f 100644 --- a/src/components/TimePicker/index.tsx +++ b/src/components/TimePicker/index.tsx @@ -1,142 +1,109 @@ -import MPicker from '../Picker' -import Taro from '@tarojs/taro' -import { CascadedData } from '../PickerView' -import { component } from '../component' -import { MTimePickerProps } from './props' +import MPicker, { MPickerCascadedData, MPickerProps } from '../Picker' +import Taro, { useEffect, useState } from '@tarojs/taro' +import { functionalComponent } from '../component' +import { MTimePickerDefaultProps, MTimePickerProps } from './props' -/** - * 时间选择器组件。 - * - * @example - * - * ```jsx - * this.setState({ selectedTime })}> - * 选择时间 - * - * ``` - */ -class MTimePicker extends component({ - props: MTimePickerProps, - state: { - localData: [] as CascadedData, - localSelectedIndexes: [] as number[], - }, -}) { - lastUpdateAt: number = 0 +function MTimePicker(props: MTimePickerProps) { + const [data, setData] = useState([]) + const [selectedIndexes, setSelectedIndexes] = useState([]) - lastSelectedIndexes: number[] = [] + useEffect( + () => { + let reject: boolean | void = false - componentDidMount() { - this.updateLocalState(this.props) - } - - componentWillReceiveProps(nextProps: MTimePicker['props']) { - // perf: 极短时间内的行为应是由子组件触发的父组件更新 - if (this.lastUpdateAt && (Date.now() - this.lastUpdateAt < 60)) { - this.setState({ - localSelectedIndexes: this.lastSelectedIndexes, - }) - } else { - this.updateLocalState(nextProps) - } - } + const startTime = props.startTime.split(':') + const startHour = parseInt(startTime[0]) || 0 + const startMinute = parseInt(startTime[1]) || 0 - updateLocalState(props: MTimePicker['props']) { - let reject: boolean | void = false + const endTime = props.endTime.split(':') + const endHour = parseInt(endTime[0]) || 23 + const endMinute = parseInt(endTime[1]) || 59 - const startTime = props.startTime.split(':') - const startHour = parseInt(startTime[0]) || 0 - const startMinute = parseInt(startTime[1]) || 0 + const useRawHourValue = props.formatHour == null + const useRawMinuteValue = props.formatMinute == null - const endTime = props.endTime.split(':') - const endHour = parseInt(endTime[0]) || 23 - const endMinute = parseInt(endTime[1]) || 59 - - const useRawHourValue = props.formatHour == null - const useRawMinuteValue = props.formatMinute == null - - const hourList: CascadedData = [] - const selectedIndexes: number[] = [] - for (let hour = startHour; hour <= endHour; hour++) { - reject = props.filterHour && props.filterHour({ - hour: hour, - }) - if (reject !== true) { - if (hour === props.selectedTime[0]) { - selectedIndexes[0] = hourList.length - } - const minuteList: CascadedData = [] - hourList.push({ - label: String(useRawHourValue ? hour.toString() : props.formatHour({ hour })), - value: hour, - children: minuteList, + const hourList: MPickerCascadedData = [] + const selectedIndexes: number[] = [] + for (let hour = startHour; hour <= endHour; hour++) { + reject = props.filterHour && props.filterHour({ + hour: hour, }) - const minutes = hour === endHour ? endMinute : 59 - for (let minute = (hour === startHour ? startMinute : 0); minute <= minutes; minute++) { - reject = props.filterMinute && props.filterMinute({ - hour: hour, - minute: minute, + if (reject !== true) { + if (hour === props.selectedTime[0]) { + selectedIndexes[0] = hourList.length + } + const minuteList: MPickerCascadedData = [] + hourList.push({ + label: String(useRawHourValue ? hour.toString() : props.formatHour({ hour })), + value: hour, + children: minuteList, }) - if (reject !== true) { - if (minute === props.selectedTime[1]) { - selectedIndexes[1] = minuteList.length - } - minuteList.push({ - label: String(useRawMinuteValue ? minute.toString() : props.formatMinute({ hour, minute })), - value: minute, + const minutes = hour === endHour ? endMinute : 59 + for (let minute = (hour === startHour ? startMinute : 0); minute <= minutes; minute++) { + reject = props.filterMinute && props.filterMinute({ + hour: hour, + minute: minute, }) - } else { - reject = false + if (reject !== true) { + if (minute === props.selectedTime[1]) { + selectedIndexes[1] = minuteList.length + } + minuteList.push({ + label: String(useRawMinuteValue ? minute.toString() : props.formatMinute({ hour, minute })), + value: minute, + }) + } else { + reject = false + } } + selectedIndexes[1] = selectedIndexes[1] == null ? 0 : selectedIndexes[1] + } else { + reject = false } - selectedIndexes[1] = selectedIndexes[1] == null ? 0 : selectedIndexes[1] - } else { - reject = false } - } - selectedIndexes[0] = selectedIndexes[0] == null ? 0 : selectedIndexes[0] + selectedIndexes[0] = selectedIndexes[0] == null ? 0 : selectedIndexes[0] - this.setState({ - localData: hourList, - localSelectedIndexes: selectedIndexes, - }) - } + setData(hourList) + setSelectedIndexes(selectedIndexes) + }, + [ + props.startTime, + props.endTime, + props.filterHour, + props.filterMinute, + props.formatHour, + props.formatMinute, + ], + ) - handleConfirm: MPicker['props']['onConfirm'] = selectedIndexes => { - const { localData } = this.state + const handleConfirm: MPickerProps['onConfirm'] = selectedIndexes => { const selectedDate: number[] = [] - let list = localData - const n = Math.min(selectedIndexes.length, 2) - for (let i = 0; i < n; i++) { + for ( + let i = 0, + n = Math.min(selectedIndexes.length, 2), + list = data; + i < n; + i++ + ) { if (!list[selectedIndexes[i]]) break selectedDate.push(list[selectedIndexes[i]].value) list = list[selectedIndexes[i]].children if (!list) break } - this.lastUpdateAt = Date.now() - this.lastSelectedIndexes = selectedIndexes - this.props.onConfirm(selectedDate) + props.onConfirm(selectedDate) } - render() { - const { - localData, - localSelectedIndexes, - } = this.state - - return !localData.length ? this.props.children : ( - - {this.props.children} - - ) - } + return !data.length ? props.children : ( + + {props.children} + + ) } -export default MTimePicker +export { MTimePickerProps } + +export default functionalComponent(MTimePickerDefaultProps)(MTimePicker) diff --git a/src/components/TimePicker/props.ts b/src/components/TimePicker/props.ts index 47991ac..37f80b1 100644 --- a/src/components/TimePicker/props.ts +++ b/src/components/TimePicker/props.ts @@ -1,78 +1,85 @@ -import { MPickerProps } from '../Picker/props' +import { createProps, RequiredProp } from '../component' +import { MPickerDefaultProps } from '../Picker/props' import { noop, omit } from 'vtils' -import { RequiredProp } from '../component' -export const MTimePickerProps = { - ...omit(MPickerProps, ['data', 'selectedIndexes', 'onConfirm']), +export const MTimePickerDefaultProps = { + ...omit( + MPickerDefaultProps, + ['data', 'selectedIndexes', 'onConfirm'], + ), - /** - * 开始时间。 - * - * @default '00:00' - */ - startTime: '00:00' as string, + ...createProps({ + /** + * 开始时间。 + * + * @default '00:00' + */ + startTime: '00:00' as string, - /** - * 结束时间。 - * - * @default '23:59' - */ - endTime: `23:59` as string, + /** + * 结束时间。 + * + * @default '23:59' + */ + endTime: `23:59` as string, - /** - * 格式化小时。 - * - * @default params => params.hour - */ - formatHour: undefined as (params: { - /** 时 */ - hour: number, - }) => string | number, + /** + * 格式化小时。 + * + * @default params => params.hour + */ + formatHour: undefined as (params: { + /** 时 */ + hour: number, + }) => string | number, - /** - * 格式化分钟。 - * - * @default params => params.minute - */ - formatMinute: undefined as (params: { - /** 时 */ - hour: number, - /** 分 */ - minute: number, - }) => string | number, + /** + * 格式化分钟。 + * + * @default params => params.minute + */ + formatMinute: undefined as (params: { + /** 时 */ + hour: number, + /** 分 */ + minute: number, + }) => string | number, - /** - * 小时过滤器,返回 `true` 可过滤掉传入的小时。 - */ - filterHour: undefined as (params: { - /** 时 */ - hour: number, - }) => boolean | void, + /** + * 小时过滤器,返回 `true` 可过滤掉传入的小时。 + */ + filterHour: undefined as (params: { + /** 时 */ + hour: number, + }) => boolean | void, - /** - * 分钟过滤器,返回 `true` 可过滤掉传入的分钟。 - */ - filterMinute: undefined as (params: { - /** 时 */ - hour: number, - /** 分 */ - minute: number, - }) => boolean | void, + /** + * 分钟过滤器,返回 `true` 可过滤掉传入的分钟。 + */ + filterMinute: undefined as (params: { + /** 时 */ + hour: number, + /** 分 */ + minute: number, + }) => boolean | void, - /** - * 选中的时间。 - * - * @example - * - * [20, 5] // => 20 时 5 分 - * [0, 20] // => 0 时 20 分 - */ - selectedTime: [] as any as RequiredProp, + /** + * 选中的时间。 + * + * @example + * + * [20, 5] // => 20 时 5 分 + * [0, 20] // => 0 时 20 分 + */ + selectedTime: [] as any as RequiredProp, - /** - * 点击确定事件。 - * - * @default () => {} - */ - onConfirm: noop as any as RequiredProp<(selectedTime: number[]) => void>, + /** + * 点击确定事件。 + * + * @default () => {} + */ + onConfirm: noop as any as RequiredProp<(selectedTime: number[]) => void>, + }), } + +export type MTimePickerProps = typeof MTimePickerDefaultProps