内容简介:接着上一篇:现在您已经有了将以下代码片段添加到
接着上一篇: 如何使用 React 构建自定义日期选择器(1)
Calendar 组件
构建 Calendar 组件
现在您已经有了 calendar helper
模块,是时候构建 React Calendar
组件了。
将以下代码片段添加到 src/components/Calendar/index.js
文件。
import React, { Component, Fragment } from "react"; import PropTypes from "prop-types"; import * as Styled from "./styles"; import calendar, { isDate, isSameDay, isSameMonth, getDateISO, getNextMonth, getPreviousMonth, WEEK_DAYS, CALENDAR_MONTHS } from "../../helpers/calendar"; class Calendar extends Component { state = { ...this.resolveStateFromProp(), today: new Date() }; resolveStateFromDate(date) { const isDateObject = isDate(date); const _date = isDateObject ? date : new Date(); return { current: isDateObject ? date : null, month: +_date.getMonth() + 1, year: _date.getFullYear() }; } resolveStateFromProp() { return this.resolveStateFromDate(this.props.date); } getCalendarDates = () => { const { current, month, year } = this.state; const calendarMonth = month || +current.getMonth() + 1; const calendarYear = year || current.getFullYear(); return calendar(calendarMonth, calendarYear); }; render() { return ( <Styled.CalendarContainer> { this.renderMonthAndYear() } <Styled.CalendarGrid> <Fragment> { Object.keys(WEEK_DAYS).map(this.renderDayLabel) } </Fragment> <Fragment> { this.getCalendarDates().map(this.renderCalendarDate) } </Fragment> </Styled.CalendarGrid> </Styled.CalendarContainer> ); } } Calendar.propTypes = { date: PropTypes.instanceOf(Date), onDateChanged: PropTypes.func } export default Calendar;
请注意,在此代码片段中,已经从 calendar helper
模块导入了 calendar builder
函数以及其他 helper 函数和常量。此外, calendar styles
模块的所有导出都已使用 Styled
命名空间导入。
虽然目前还没有创建样式,但是很快就会使用 styled-components
包创建样式。
组件 state 部分通过使用 resolveStateFromProp()
方法从 props
解析,该方法返回一个对象,该对象包含:
current month year
month
和 year
状态属性是正常渲染日历所必需的,如 getCalendarDates()
方法所示,该方法使用 calendar builder
函数构建月份和年份的日历。
最后,使用 today
属性对 state 进行扩展,该属性是当前日期的 Date
对象。
渲染 Calendar 组件的各个部分
在前面的 Calendar
组件代码片段中, render()
方法引用了其他一些用于渲染月份、年份、星期和日历日期的方法。
将这些方法添加到 Calendar
组件,如下面的代码片段所示。
class Calendar extends Component { // Render the month and year header with arrow controls // for navigating through months and years renderMonthAndYear = () => { const { month, year } = this.state; // Resolve the month name from the CALENDAR_MONTHS object map const monthname = Object.keys(CALENDAR_MONTHS)[ Math.max(0, Math.min(month - 1, 11)) ]; return ( <Styled.CalendarHeader> <Styled.ArrowLeft onMouseDown={this.handlePrevious} onMouseUp={this.clearPressureTimer} title="Previous Month" /> <Styled.CalendarMonth> {monthname} {year} </Styled.CalendarMonth> <Styled.ArrowRight onMouseDown={this.handleNext} onMouseUp={this.clearPressureTimer} title="Next Month" /> </Styled.CalendarHeader> ); } // Render the label for day of the week // This method is used as a map callback as seen in render() renderDayLabel = (day, index) => { // Resolve the day of the week label from the WEEK_DAYS object map const daylabel = WEEK_DAYS[day].toUpperCase(); return ( <Styled.CalendarDay key={daylabel} index={index}> {daylabel} </Styled.CalendarDay> ); } // Render a calendar date as returned from the calendar builder function // This method is used as a map callback as seen in render() renderCalendarDate = (date, index) => { const { current, month, year, today } = this.state; const _date = new Date(date.join("-")); // Check if calendar date is same day as today const isToday = isSameDay(_date, today); // Check if calendar date is same day as currently selected date const isCurrent = current && isSameDay(_date, current); // Check if calendar date is in the same month as the state month and year const inMonth = month && year && isSameMonth(_date, new Date([year, month, 1].join("-"))); // The click handler const onClick = this.gotoDate(_date); const props = { index, inMonth, onClick, title: _date.toDateString() }; // Conditionally render a styled date component const DateComponent = isCurrent ? Styled.HighlightedCalendarDate : isToday ? Styled.TodayCalendarDate : Styled.CalendarDate; return ( <DateComponent key={getDateISO(_date)} {...props}> {_date.getDate()} </DateComponent> ); } }
在 renderMonthAndYear()
方法中,首先从 CALENDAR_MONTHS
对象解析月份名称。然后它与年份及左侧和右侧两个箭头控件一起渲染,用于导航月和年。
箭头控件每个都有 mousedown
和 mouseup
事件处理,稍后将定义这些事件处理—— handlePrevious()
、 handleNext()
和 clearPressureTimer()
。
renderMonthAndYear()
方法渲染的 DOM 看起来像下面的截图(带有一些样式):
renderDayLabel()
方法渲染一周中某一天的标签。 它解析 WEEK_DAYS
对象中的标签。注意,它有两个参数—— day
和 index
,因为它用作 .map()
的回调函数,如 render()
方法所示。
映射之后,一周中日期的渲染 DOM 看起来像下面的截图 。
renderCalendarDate()
方法也用作 .map()
回调函数并渲染日历日期。它接收到的第一个参数 date
的格式是 [YYYY, MM, DD]
。
它检查 date
是否与今天相同,是否与当前选择的日期相同,是否与当前 state 的月份和年份相同。通过这些检查,它有条件地渲染日历日期单元格的不同形态—— HiglightedCalendarDate
、 TodayCalendarDate
或 CalendarDate
。
还要注意,使用 gotoDate()
方法(将在下一节中定义)为每个日历日期设置 onClick
处理,以跳转到特定日期。
事件处理
在前面几节中已经对一些事件处理进行了一些引用。继续并更新 Calendar
组件,以包含事件处理的以下代码片段。
class Calendar extends Component { gotoDate = date => evt => { evt && evt.preventDefault(); const { current } = this.state; const { onDateChanged } = this.props; !(current && isSameDay(date, current)) && this.setState(this.resolveStateFromDate(date), () => { typeof onDateChanged === "function" && onDateChanged(date); }); } gotoPreviousMonth = () => { const { month, year } = this.state; this.setState(getPreviousMonth(month, year)); } gotoNextMonth = () => { const { month, year } = this.state; this.setState(getNextMonth(month, year)); } gotoPreviousYear = () => { const { year } = this.state; this.setState({ year: year - 1 }); } gotoNextYear = () => { const { year } = this.state; this.setState({ year: year + 1 }); } handlePressure = fn => { if (typeof fn === "function") { fn(); this.pressureTimeout = setTimeout(() => { this.pressureTimer = setInterval(fn, 100); }, 500); } } clearPressureTimer = () => { this.pressureTimer && clearInterval(this.pressureTimer); this.pressureTimeout && clearTimeout(this.pressureTimeout); } handlePrevious = evt => { evt && evt.preventDefault(); const fn = evt.shiftKey ? this.gotoPreviousYear : this.gotoPreviousMonth; this.handlePressure(fn); } handleNext = evt => { evt && evt.preventDefault(); const fn = evt.shiftKey ? this.gotoNextYear : this.gotoNextMonth; this.handlePressure(fn); } }
gotoDate()
方法是一个高阶函数,它接受一个 Date
对象作为参数,并返回一个事件处理函数,该事件处理函数可以被触发以更新 state 中当前选定的日期。注意, resolveStateFromDate()
方法用于从日期中解析 month
和 year
并更新 state。
如果 Calendar
组件的 props
传递了 onDateChanged
回调函数,则将使用更新的日期调用该函数。 这对于您希望将日期更改传播到父组件的情况非常有用。
handlePrevious()
和 handleNext()
事件处理共享类似的行为。默认情况下,它们会按月循环。然而,如果按下 shift 键,它们就会以年为单位循环。最后,他们将控制权交给 handlePressure()
方法。
handlePressure()
方法简单地使用计时器模拟压力单击,以快速循环数月或数年,而 clearPressureTimer()
方法清除这些计时器。
组件生命周期方法
Calendar
组件离完成还差一些生命周期方法。下面是 Calendar
组件的生命周期方法。
class Calendar extends Component { // ... other methods here componentDidMount() { const now = new Date(); const tomorrow = new Date().setHours(0, 0, 0, 0) + 24 * 60 * 60 * 1000; const ms = tomorrow - now; this.dayTimeout = setTimeout(() => { this.setState({ today: new Date() }, this.clearDayTimeout); }, ms); } componentDidUpdate(prevProps) { const { date, onDateChanged } = this.props; const { date: prevDate } = prevProps; const dateMatch = date == prevDate || isSameDay(date, prevDate); !dateMatch && this.setState(this.resolveStateFromDate(date), () => { typeof onDateChanged === "function" && onDateChanged(date); }); } clearDayTimeout = () => { this.dayTimeout && clearTimeout(this.dayTimeout); } componentWillUnmount() { this.clearPressureTimer(); this.clearDayTimeout(); } }
在 componentDidMount()
方法中,有一个日期计时器,它被设置为在当前日期结束时自动将 state 中的 today
属性更新到第二天。
在卸载组件之前,清除所有计时器,如 componentWillUnmount()
方法中所示。
设置日历样式
现在您已经完成了 Calendar
组件,接下来您将创建为日历提供样式的样式化组件。
将以下代码片段添加到 src/components/Calendar/styles.js
文件。
import styled from 'styled-components'; export const Arrow = styled.button` appearance: none; user-select: none; outline: none !important; display: inline-block; position: relative; cursor: pointer; padding: 0; border: none; border-top: 1.6em solid transparent; border-bottom: 1.6em solid transparent; transition: all .25s ease-out; `; export const ArrowLeft = styled(Arrow)` border-right: 2.4em solid #ccc; left: 1.5rem; :hover { border-right-color: #06c; } `; export const ArrowRight = styled(Arrow)` border-left: 2.4em solid #ccc; right: 1.5rem; :hover { border-left-color: #06c; } `; export const CalendarContainer = styled.div` font-size: 5px; border: 2px solid #06c; border-radius: 5px; overflow: hidden; `; export const CalendarHeader = styled.div` display: flex; align-items: center; justify-content: space-between; `; export const CalendarGrid = styled.div` display: grid; grid-template: repeat(7, auto) / repeat(7, auto); `; export const CalendarMonth = styled.div` font-weight: 500; font-size: 5em; color: #06c; text-align: center; padding: 0.5em 0.25em; word-spacing: 5px; user-select: none; `; export const CalendarCell = styled.div` text-align: center; align-self: center; letter-spacing: 0.1rem; padding: 0.6em 0.25em; user-select: none; grid-column: ${props => (props.index % 7) + 1} / span 1; `; export const CalendarDay = styled(CalendarCell)` font-weight: 600; font-size: 2.25em; color: #06c; border-top: 2px solid #06c; border-bottom: 2px solid #06c; border-right: ${props => (props.index % 7) + 1 === 7 ? `none` : `2px solid #06c`}; `; export const CalendarDate = styled(CalendarCell)` font-weight: ${props => props.inMonth ? 500 : 300}; font-size: 4em; cursor: pointer; border-bottom: ${props => ((props.index + 1) / 7) <= 5 ? `1px solid #ddd` : `none`}; border-right: ${props => (props.index % 7) + 1 === 7 ? `none` : `1px solid #ddd`}; color: ${props => props.inMonth ? `#333` : `#ddd`}; grid-row: ${props => Math.floor(props.index / 7) + 2} / span 1; transition: all .4s ease-out; :hover { color: #06c; background: rgba(0, 102, 204, 0.075); } `; export const HighlightedCalendarDate = styled(CalendarDate)` color: #fff !important; background: #06c !important; position: relative; ::before { content: ''; position: absolute; top: -1px; left: -1px; width: calc(100% + 2px); height: calc(100% + 2px); border: 2px solid #06c; } `; export const TodayCalendarDate = styled(HighlightedCalendarDate)` color: #06c !important; background: transparent !important; ::after { content: ''; position: absolute; right: 0; bottom: 0; border-bottom: 0.75em solid #06c; border-left: 0.75em solid transparent; border-top: 0.75em solid transparent; } :hover { color: #06c !important; background: rgba(0, 102, 204, 0.075) !important; } `;
以上就是正常渲染日历所需的组件和样式。如果此时在应用程序中渲染 Calendar
组件,它应该看起来像这个截图。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 使用Python构建自定义新闻源
- 使用rpmbuild自定义构建rpm包
- 使用CSS自定义属性构建骨架屏
- 如何使用 React 构建自定义日期选择器(1)
- 如何使用 React 构建自定义日期选择器(3)
- 重新定义堡垒机 构建IT基础设施特权身份银行
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Music Recommendation and Discovery
Òscar Celma / Springer / 2010-9-7 / USD 49.95
With so much more music available these days, traditional ways of finding music have diminished. Today radio shows are often programmed by large corporations that create playlists drawn from a limited......一起来看看 《Music Recommendation and Discovery》 这本书的介绍吧!