如何用react封裝一款日期選擇器元件

語言: CN / TW / HK

theme: channing-cyan highlight: night-owl


這是我參與11月更文挑戰的第12天,活動詳情檢視:[2021最後一次更文挑戰](https://juejin.cn/post/7023643374569816095/ "https://juejin.cn/post/7023643374569816095/") > TIP 👉 **嗚呼!楚雖三戶能亡秦,豈有堂堂中國空無人!____陸游《金錯刀行》**

前言

Web Component是前端界一直非常熱衷的一個領域,用第三方元件化的框架去實現的話,你需要依賴框架本身很多東西,很多時候我們只是簡單的幾個元件,不是很大,也不是很多,所以為了保證元件的`輕量,簡單`,其實這個時候我們並不想採用第三方的框架。 # 日期選擇器元件 ### import ```js import DatePicker from '@/components/DatePicker/DatePicker'; ``` ### Props ##### 1. onChange * 型別:func (必填) * 預設值:無 * 說明:選中日期後的回撥函式,入參: * {Moment | Date | String | Number} value 選中日期值(與valueType對應) * {Moment} momentValue 選中日期的Moment值 ##### 2. valueType * 型別:DatePicker.VALUE_TYPE 中的一種 * 預設值:DatePicker.VALUE_TYPE.string * 說明:日期值型別 ##### 3. value * 型別:valueType指定的型別 * 預設值:無 * 說明:日期值 ```html ``` ```html ``` ```html ``` ```html ``` ##### 4. format * 型別:string * 預設值:'YYYY-MM-DD'(showTime為true時為'YYYY-MM-DD HH:mm:ss') * 說明:日期字串的格式 ##### 5. showTime * 型別:bool * 預設值:false * 說明:是否顯示時間(時分秒) ##### 6. defaultTime * 型別:string * 預設值:無(未設定時,預設為當前時間) * 說明:預設時間(時分秒),如:'00:00:00' ##### 7. minValue * 型別:DatePicker.VALUE_TYPE 中的任意一種,與valueType無關 * 預設值:無 * 說明:可選日期的最小值 ##### 8. maxValue * 型別:DatePicker.VALUE_TYPE 中的任意一種,與valueType無關 * 預設值:無 * 說明:可選日期的最大值 ##### 9. placeholder * 型別:string * 預設值:'請選擇日期'(showTime為true時為'請選擇日期時間') * 說明:輸入提示資訊 ##### 10. showClear * 型別:bool * 預設值:true * 說明:輸入框右側是否顯示清空按鈕 ##### 11. disabled * 型別:bool * 預設值:false * 說明:是否不可用,true表示日期選擇器不可用 ##### 12. disabledDate * 型別:func (必填) * 預設值:無 * 說明:判斷日期是否可選 * 入參: * {Moment} current 當前日期 * 返回: * {Boolen} 是否為不可選,true表示當前日期不可選 ##### 13. disabledHours * 型別:func (必填) * 預設值:無 * 說明:獲取不可用小時的陣列(showTime為true時有效) * 入參: * {Moment} current 當前日期 * 返回: * {Array} 不可用的小時陣列 ##### 14. disabledMinutes * 型別:func (必填) * 預設值:無 * 說明:獲取不可用分鐘的陣列(showTime為true時有效) * 入參: * {Number} selectedHour 當前選中的小時 * {Moment} current 當前日期 * 返回: * {Array} 不可用的分鐘陣列 ##### 15. disabledSeconds * 型別:func (必填) * 預設值:無 * 說明:獲取不可用秒的陣列(showTime為true時有效) * 入參: * {Number} selectedHour 當前選中的小時 * {Number} selectedMinute 當前選中的分鐘 * {Moment} current 當前日期 * 返回: * {Array} 不可用的秒陣列 實現DatePicker.js ```js import React from 'react'; import PropTypes from 'prop-types'; import Calendar from 'rc-calendar'; import Picker from 'rc-calendar/lib/Picker'; import zhCN from 'rc-calendar/lib/locale/zh_CN'; import TimePickerPanel from 'rc-time-picker/lib/Panel'; import moment from 'moment'; import valueTypes from './utils/value-types'; import PickerPropTypes from './utils/picker-prop-types.js'; import valueConvertUtil from './utils/value-convert-util.js'; import 'moment/locale/zh-cn'; import './datePicker.scss'; /** * 日期選擇器 */ export default class DatePicker extends React.Component { // 值型別常量 static VALUE_TYPE = valueTypes; // 入參型別檢查 static propTypes = { /** * 選中日期後的回撥函式 * @param {Moment | Date | String | Number} value 選中日期值(與valueType對應) * @param {Moment} momentValue 選中日期的Moment值 */ onChange: PropTypes.func.isRequired, // 日期值型別:DatePicker.VALUE_TYPE 中的一種 valueType: PropTypes.oneOf(Object.keys(DatePicker.VALUE_TYPE).map( k => DatePicker.VALUE_TYPE[k]) ), // 日期值(必須是 valueType 指定型別的數值) value: PickerPropTypes.dateValue, // 日期字串的格式 format: PropTypes.string, // 是否顯示時間(時分秒) showTime: PropTypes.bool, // 預設時間(時分秒),showTime為true時有效,如:'00:00:00',為空則會是當前時間 defaultTime: PropTypes.string, // 可選日期的最小值(DatePicker.VALUE_TYPE 中的任意一種,與valueType無關) minValue: PickerPropTypes.looseDateValue, // 可選日期的最大值(DatePicker.VALUE_TYPE 中的任意一種,與valueType無關) maxValue: PickerPropTypes.looseDateValue, // 輸入提示資訊 placeholder: PropTypes.string, // 是否顯示清空按鈕 showClear: PropTypes.bool, // 是否不可用 disabled: PropTypes.bool, /** * 判斷日期是否可選 * @param {Moment} current 當前日期 * @return {Boolen} 是否為不可選,true表示當前日期不可選 */ disabledDate: PropTypes.func, /** * 獲取不可用小時的陣列(showTime為true時有效) * @param {Moment} current 當前日期 * @return {Array} 不可用的小時陣列 */ disabledHours: PropTypes.func, /** * 獲取不可用分鐘的陣列(showTime為true時有效) * @param {Number} selectedHour 當前選中的小時 * @param {Moment} current 當前日期 * @return {Array} 不可用的分鐘陣列 */ disabledMinutes: PropTypes.func, /** * 獲取不可用秒的陣列(showTime為true時有效) * @param {Number} selectedHour 當前選中的小時 * @param {Number} selectedMinute 當前選中的分鐘 * @param {Moment} current 當前日期 * @return {Array} 不可用的秒陣列 */ disabledSeconds: PropTypes.func } // 入參預設值 static defaultProps = { // 日期值型別,預設為字串 valueType: DatePicker.VALUE_TYPE.string, // 是否顯示時間(時分秒),預設為不顯示 showTime: false, // 是否顯示清空按鈕 showClear: true, // 是否不可用,預設為可用 disabled: false } constructor(props) { super(props); const format = this.props.format || (this.props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'); const momentValue = valueConvertUtil.convertToMoment(this.props.value, this.props.valueType, this.props.showTime, format); const minMomentValue = valueConvertUtil.convertToMoment(this.props.minValue, null, this.props.showTime, format); const maxMomentValue = valueConvertUtil.convertToMoment(this.props.maxValue, null, this.props.showTime, format); const defaultTimeValue = this.props.defaultTime ? moment(this.props.defaultTime, 'HH:mm:ss') : moment(); const placeholder = this.props.placeholder ? this.props.placeholder : (this.props.showTime ? '請選擇日期時間' : '請選擇日期'); this.state = { // moment型別的值 momentValue, // 時間字串格式 format, // 可選時間最小值的moment物件 minMomentValue, // 可選時間最大值的moment物件 maxMomentValue, // 預設時分秒 defaultTimeValue, // 輸入提示資訊 placeholder }; } componentDidUpdate(prevProps, prevState) { this.receivePropsResetState(prevProps); } // 根據 props 的變化重新設定 state receivePropsResetState (prevProps) { const format = this.props.format || (this.props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'); if (prevProps.format !== this.props.format || prevProps.showTime !== this.props.showTime) { const momentValue = valueConvertUtil.convertToMoment(this.props.value, this.props.valueType, this.props.showTime, format); const minMomentValue = valueConvertUtil.convertToMoment(this.props.minValue, null, this.props.showTime, format); const maxMomentValue = valueConvertUtil.convertToMoment(this.props.maxValue, null, this.props.showTime, format); const placeholder = this.props.placeholder ? this.props.placeholder : (this.props.showTime ? '請選擇日期時間' : '請選擇日期'); this.setState({ // moment型別的值 momentValue, // 時間字串格式 format, // 可選時間最小值的moment物件 minMomentValue, // 可選時間最大值的moment物件 maxMomentValue, // 輸入提示資訊 placeholder }); } else { if (prevProps.valueType !== this.props.valueType) { console.warn('DatePicker元件 props.valueType 的值不能隨意改變'); } if (!valueConvertUtil.isSameValue(prevProps.value, this.props.value)) { const momentValue = valueConvertUtil.convertToMoment(this.props.value, this.props.valueType, this.props.showTime, format); this.setState({ momentValue }); } if (prevProps.minValue !== this.props.minValue) { const minMomentValue = valueConvertUtil.convertToMoment(this.props.minValue, null, this.props.showTime, format); this.setState({ minMomentValue }); } if (prevProps.maxValue !== this.props.maxValue) { const maxMomentValue = valueConvertUtil.convertToMoment(this.props.maxValue, null, this.props.showTime, format); this.setState({ maxMomentValue }); } if (prevProps.defaultTime !== this.props.defaultTime) { const defaultTimeValue = this.props.defaultTime ? moment(this.props.defaultTime, 'HH:mm:ss') : moment(); this.setState({ defaultTimeValue }); } if (prevProps.placeholder !== this.props.placeholder) { const placeholder = this.props.placeholder ? this.props.placeholder : (this.props.showTime ? '請選擇日期時間' : '請選擇日期'); this.setState({ placeholder }); } } } /** * 將其他型別轉換為moment物件 * @param value 其他型別時間值 */ convertToMoment (value) { return valueConvertUtil.convertToMoment(value, this.props.valueType, this.props.showTime, this.state.format); } /** * 將moment物件轉換為其他型別 * @param obj moment物件 */ convertFromMoment (obj) { return valueConvertUtil.convertFromMoment(obj, this.props.valueType, this.props.showTime, this.state.format); } // 日期選擇器選擇後觸發的事件 onPickerChange = (value) => { this.setState({ momentValue: value }) if (!this.props.showTime || value === null) { this.props.onChange && this.props.onChange(this.convertFromMoment(value), value); } } // 日期選擇器開啟狀態改變時觸發的事件 onOpenChange = (open) => { if (this.props.showTime && !open) { let value = this.state.momentValue; this.props.onChange && this.props.onChange(this.convertFromMoment(value), value); } } // 清空 onClear = (event) => { this.props.onChange && this.props.onChange(null, null); event.stopPropagation(); }; // 建立時間選擇器元件(時分秒選擇器元件) createTimePicker = (value) => { if (this.props.showTime) { return ; } return null; } /** * 判斷日期是否可選 * @param {Moment} current moment物件 * @returns {boolean} */ disabledDate = (current) => { if (!this.props.showTime) { // 不顯示時間(時分秒)時 let curValue = moment(current.format('YYYY-MM-DD'), 'YYYY-MM-DD'); let { minMomentValue, maxMomentValue } = this.state; if (minMomentValue && curValue.isBefore(minMomentValue)) return true; if (maxMomentValue && curValue.isAfter(maxMomentValue)) return true; } else { // 顯示時間(時分秒)時 let curValue = this.state.momentValue; if ( curValue ) { curValue = moment(`${current.format('YYYY-MM-DD')} ${this.convertToMoment(curValue).format('HH:mm:ss')}`, 'YYYY-MM-DD HH:mm:ss'); } else { curValue = moment(`${current.format('YYYY-MM-DD')} ${this.state.defaultTimeValue.format('HH:mm:ss')}`, 'YYYY-MM-DD HH:mm:ss'); } let { minMomentValue, maxMomentValue } = this.state; let minVal, maxVal; if (minMomentValue) { minVal = moment(minMomentValue.format('YYYY-MM-DD'), 'YYYY-MM-DD') if (curValue.valueOf() < minVal.valueOf()) return true; } if (maxMomentValue) { if (maxMomentValue.format('HH:mm:ss') === '00:00:00') { maxVal = maxMomentValue.clone(); } else { maxVal = moment(maxMomentValue.format('YYYY-MM-DD'), 'YYYY-MM-DD').add(moment.duration({'day' : 1})); } if (curValue.valueOf() >= maxVal.valueOf()) return true; } } if (this.props.disabledDate) { return this.props.disabledDate(current); } return false; } /** * 判斷時間(時、分、秒)是否可選 * @param {Moment} current moment物件 * @returns {Object} 返回包含disabledHours、disabledMinutes、disabledSeconds函式的物件 */ disabledTime = (current) => { // 根據最小值和最大值建立連續數字陣列方法 function createNumArray (minNum, maxNum) { let arr = []; for (let i = minNum; i <= maxNum; i++) { arr.push(i); } return arr; } // 可選日期的最大值和最小值 let { minMomentValue, maxMomentValue } = this.state; // 如果已選擇日期 if (current) { // 獲取不可用小時陣列的方法 let disabledHours = () => { let hours = []; if (minMomentValue && current.isSame(minMomentValue, 'day')) { hours = createNumArray(0, minMomentValue.hour() - 1); } if (maxMomentValue && current.isSame(maxMomentValue, 'day')) { hours = hours.concat(createNumArray(maxMomentValue.hour() + 1, 23)); } if (this.props.disabledHours) { hours = hours.concat(this.props.disabledHours(current)); } return hours; } // 獲取不可用分鐘陣列的方法 let disabledMinutes = (selectedHour) => { let minutes = []; if (minMomentValue && current.isSame(minMomentValue, 'day') && selectedHour === minMomentValue.hour()) { minutes = createNumArray(0, minMomentValue.minute() - 1); } if (maxMomentValue && current.isSame(maxMomentValue, 'day') && selectedHour === maxMomentValue.hour()) { minutes = minutes.concat(createNumArray(maxMomentValue.minute() + 1, 59)); } if (this.props.disabledMinutes) { minutes = minutes.concat(this.props.disabledMinutes(selectedHour, current)); } return minutes; } // 獲取不可用秒陣列的方法 let disabledSeconds = (selectedHour, selectedMinute) => { let seconds = []; if (minMomentValue && current.isSame(minMomentValue, 'day') && selectedHour === minMomentValue.hour() && selectedMinute === minMomentValue.minute()) { seconds = createNumArray(0, minMomentValue.second() - 1); } if (maxMomentValue && current.isSame(maxMomentValue, 'day') && selectedHour === maxMomentValue.hour() && selectedMinute === maxMomentValue.minute()) { seconds = seconds.concat(createNumArray(maxMomentValue.second() + 1, 59)); } if (this.props.disabledSeconds) { seconds = seconds.concat(this.props.disabledSeconds(selectedHour, selectedMinute, current)); } return seconds; } return { disabledHours, disabledMinutes, disabledSeconds } } else { // 如果未選擇日期,且限制了日期可選範圍,則時分秒不可選 if (minMomentValue || maxMomentValue) { return { disabledHours: () => createNumArray(0, 23), disabledMinutes: () => createNumArray(0, 59), disabledSeconds: () => createNumArray(0, 59) }; } else { // 如果未選擇日期,且未限制日期範圍,則使用入參的disabledHours、disabledMinutes、disabledSeconds進行過濾 return { disabledHours: () => { return this.props.disabledHours ? this.props.disabledHours(current) : []; }, disabledMinutes: (selectedHour) => { return this.props.disabledMinutes ? this.props.disabledMinutes(selectedHour, current) : []; }, disabledSeconds: (selectedHour, selectedMinute) => { return this.props.disabledSeconds ? this.props.disabledSeconds(selectedHour, selectedMinute, current) : []; } }; } } } render() { const dateValue = this.state.momentValue; const showClear = this.props.showClear && this.props.value; const calendar = (); return ( { ({ value }) => { return (
{showClear ? : null}
); } }
); } } ``` 樣式這塊就先不放了 「歡迎在評論區討論」 #### 希望看完的朋友可以給個贊,鼓勵一下