React hooks封装的年份组件---hooks练习实例

React hooks封装的年份组件—hooks练习实例

react hooks已经出来很长时间了,看了之后一直没有做一个完整的练习,最近正好再写一个年份组件,antd 4.x版本中年份组件有的功能不能满足需求,所以自己封装了一个,一个是类组件封装,一个是对函数组件封装,练习了一下下,下文只贴了函数组件的封装,引入时参数可以传入样式默认值打开列表展示的范围值选中的回调可选年份范围,在练习期间也遇到了很多问题,拿不到最新值,还有各种错误,通过查阅网上大神的文章加上自己的理解最后可以做到正常复用。接下来看一下项目,代码有点多…

项目运行效果

在这里插入图片描述

项目目录

在这里插入图片描述
① yearPicker文件夹中index.js是年份组件,list.js是打开弹框的列表组件
② useYearPicker文件夹是引用年份组件的。
③ context中context.js是存放所有的useContext的初始化创建

年份组件内容(yearPicker.js)

import React, { useState, useEffect, useRef } from 'react'
import './style/index.less'
import { Icon } from 'antd'
import List from './list'
function YearPicker(props) {
  const [isShow, setIsShow] = useState(false)  // 是否展示list列表
  const [isShowErrIcon, setisShowErrIcon] = useState(false) //是否展示错误图标
  const [isFirst, setisFirst] = useState(true)  //默认是第一次点击输入框
  const [selectYear, setSelectYear] = useState('') // 选中的年
  const [years, setYears] = useState([])   // 传给子组建的年数组

  const show = () => {
    let { range } = props
    range = range ? range : 12
    // 初始化数据
    initData(range, selectYear)
    setIsShow(true)
  }

  useEffect(() => {
   // 页面第一次加载把父组件设置的默认值给selectYear, 如果没有穿 为当前年份
    let { defaultValue } = props
    defaultValue = defaultValue ? defaultValue : new Date().getFullYear()
    setSelectYear(defaultValue)
  }, []) //传空数组 会在挂载完成执行,不传 在挂载完成和更新完成后执行,传固定变量是在变量改变时才会执行

  document.addEventListener(  // 用来做点击input框打开list组件,点击空白list消失
    'click',
    function (e) {
      let clsName = e.target.className
      e.stopPropagation()
      // e.stopImmediatePropagation()
      if (typeof clsName === 'string' && !clsName.includes('calendar')) {
        hide()
      } else {
        return
      }
    },
    false
  )
  const initData = (range, selectyear) => {
    range = range ? range : 12
    let year = selectyear - 1970 //50
    let curr = year % range // 5
    let startYear = selectYear - curr //2015
    let endYear = startYear + range - 1 // 2023 
    getYearArr(startYear, endYear)
  }
  const getYearArr = (startYear, endYear) => {
    let arr = []
    for (let i = startYear; i <= endYear; i++) {
      arr.push(Number(i))
    }
    setYears(arr)
  }
  const hide = () => {
    setIsShow(false)
  }
  const pre = () => {
    if (years[0] <= 1970) {
      return
    }
    getRange('pre')
  }
  const next = () =>  {
    getRange('next')
  }
  const getRange =(type) => {
    const range = Number(props.range)
    let start = Number(years[0])
    let end = Number(years[years.length - 1])
    let newStart = 0
    let newEnd = 0
    if (type == 'pre') {
      newStart = parseInt(start - range)
      newEnd = parseInt(end - range)
    }
    if (type == 'next') {
      newStart = parseInt(start + range)
      newEnd = parseInt(end + range)
    }
    getYearArr(newStart, newEnd)
  }
  // 选择年份
  const cback = (e)=> {
    hide()
    setisFirst(false)
    setSelectYear(Number(e.target.value))
    // 选中值传给父组件
    props.callback && props.callback(Number(e.target.value))
  }
 // 鼠标移入移出
  const enter = () => {
    !isFirst &&  setisShowErrIcon(true)
  }
  const leave = () => {
    if(isFirst){
      return
    }else{
      !isFirst && isShowErrIcon && setisShowErrIcon(false)
    }
  }
  const close = () => {
    setisShowErrIcon(false)
    setisFirst(true)
  }
  return (
    <div className="calendar" style={props.style}>
      <div className="calendar-input"  onMouseEnter={enter} onMouseLeave={leave}>
        <input
          className="calendar-value"
          placeholder="请选择年份"
          value={isFirst ? '' : selectYear}
          onFocus={show}
          readOnly
        />
        {!isShowErrIcon ? (
          <Icon type="calendar" className="calendar-icon" />
        ) : (
          <Icon
            type="close-circle"
            theme="filled"
            className="calendar-icon"
            onClick={close}
          />
        )}
      </div> 
      {isShow ? ( <List data={years}  cback={cback}  pre={pre}  
          next={next}  selectYear={selectYear} /> ) :  null } </div>
  )
}
export default YearPicker

