资讯专栏INFORMATION COLUMN

用React hooks写了一个日历组件,来看看?

cangck_X / 2757人阅读

摘要:然后使用,在的上绑定了,很简单。日历遍历的思路这一次的提交主要是确定了日历组件,怎么写,具体思路看下面的代码,尽量通过注释把思路讲的清楚一些。

前言

在最近的项目中,大量的尝试了react hooks,我们的组件库用的是Next,除了一个地方因为要使用Form + Field的组合,所以使用了class组件,经过了这个项目,也算是总结一些使用的经验,所以准备自己封装一个日历组件来分享一下。以下也会通过git中的commit记录,来分析我的思路。

下来看下效果(因为软件的原因,转的gif竟然如此抖动,顺便求一个mac上转换gif比较好用的软件)

同时奉上代码地址: 仓库

初始化项目

只是使用了create-react-app进行了项目的搭建,然后总体就是hooks + typescript,当然写的不够好,就没有往npm上发的意愿。

工具方法,保存状态

首先是写了一个工具方法,作用是将时间戳转化成 年月日 三个属性。

interface Res {
    year: number;
    month: number;
    day: number;
}

export const getYearMonthDay = (value: number):Res => {
    const date = new Date(value);
    return {
        year: date.getFullYear(),
        month: date.getMonth(),
        day: date.getDay()
    }    
} 

然后使用useState,在input的dom上绑定了value,很简单。

const Test:React.FC = memo(({value = Date.now(), onChange}) => {
  console.log("render -------");
  const [date, setDate] = useState<Date>(new Date(value));
  const { year, month, day } = getYearMonthDay(date.getTime());
  console.log(year, month, day);
  return (
    <div>
      <input type="text" value={`${year} - ${month} - ${day}`} />
    div>
  )
});
日历遍历的思路

这一次的提交主要是确定了日历组件,怎么写,具体思路看下面的代码,尽量通过注释把思路讲的清楚一些。

// 以下两个数组只是为了遍历
const ary7 = new Array(7).fill("");
const ary6 = new Array(6).fill("");

const Test: React.FC = memo(({ value = Date.now(), onChange }) => {
  const [date, setDate] = useState<Date>(new Date(value));
  // useState保存下面弹窗收起/展开的状态
  const [contentVisible, setContentVisible] = useState(true);
  const { year, month, day } = getYearMonthDay(date.getTime());
  // 获取当前选中月份的第一天
  const currentMonthFirstDay = new Date(year, month, 1);
  // 判断出这一天是星期几
  const dayOfCurrentMonthFirstDay = currentMonthFirstDay.getDay();
  // 然后当前日历的第一天就应该是月份第一天向前移几天
  const startDay = new Date(currentMonthFirstDay.getTime() - dayOfCurrentMonthFirstDay * 1000 * 60 * 60 * 24);
  const dates:Date[] = [];
  // 生成一个长度为42的数组,里面记录了从第一天开始的每一个date对象
  for (let index = 0; index < 42; index++) {
    dates.push(new Date(startDay.getTime() + 1000 * 60 * 60 * 24 * index));
  }
  return (
    <div>
      <input type="text" value={`${year} - ${month} - ${day}`} />
      {
        contentVisible && (
          <div>
            <div>
              <span><span>
              <span>< <span>
              <span>span>
              <span>>span>
              <span>> >span>
            div>
            <div>
                // 生成的日历应该是7*6的,然后遍历出42个span, 这就是之前生成的两个常量数组的作用
              {
                ary6.map((_, index) => {
                  return (
                    <div>
                      {
                        ary7.map((__, idx) => {
                          const num = index * 7 + idx;
                          console.log(num);
                          
                          const curDate = dates[num]
                          return (
                            <span>{curDate.getDate()}span>
                          )
                        })
                      }
                    div>
                  )
                })
              }
            div>
          div>
        )
      }
    div>
  )
});
处理document点击事件
const Test: React.FC = memo(({ value = Date.now(), onChange }) => {
  // 使用useRef保存最外一层包裹的div
  const wrapper = useRef(null);
  // 展开收起的方法,都使用了useCallback,传入空数组,让每次渲染都返回相同的函数
  const openContent = useCallback(
    () => setContentVisible(true),
    []
  );
  const closeContent = useCallback(
    () => setContentVisible(false),
    []
  );
  const windowClickhandler = useCallback(
    (ev: MouseEvent) => {
      let target = ev.target as HTMLElement;
      if(wrapper.current && wrapper.current.contains(target)) {
      } else {
        closeContent();
      }
    },
    []
  )
  // 使用useEffect模拟componentDidMount和componentWillUnmount的生命周期函数,来绑定事件
  useEffect(
    () => {
      window.addEventListener("click",windowClickhandler);
      return () => {
        window.removeEventListener("click", windowClickhandler);
      }
    },
    []
  )
  return (
    <div ref={wrapper}>
      // 之前的那些东西,没有变化
    div>
  )
});
处理每个日期的点击事件
  // 使用setDate,处理,这里其实第二个参数传递一个空数组即可,因为这个函数是不依赖当前的date状态来变化的。
  const dateClickHandler = useCallback(
    (date:Date) => {
      setDate(date);
      const { year, month, day } = getYearMonthDay(date.getTime());
      onChange(`${year}-${month}-${day}`);
      setContentVisible(false);
    },
    [date]
  )

 dateClickHandler(curDate)}>{curDate.getDate()}

