手把手教會使用react開發日曆組件

準備工做

提早須要準備好react腳手架開發環境,因爲react已經不支持在頁面內部經過jsx.transform來轉義,咱們就本身大了個簡易的開發環境css

建立一個文件夾,命名爲react-canlendarhtml

cd ./react-canlendar

運行react

npm init

一路enter咱們獲得一個package.json的文件webpack

安裝幾個咱們須要的腳手架依賴包程序員

npm install awesome-typescript-loader typescript webpack webpack-cli -D

安裝幾個咱們須要的類庫web

npm install @types/react react react-dom --save

基礎類庫安裝完畢,開始構建webpack配置typescript

新建一個目錄config,config下面新增一個文件,名字叫作webpack.jsnpm

var path = require('path')

module.exports = {
    entry: {
        main: path.resolve(__dirname, '../src/index.tsx')
    },
    output: {
        filename: '[name].js'
    },
    resolve: {
        extensions: [".ts", ".tsx", ".js", ".json"]
    },
    module: {
        rules: [
            {test: /\.tsx?$/, use: ['awesome-typescript-loader']}
        ]
    }
}

還須要建立一個index.html文件,這是咱們的入口文件json

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="root"></div>
    <script src="./dist/main.js"></script>
</body>
</html>

以上環境只是一個極簡單的環境,真實環境要比這個複雜的多數組

好了,言歸正傳,咱們仍是聚焦到日曆組件的開發中來吧

建立一個src文件夾,內部建立一個index.tsx文件。

這個入口文件很簡單就是一個掛載

import * as React from 'react'
import * as ReactDOM from 'react-dom'

ReactDOM.render((
  <div>
    test
  </div>
), document.getElementById('root'))

ok,打開頁面能夠看到頁面正常顯示了test字樣。

咱們須要建立Calendar組件了。

建立一個components文件夾,內部建立一個Calendar.tsx文件。

import * as React from 'react'

export default class Calendar extends React.Component {
  render() {
   
    return (<div>
        日曆
    </div>)
  }
}

在index.tsx中把Calendar.tsx引入,並使用起來。因而index.tsx變成這個樣子。

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import Calendar from './components/Calendar'

ReactDOM.render((
  <div>
    <Calendar/>
  </div>
), document.getElementById('root'))

能夠看到頁面顯示了日曆字樣。

要顯示日曆,首先須要顯示日曆這個大框以及內部的一個個小框。實現這種佈局最簡單的佈局就是table了

因此咱們首先建立的是這種日曆table小框框,以及表頭的星期排列。

import * as React from 'react'

const WEEK_NAMES = ['日', '一', '二', '三', '四', '五', '六']
const LINES = [1,2,3,4,5,6]

export default class Calendar extends React.Component {
  render() {
    return (<div>
      <table cellPadding={0} cellSpacing={0} className="table">
        <thead>
        <tr>
          {
            WEEK_NAMES.map((week, key) => {
              return <td key={key}>{week}</td>
            })
          }
        </tr>
        </thead>
        <tbody>
        {
          LINES.map((l, key) => {
            return <tr key={key}>
              {
                WEEK_NAMES.map((week, index) => {
                  return <td key={index}>{index}</td>
                })
              }
            </tr>
          })
        }
        </tbody>
      </table>
    </div>)
  }
}

能夠看到咱們使用了一個星期數組做爲表頭,咱們按照慣例是從週日開始的。你也能夠從其餘星期開始,不過會對下面的日期顯示有影響,由於每月的第一天是周幾決定第一天顯示在第幾個格子裏。

那爲何行數要6行呢?由於咱們是按照最大行數來肯定表格的行數的,若是一個月有31天,而這個月的第一天恰好是週六。就確定會顯示6行了。

爲了顯示好看,我直接寫好了樣式放置在index.html中了,這個不重要,不講解。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }
        .table {
            border-collapse:collapse;
            border-spacing:0;
        }
        .table td{
            border: 1px solid #ddd;
            padding: 10px;
        }
        .table caption .caption-header{
            border-top: 1px solid #ddd;
            border-right: 1px solid #ddd;
            border-left: 1px solid #ddd;
            padding: 10px;
            display: flex;
            justify-content: space-between;
        }
        .table caption .caption-header .arrow {
            cursor: pointer;
            font-family: "宋體";
            transition: all 0.3s;
        }
        .table caption .caption-header .arrow:hover {
            opacity:0.7;
        }
    </style>
</head>
<body>
    <div id="root"></div>
    <script src="./dist/main.js"></script>
</body>
</html>

下面就要開始顯示日期了,首先要把當前月份的日期顯示出來,咱們先在組件的state中定義當前組件的狀態

state = {
    month: 0,
    year: 0,
    currentDate: new Date()
}

咱們定義一個方法獲取當前年月,爲何不須要獲取日,由於日曆都是按月顯示的。獲取日如今看來對咱們沒有意義,因而新增一個方法,設置當前組件的年月

setCurrentYearMonth(date) {
    var month = Calendar.getMonth(date)
    var year = Calendar.getFullYear(date)
    this.setState({
      month,
      year
    })
}

static getMonth(date: Date): number{
    return date.getMonth()
}

static getFullYear(date: Date): number{
    return date.getFullYear()
}

建立兩個靜態方法獲取年月,爲何是靜態方法,由於與組件的實例無關,最好放到靜態方法上去。

要想繪製一個月還須要知道一個月的天數吧,纔好繪製吧