列表组件内容(list.js)

import React, { useEffect, useState, useContext } from 'react'
import { Icon } from 'antd'
import './style/index.less'
import { Disabled } from '../../context/context'
function List(props) {
   // 拿到上层组件 Provider提供的值
  const disabled = useContext(Disabled)
  const [yearsArr, setYearsArr] = useState([])
 
  const rangeYear = (startYear, endYear) => {
    let startValue = Number(startYear)
    let endValue = Number(endYear)
    let years = []
    for (let i = 0; i < yearsArr.length; i++) {
      years.push(yearsArr[i].number)
    }
    // 判断范围
    if (years.includes(startValue) && years.includes(endValue)) {
    //  判断年数组包含开始年份和结束年份,区间禁用
      for (let i = 0; i < years.indexOf(startValue); i++) {
        yearsArr[i].disabled = true
      }
      for (let i = years.indexOf(endValue) + 1; i < years.length; i++) {
        yearsArr[i].disabled = true
      }
    } 
    else if (years.includes(startValue)) {
    //  判断年数组只包含开始年份,
      for (let i = 0; i < years.indexOf(startValue); i++) {
        yearsArr[i].disabled = true
      }
    } else if (years[years.length - 1] < startValue) {
      // 判断年数组的最大值 小于 开始年份 整个年数组禁用
      for (let i = 0; i < years.length; i++) {
        yearsArr[i].disabled = true
      }
    } else if (years.includes(endValue)) {
     //  判断年数组中只包含结束年份,
      for (let i = years.indexOf(endValue) + 1; i < years.length; i++) {
        yearsArr[i].disabled = true
      }
    } else if (years[0] > endValue) {
    // 判断年数组的最小值 小于 开始年份 整个年数组禁用
      for (let i = 0; i < years.length; i++) {
        yearsArr[i].disabled = true
      }
    }
  }

  useEffect(() => { 
   // 当 传入的年数组改变时,触发,会重新渲染组件,相当于类组件中的更新,或者componentWillReceiveProps
    let year = []
    for (let i = 0; i < props.data.length; i++) {
      year.push({ number: props.data[i], disabled: false })
    }
    setYearsArr(year)
    console.log(yearsArr)
  }, [props.data])
  
 // 因为第一次yearsArr初始值为[]空数组,当useEffect执行后才会给yearsArr重新赋值,所以在这里要判断长度,长度大于0再判断区间范围
  if(yearsArr.length > 0 && disabled ){
    rangeYear(disabled[0], disabled[1])
  }
  let { selectYear, pre, next, data, cback } = props

  return (
    <div className="calendar-container">
      <div className="calendar-head">
        <Icon
          type="double-left"
          className="arrow calendar-prevBtn"
          onClick={pre}
        />
        <span className="calendar-currentYear">{selectYear}</span>
        <Icon
          type="double-right"
          className="arrow calendar-nextBtn"
          onClick={next}
        />
      </div>
      <div className="calendar-body">
        <ul className="calendar-body-ul">
          {yearsArr.length ?  yearsArr.map((item, index) => (
            <li
              key={index}
              title={item.number}
              className={
                item.number === selectYear
                  ? 'calendar-body-ul-li selectedYear'
                  : 'calendar-body-ul-li'
              }
            >
              <button disabled={item.disabled} onClick={cback} value={item.number} className={item.disabled ? 'calendar-disabledStyle' : 'calendar-btn'}>
                {item.number}
              </button>
            </li>
          )): ''}
        </ul>
      </div>
    </div>
  )
}
export default List