支持value传递
// 先判断以下value是否传递,如果没传默认就是当前的时间
const DatePicker: React.FC = ({ value = "", onChange = () => { } }) => {
  let initialDate: Date;
  if (value) {
    let [year, month, day] = value.split("-");
    initialDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
  } else {
    initialDate = new Date();
  }
  const [date, setDate] = useState<Date>(initialDate);
 }
月份切换的事件处理

月份处理就是当前月份的第一天向前移动一个月或者一个月等等。

const DatePicker: React.FC = ({ value = "", onChange = () => { } }) => {
  // 之前这里的currentMonthFirstDay是直接由date得出的,现在成为组件的state就可以让他支持变化。
  const { year, month, day } = getYearMonthDay(date.getTime());
  const [currentMonthFirstDay, setCurrentMonthFirstDay] = useState<Date>(new Date(year, month, 1));
  const { year: chosedYear, month: chosedMonth } = getYearMonthDay(currentMonthFirstDay.getTime());
  const dayOfCurrentMonthFirstDay = currentMonthFirstDay.getDay();
  const startDay = new Date(currentMonthFirstDay.getTime() - dayOfCurrentMonthFirstDay * 1000 * 60 * 60 * 24);
  const dates: Date[] = [];
  for (let index = 0; index < 42; index++) {
    dates.push(new Date(startDay.getTime() + 1000 * 60 * 60 * 24 * index));
  }

  const openContent = useCallback(
    () => setContentVisible(true),
    []
  );
  const closeContent = useCallback(
    () => setContentVisible(false),
    []
  );
  const windowClickhandler = useCallback(
    (ev: MouseEvent) => {
      let target = ev.target as HTMLElement;
      if (wrapper.current && wrapper.current.contains(target)) {
      } else {
        closeContent();
      }
    },
    []
  );
  const dateClickHandler = useCallback(
    (date: Date) => {
      const { year, month, day } = getYearMonthDay(date.getTime());
      onChange(`${year}-${month + 1}-${day}`);
      setContentVisible(false);
    },
    [date]
  );
  // 这里所有的月份切换事件都选择了使用了函数式更新
  const prevMonthHandler = useCallback(
    () => {
      setCurrentMonthFirstDay(value => {
        let { year, month } = getYearMonthDay(value.getTime());
        if (month === 0) {
          month = 11;
          year--;
        } else {
          month--;
        }
        return new Date(year, month, 1)
      })
    },
    []
  );
  const nextMonthHandler = useCallback(
    () => {
      setCurrentMonthFirstDay(value => {
        let { year, month } = getYearMonthDay(value.getTime());
        if (month === 11) {
          month = 0;
          year++;
        } else {
          month++;
        };
        return new Date(year, month, 1);
      })
    },
    []
  );
  const prevYearhandler = useCallback(
    () => {
      setCurrentMonthFirstDay(value => {
        let { year, month } = getYearMonthDay(value.getTime());
        return new Date(--year, month, 1)
      })
    },
    []
  );
  const nextYearHandler = useCallback(
    () => {
      setCurrentMonthFirstDay(value => {
        let { year, month } = getYearMonthDay(value.getTime());
        return new Date(++year, month, 1)
      })
    },
    []
  )
  return (
    <div ref={wrapper}>
      <input type="text" value={`${year} - ${month + 1} - ${day}`} onFocus={openContent} />
      {
        contentVisible && (
          <div className="content">
            <div className="header">
              <span onClick={prevYearhandler}>< <span>
              <span onClick={prevMonthHandler}><span>
              <span>{`${chosedYear} - ${chosedMonth + 1}`}span>
              <span onClick={nextMonthHandler}>>span>
              <span onClick={nextYearHandler}>> >span>
            div>
          div>
        )
      }
    div>
  )
};