因此咱們建立一個數組來表示月份的天數

const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]  // 暫定2月份28天吧

組件上建立一個函數,根據月份獲取天數,也是靜態的

static getCurrentMonthDays(month: number): number {
    return MONTH_DAYS[month]
}

下面還有一個重要的事情,就是獲取當前月份第一天是周幾,這樣子就能夠決定把第一天繪製在哪裏了。首先要根據年月的第一天得到date,根據這個date獲取周幾。

static getDateByYearMonth(year: number, month: number, day: number=1): Date {
    var date = new Date()
    date.setFullYear(year)
    date.setMonth(month, day)
    return date
  }

這裏得到每月的第一天是周幾了。

static getWeeksByFirstDay(year: number, month: number): number {
    var date = Calendar.getDateByYearMonth(year, month)
    return date.getDay()
  }

好了,開始在框子插入日期數字了。由於每一個日期都是不同的,這個二維數組能夠先計算好,或者經過函數直接插入到jsx中間。

static getDayText(line: number, weekIndex: number, weekDay: number, monthDays: number): any {
    var number = line * 7 + weekIndex - weekDay + 1
    if ( number <= 0 || number > monthDays ) {
      return <span>&nbsp;</span>
    }

    return number
  }

看一下這個函數須要幾個參數哈,第一個行數,第二個列數(周幾),本月第一天是周幾,本月天數。line * 7 + weekIndex表示當前格子原本是幾,減去本月第一天星期數字。爲何+1,由於索引是從0開始的,而天數則是從1開始。那麼<0 || >本月最大天數的則過濾掉,返回一個空span,只是爲了撐開td。其餘則直接返回數字。

import * as React from 'react'

const WEEK_NAMES = ['日', '一', '二', '三', '四', '五', '六']
const LINES = [1,2,3,4,5,6]
const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

export default class Calendar extends React.Component {
  state = {
    month: 0,
    year: 0,
    currentDate: new Date()
  }

  componentWillMount() {
    this.setCurrentYearMonth(this.state.currentDate)
  }

  setCurrentYearMonth(date) {
    var month = Calendar.getMonth(date)
    var year = Calendar.getFullYear(date)
    this.setState({
      month,
      year
    })
  }

  static getMonth(date: Date): number{
    return date.getMonth()
  }

  static getFullYear(date: Date): number{
    return date.getFullYear()
  }

  static getCurrentMonthDays(month: number): number {
    return MONTH_DAYS[month]
  }

  static getWeeksByFirstDay(year: number, month: number): number {
    var date = Calendar.getDateByYearMonth(year, month)
    return date.getDay()
  }

  static getDayText(line: number, weekIndex: number, weekDay: number, monthDays: number): any {
    var number = line * 7 + weekIndex - weekDay + 1
    if ( number <= 0 || number > monthDays ) {
      return <span>&nbsp;</span>
    }

    return number
  }

  static formatNumber(num: number): string {
    var _num = num + 1
    return _num < 10 ? `0${_num}` : `${_num}`
  }

  static getDateByYearMonth(year: number, month: number, day: number=1): Date {
    var date = new Date()
    date.setFullYear(year)
    date.setMonth(month, day)
    return date
  }

  checkToday(line: number, weekIndex: number, weekDay: number, monthDays: number): Boolean {
    var { year, month } = this.state
    var day = Calendar.getDayText(line, weekIndex, weekDay, monthDays)
    var date = new Date()
    var todayYear = date.getFullYear()
    var todayMonth = date.getMonth()
    var todayDay = date.getDate()

    return year === todayYear && month === todayMonth && day === todayDay
  }

  monthChange(monthChanged: number) {
    var { month, year } = this.state
    var monthAfter = month + monthChanged
    var date = Calendar.getDateByYearMonth(year, monthAfter)
    this.setCurrentYearMonth(date)
  }

  render() {
    var { year, month } = this.state
    console.log(this.state)

    var monthDays = Calendar.getCurrentMonthDays(month)
    var weekDay = Calendar.getWeeksByFirstDay(year, month)

    return (<div>
      {this.state.month}
      <table cellPadding={0} cellSpacing={0} className="table">
        <caption>
          <div className="caption-header">
            <span className="arrow" onClick={this.monthChange.bind(this, -1)}>&#60;</span>
            <span>{year} - {Calendar.formatNumber(month)}</span>
            <span className="arrow" onClick={this.monthChange.bind(this, 1)}>&gt;</span>
          </div>
        </caption>
        <thead>
          <tr>
            {
              WEEK_NAMES.map((week, key) => {
                return <td key={key}>{week}</td>
              })
            }
          </tr>
        </thead>
        <tbody>
        {
          LINES.map((l, key) => {
            return <tr key={key}>
              {
                WEEK_NAMES.map((week, index) => {
                  return <td key={index} style={{color: this.checkToday(key, index, weekDay, monthDays) ? 'red' : '#000'}}>
                    {Calendar.getDayText(key, index, weekDay, monthDays)}
                  </td>
                })
              }
            </tr>
          })
        }
        </tbody>
      </table>
    </div>)
  }
}

能夠看到最終的代碼多了一些東西,由於我加了月份的切換。

還記的上文咱們把二月份天數寫28天嘛?要不大家本身改改,判斷一下閏年。

建立了一個程序員交流微信羣,你們進羣交流IT技術

圖片描述

若是已過時,能夠添加博主微信號15706211347,拉你進羣

相關文章
相關標籤/搜索