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没有敲,真正来写的时候还是遇到很多问题的,所以,还是得多敲多敲!!!