处理porps变化

工具方法

export const getDateFromString = (str: string):Date => {
    let [year, month, day] = str.split("-");
    return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
}

组件

const DatePicker: React.FC = ({ value = "", onChange = () => { } }) => {
  let initialDate: Date;
  if (value) {
    initialDate = getDateFromString(value);
  } else {
    initialDate = new Date();
  };
  
  const [date, setDate] = useState<Date>(initialDate);
  
  // 使用了useRef来保存上一次渲染时传递的value值,
  const prevValue = useRef(value);

  useEffect(
    () => {
      // 仅当value值变化且不同与上一次值时,才会重新进行改变自身date状态。
      if (prevValue.current !== value) {
        let newDate = value ");new Date();
        setDate(newDate);
        const { year, month } = getYearMonthDay(newDate.getTime());
        setCurrentMonthFirstDay(new Date(year, month, 1))
      }
    },
    [value]
  )

  return (
    ...
  )
};

这里现在想来其实也可以用useMemo来处理传递进来的value值,这也是一种思路。稍后实现一下。

最后

最后贴下代码github地址。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/6921.html

相关文章

  • 30分钟精通React今年最劲爆的新特性——React Hooks

    摘要:所以我们做的事情其实就是,声明了一个状态变量,把它的初始值设为,同时提供了一个可以更改的函数。 你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? ——拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。 你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? ——拥有了Hooks,生命周期钩子函数可以先丢一边了。 你在还...

    icattlecoder 评论0 收藏0
  • [译]开发类 redux 库来理解状态管理

    摘要:第一个功能是普通经典类组件,也就是众所周知的有状态组件。我们准备创建一个上下文环境来存放全局状态,然后把它的包裹在一个有状态组件中,然后用来管理状态。接下来我们需要用有状态组件包裹我们的,利用它进行应用状态的管理。 原文地址对于想要跳过文章直接看结果的人,我已经把我写的内容制作成了一个库:use-simple-state,无任何依赖(除了依赖 react ),只有3kb,相当轻量。 ...

    KoreyLee 评论0 收藏0
  • RN自定义组件封装 - 拖拽选择日期的日历

    摘要:前言由于最近接到一个需要支持拖拽选择日期的日历需求,做出来感觉体验和效果都还不错,所以今天想跟大家分享一下封装这个日历组件的过程。其中,代表该日期当前的状态,主要是用以区分用户在拖拽操作日历时,有没有选中该日期。 1. 前言 由于最近接到一个需要支持拖拽选择日期的日历需求,做出来感觉体验和效果都还不错,所以今天想跟大家分享一下封装这个日历组件的过程。 2. 调研开始 正所谓磨刀不误砍柴...

    ivydom 评论0 收藏0
  • React Hooks+Umi+TypeScript+Dva开发体验

    摘要:为什么选择是开发和维护的一种面向对象的编程语言。一在组件组件复用状态逻辑很难没有提供将可复用性行为附加到组件的途径例如,把组件连接到。如此很容易产生,并且导致逻辑不一致。同时,这也是很多人将与状态管理库结合使用的原因之一。 前端时间,接触了hooks,研究了一段时间后感觉使用起来十分方便,正好公司开了一个新的小项目,正好使用hooks来实践一下。 技术选型 1.为什么选择umi 在之前...

    stonezhu 评论0 收藏0
  • React函数式组件说到Hooks

    摘要:难道还不允许设计得对新人更友好了我们先把做成就是找骂啊这怎么怪到我们头上了事实是,即使在内部,也显然不是所有程序员都熟悉函数式编程的概念。 1.前言介绍 历史React在2013年开源,在2015引入函数式组件,不过在日常开发中经常被忽略。ReactJS Core Team 确实大部分成员都曾在推特上公开夸赞过对函数式编程 与 ML 系语言(或其特性)的优点:Sebastian 日常提...

    lavor 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<