引用年份组件内容,userYearPicker —> index.js

import React from 'react'
import YearPicker from '../yearPicker'
import './style/index.less'
import { Disabled } from  '../../context/context'
export default function UserYearPicker(){
  let style = {
    width: '150px'
  }
  // 获取选中的值的回调
  const getSelectYaer = (year) => {
    console.log(year)
  }
  return(
    <div className='useCalendar'>
      <Disabled.Provider value={[2006, 2018]}>
        <YearPicker style={style}  range={9} callback={getSelectYaer} ></YearPicker>
      </Disabled.Provider>
    </div>
  )
}

引用context管理状态 context–>context.js

import React from 'react'
//创建 可选择范围 ,初始值为 空
export  const Disabled = React.createContext();

less代码

*{
  margin: 0;
  padding: 0;
}
.calendar{
  margin: 0 auto;
  position: relative;
  min-width: 150px;
  max-width: 500px;
  &-input{
    width: 100%;
    position: relative;
    cursor: pointer;
    .calendar-icon{
      position: absolute;
      right: 10px;
      top: 10px;
      color: #929292;
    }
  }
  input {
    width: 100%;
    height: 31px;
    border: 1px solid rgb(189, 189, 189);
    border-radius: 4px;
    font-size: 12px;
    outline: none;
    display: block;
    padding: 6px 7px;
    transition: all 0.3s;

    &:hover,
    &:active {
      border-color: rgb(114, 189, 240);
    }
  }
  &-container {
    width: 120%;
    outline: none;
    border-radius: 4px;
    box-shadow: 0 1px 6px rgb(189, 189, 189);
    border: 1px solid rgb(201, 201, 201);
    position: absolute;
    top: 0;
    left: 0;
    z-index: 999;
    background-color: #fff;
    line-height: 1.5;
  }

  &-head {
    height: 34px;
    line-height: 34px;
    text-align: center;
    width: 100%;
    position: relative;
    border-bottom: 1px solid #e9e9e9;

  &-currentYear {
      padding: 0 2px;
      font-weight: bold;
      display: inline-block;
      color: rgba(0, 0, 0, 0.65);
      line-height: 34px;
    }

  .arrow {
      position: absolute;
      top: 0;
      color: rgba(0, 0, 0, 0.43);
      padding: 0 5px;
      font-size: 12px;
      display: inline-block;
      line-height: 34px;
      cursor: pointer;
      &:hover {
        color: red;
      }
  }

  .calendar-prevBtn {
    left: 7px;
  }

  .calendar-nextBtn {
    right: 7px;
  }
  }

  &-body {
    width: 100%;

    &-ul {
      list-style: none;
      flex-wrap: wrap;
      display: flex;
      align-items: center;
      justify-content: space-between;
      &-li {
        float: left;
        text-align: center;
        width: 30%;
        cursor: pointer;

        >button {
          cursor: pointer;
          outline: none;
          border: 0;
          display: inline-block;
          margin: 0 auto;
          color: rgba(0, 0, 0, 0.65);
          background: transparent;
          text-align: center;
          height: 24px;
          line-height: 24px;
          padding: 0 6px;
          border-radius: 4px;
          transition: background 0.3s ease;
          margin: 14px 0;

          &:hover {
            color: red;
          }
        }
      }

    .selectedYear {
      >button {
        background: #108ee9;
        color: #fff;

        &:hover {
          color: #fff;
        }
      }
    }
    }
  }
}
.calendar-disabledStyle{
  color : rgb(173, 173, 173)!important;
  background-color: rgb(245, 245, 245)!important;
}

结语

所有的代码至此结束,之前学习hooks没有敲,真正来写的时候还是遇到很多问题的,所以,还是得多敲多敲!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值