内容简介:接着上一篇:现在您已经有了将以下代码片段添加到
接着上一篇: 如何使用 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基础设施特权身份银行
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
More Effective C++(中文版)
梅耶(Scott Meyers) / 侯捷 / 电子工业出版社 / 2011-1-1 / 59.00元
《More Effective C++:35个改善编程与设计的有效方法(中文版)》是梅耶尔大师Effective三部曲之一。继Effective C++之后,Scott Meyers于1996推出这本《More Effective C++(35个改善编程与设计的有效方法)》“续集”。条款变得比较少,页数倒是多了一些,原因是这次选材比“一集”更高阶,尤其是第5章。Meyers将此章命名为技术(tec......一起来看看 《More Effective C++(中文版)》 这本书的介绍吧!