如何使用 React 構建自定義日期選擇器(1)
在 web 上經常看到包含一個或多個日期的表單。無論是出生日期還是航班時間表日期,您總希望使用者能夠提供了有效的日期。
在 HTML5/">HTML5 中,引入了新的 date
輸入型別,來確保獲取表單中的有效日期值。 date
輸入型別的預設行為是向用戶顯示日期選擇器。但是,這個日期選擇器的外觀在不同瀏覽器之間並不一致。
您可以在 ofollow,noindex">這裡 找到更多關於 date
輸入型別和瀏覽器支援的資訊。
在本教程中,您將學習如何使用 React 和原生 JavaScript/">JavaScript日期物件從頭構建自定義日期選擇器。下面是一個簡短的演示,展示了日期選擇器的外觀。
您還可以在 Code Sandbox 上檢視 演示 。
先決條件
本教程假設您非常熟悉 JavaScript,並且已經熟悉 React 框架。如果不是這樣,您可以檢視 React文件 來了解有關React的更多資訊。
在開始之前,您需要確保您的計算機上已經安裝了 Node 。建議您在機器上安裝 Yarn 包管理器,因為它將代替 Node 附帶的 npm 。您可以按照此 Yarn 安裝指南 在您的機器上安裝 Yarn。
React 應用程式的樣板程式碼將使用 create-react-app 包建立。您還需要確保它在您的機器上是全域性安裝的。如果您使用 npm >= 5.2
,那麼您不需要將 create-react-app
作為一個全域性依賴項安裝——您可以使用 npx
命令。
開始
建立新的應用程式
使用以下命令建立新的 React 應用程式。您可以隨意命名應用程式。
create-react-app react-datepicker
npm> = 5.2
如果您使用的是 npm 5.2
或更高版本,它會附帶一個額外的 npx
二進位制檔案。 使用 npx
二進位制檔案,您無需在計算機上全域性安裝 create-react-app
。 您可以使用以下簡單命令建立新的 React 應用程式:
npx create-react-app react-datepicker
安裝依賴
這個應用程式的依賴儘可能地保持精簡。執行以下命令安裝所需的依賴項。
yarn add bootstrap reactstrap styled-components prop-types
引入 Bootstrap CSS
本教材為了方便,直接使用 bootstrap
來提供一些預設樣式。請編輯 src/index
並在其他 import
語句之前新增以下行。
import 'bootstrap/dist/css/bootstrap.min.css';
目錄設定
對於這個應用程式,需要兩個主要元件。
Calendar元件 Datepicker元件
每個元件都將包含在自己的目錄中,其中包含兩個檔案—— index.js
和 styles.js
。 index.js
匯出元件,而 styles.js
匯出元件所需樣式的樣式化元件。
從專案根目錄執行以下命令來建立元件目錄和檔案:
# Create directories mkdir -p src/components/Calendar src/components/Datepicker # Create files (cd src/components/Calendar && touch index.js styles.js) (cd src/components/Datepicker && touch index.js styles.js)
由於在這個應用程式中不需要外部依賴來處理日期,因此需要自己編寫日期處理的 helper 函式。執行以下命令來建立 calendar helper 模組。
mkdir -p src/helpers touch src/helpers/calendar.js
啟動應用程式
通過在終端上使用 yarn
執行以下命令來啟動應用程式:
yarn start
應用程式現在已經啟動,可以開始開發了。請注意,已經為您打開了一個瀏覽器選項卡,該選項卡具有實時重新載入功能,以便在開發時與應用程式中的更改保持同步。
Calendar helper 模組
基本常量和 helper 函式
首先,定義一些構建日曆所需的日曆常量和 helper 函式。它們將在前面建立的 calendar helper 模組中定義並匯出。
將以下內容新增到 src/helpers/calendar.js
檔案中。
// (int) The current year export const THIS_YEAR = +(new Date().getFullYear()); // (int) The current month starting from 1 - 12 // 1 => January, 12 => December export const THIS_MONTH = +(new Date().getMonth()) + 1; // Week days names and shortnames export const WEEK_DAYS = { Sunday: "Sun", Monday: "Mon", Tuesday: "Tue", Wednesday: "Wed", Thursday: "Thu", Friday: "Fri", Saturday: "Sat" } // Calendar months names and shortnames export const CALENDAR_MONTHS = { January: "Jan", February: "Feb", March: "Mar", April: "Apr", May: "May", June: "Jun", July: "Jul", August: "Aug", September: "Sep", October: "Oct", November: "Nov", December: "Dec" } // Weeks displayed on calendar export const CALENDAR_WEEKS = 6; // Pads a string value with leading zeroes(0) until length is reached // For example: zeroPad(5, 2) => "05" export const zeroPad = (value, length) => { return `${value}`.padStart(length, '0'); } // (int) Number days in a month for a given year from 28 - 31 export const getMonthDays = (month = THIS_MONTH, year = THIS_YEAR) => { const months30 = [4, 6, 9, 11]; const leapYear = year % 4 === 0; return month === 2 ? leapYear ? 29 : 28 : months30.includes(month) ? 30 : 31; } // (int) First day of the month for a given year from 1 - 7 // 1 => Sunday, 7 => Saturday export const getMonthFirstDay = (month = THIS_MONTH, year = THIS_YEAR) => { return +(new Date(`${year}-${zeroPad(month, 2)}-01`).getDay()) + 1; }
這個程式碼片段包含註釋,解釋每個 helper 函式都做了什麼。然而,有幾件事值得指出。
首先, Date.prototype
中的 getDay()
和 getMonth()
方法通常會返回從零開始的值。因此,一年的第一個月(January)是 0
,December 是 11
,而一週的第一天(Sunday)是 0
,Saturday 是 7
。
在前面的程式碼片段中,您會看到 1
總是被新增到這些從零開始的值中,因此 Sunday 為 1
,December 為 12
。
還要注意, CALENDAR_WEEKS
被設定為 6
。由於一個月通常跨越 4
周,因此日曆至少可以容納上個月的最後一週和下個月的第一週。您很快就會看到這個常量的效果,因為它將在 calendar builder 函式中使用。
額外的 helper 函式
將以下內容附加到 src/helper/calendar.js
檔案中,新增一些額外的輔助功能到 calendar helper 模組。
// (bool) Checks if a value is a date - this is just a simple check export const isDate = date => { const isDate = Object.prototype.toString.call(date) === '[object Date]'; const isValidDate = date && !Number.isNaN(date.valueOf()); return isDate && isValidDate; } // (bool) Checks if two date values are of the same month and year export const isSameMonth = (date, basedate = new Date()) => { if (!(isDate(date) && isDate(basedate))) return false; const basedateMonth = +(basedate.getMonth()) + 1; const basedateYear = basedate.getFullYear(); const dateMonth = +(date.getMonth()) + 1; const dateYear = date.getFullYear(); return (+basedateMonth === +dateMonth) && (+basedateYear === +dateYear); } // (bool) Checks if two date values are the same day export const isSameDay = (date, basedate = new Date()) => { if (!(isDate(date) && isDate(basedate))) return false; const basedateDate = basedate.getDate(); const basedateMonth = +(basedate.getMonth()) + 1; const basedateYear = basedate.getFullYear(); const dateDate = date.getDate(); const dateMonth = +(date.getMonth()) + 1; const dateYear = date.getFullYear(); return (+basedateDate === +dateDate) && (+basedateMonth === +dateMonth) && (+basedateYear === +dateYear); } // (string) Formats the given date as YYYY-MM-DD // Months and Days are zero padded export const getDateISO = (date = new Date) => { if (!isDate(date)) return null; return [ date.getFullYear(), zeroPad(+date.getMonth() + 1, 2), zeroPad(+date.getDate(), 2) ].join('-'); } // ({month, year}) Gets the month and year before the given month and year // For example: getPreviousMonth(1, 2000) => {month: 12, year: 1999} // while: getPreviousMonth(12, 2000) => {month: 11, year: 2000} export const getPreviousMonth = (month, year) => { const prevMonth = (month > 1) ? month - 1 : 12; const prevMonthYear = (month > 1) ? year : year - 1; return { month: prevMonth, year: prevMonthYear }; } // ({month, year}) Gets the month and year after the given month and year // For example: getNextMonth(1, 2000) => {month: 2, year: 2000} // while: getNextMonth(12, 2000) => {month: 1, year: 2001} export const getNextMonth = (month, year) => { const nextMonth = (month < 12) ? month + 1 : 1; const nextMonthYear = (month < 12) ? year : year + 1; return { month: nextMonth, year: nextMonthYear }; }
Default export
最後,這裡是 calendar helper 模組的預設匯出—— calendar builder 函式
。該函式以 month
和 year
作為引數,並返回一個包含 42
個元素的陣列,每個元素以 [YYYY, MM, DD]
的格式表示日曆日期。
下面是 calendar builder 函式。將此程式碼段追加到 src/helper/calendar.js
檔案。
// Calendar builder for a month in the specified year // Returns an array of the calendar dates. // Each calendar date is represented as an array => [YYYY, MM, DD] export default (month = THIS_MONTH, year = THIS_YEAR) => { // Get number of days in the month and the month's first day const monthDays = getMonthDays(month, year); const monthFirstDay = getMonthFirstDay(month, year); // Get number of days to be displayed from previous and next months // These ensure a total of 42 days (6 weeks) displayed on the calendar const daysFromPrevMonth = monthFirstDay - 1; const daysFromNextMonth = (CALENDAR_WEEKS * 7) - (daysFromPrevMonth + monthDays); // Get the previous and next months and years const { month: prevMonth, year: prevMonthYear } = getPreviousMonth(month, year); const { month: nextMonth, year: nextMonthYear } = getNextMonth(month, year); // Get number of days in previous month const prevMonthDays = getMonthDays(prevMonth, prevMonthYear); // Builds dates to be displayed from previous month const prevMonthDates = [...new Array(daysFromPrevMonth)].map((n, index) => { const day = index + 1 + (prevMonthDays - daysFromPrevMonth); return [ prevMonthYear, zeroPad(prevMonth, 2), zeroPad(day, 2) ]; }); // Builds dates to be displayed from current month const thisMonthDates = [...new Array(monthDays)].map((n, index) => { const day = index + 1; return [year, zeroPad(month, 2), zeroPad(day, 2)]; }); // Builds dates to be displayed from next month const nextMonthDates = [...new Array(daysFromNextMonth)].map((n, index) => { const day = index + 1; return [nextMonthYear, zeroPad(nextMonth, 2), zeroPad(day, 2)]; }); // Combines all dates from previous, current and next months return [ ...prevMonthDates, ...thisMonthDates, ...nextMonthDates ]; }
請注意,calendar builder 在陣列中返回的日曆日期從上一個月最後一週的日期到給定月份的日期,再到下一個月第一週的日期。