• Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • React 通关秘籍

    • 1.关于本小册
    • 2.一网打尽组件常用Hook
    • 3.Hook的闭包陷阱的成因和解决方案
    • 4.React组件如何写TypeScript类型
    • 5.React组件如何调试
    • 6.受控模式VS非受控模式
    • 7.组件实战:迷你Calendar
    • 8.组件实战:Calendar日历组件(上)
    • 9.组件实战:Calendar日历组件(下)
    • 10.快速掌握Storybook
    • 11.React组件如何写单测
    • 12.深入理解Suspense和ErrorBoundary
    • 13.组件实战:Icon图标组件
    • 14.组件实战:Space间距组件
    • 15.React.Children和它的两种替代方案
    • 16.三个简单组件的封装
    • 17.浏览器的5种Observer
    • 18.组件实战:Watermark防删除水印组件
    • 19.手写react-lazyload
    • 20.图解网页的各种距离
    • 21.自定义hook练习
    • 22.自定义hook练习(二)
    • 23.用react-spring做弹簧动画
    • 24.react-spring结合use-gesture手势库实现交互动画
    • 25.用react-transition-group和react-spring做过渡动画
    • 26.快速掌握Tailwind:最流行的原子化CSS框架
    • 27.用CSSModules避免样式冲突
    • 28.CSSInJS:快速掌握styled-components
    • 29.react-spring实现滑入滑出的转场动画
    • 30.组件实战:Message全局提示组件
    • 31.组件实战:Popover气泡卡片组件
    • 32.项目里如何快速定位组件源码
    • 33.一次超爽的React调试体验
    • 34.组件实战:ColorPicker颜色选择器(一)
    • 35.组件实战:ColorPicker颜色选择器(二)
    • 36.组件实战:onBoarding漫游式引导组件
    • 37.组件实战:Upload拖拽上传
    • 38.组件实战:Form表单组件
    • 39.React组件库都是怎么构建的
    • 40.组件库实战:构建esm和cjs产物,发布到npm
    • 41.组件库实战:构建umd产物,通过unpkg访问
    • 42.数据不可变:immutable和immer
    • 43.基于ReactRouter实现keepalive
    • 44.Historyapi和ReactRouter实现原理
    • 45.ReactContext的实现原理和在antd里的应用
    • 46.ReactContext的性能缺点和解决方案
    • 47.手写一个Zustand
    • 48.原子化状态管理库Jotai
    • 49.用react-intl实现国际化
    • 50.国际化资源包如何通过Excel和GoogleSheet分享给产品经理
    • 51.基于react-dnd实现拖拽排序
    • 52.react-dnd实战:拖拽版TodoList
    • 53.ReactPlayground项目实战:需求分析、实现原理
    • 54.ReactPlayground项目实战:布局、代码编辑器
    • 55.ReactPlayground项目实战:多文件切换
    • 56.ReactPlayground项目实战:babel编译、iframe预览
    • 57.ReactPlayground项目实战:文件增删改
    • 58.ReactPlayground项目实战:错误显示、主题切换
    • 59.ReactPlayground项目实战:链接分享、代码下载
    • 60.ReactPlayground项目实战:WebWorker性能优化
    • 61.ReactPlayground项目实战:总结
    • 62.手写MiniReact:思路分析
    • 63.手写MiniReact:代码实现
    • 64.手写MiniReact:和真实React源码的对比
    • 65.React18的并发机制是怎么实现的
    • 66.Ref的实现原理
    • 67.低代码编辑器:核心数据结构、全局store
    • 68.低代码编辑器:拖拽组件到画布、拖拽编辑json
    • 69.低代码编辑器:画布区hover展示高亮框
    • 70.低代码编辑器:画布区click展示编辑框
    • 71.低代码编辑器:组件属性、样式编辑
    • 72.低代码编辑器:预览、大纲
    • 73.低代码编辑器:事件绑定
    • 74.低代码编辑器:动作弹窗
    • 75.低代码编辑器:自定义JS
    • 76.低代码编辑器:组件联动
    • 77.低代码编辑器:拖拽优化、Table组件
    • 78.低代码编辑器:Form组件、store持久化
    • 79.低代码编辑器:项目总结
    • 80.快速掌握ReactFlow画流程图
    • 81.ReactFlow振荡器调音:项目介绍
    • 82.ReactFlow振荡器调音:流程图绘制
    • 83.ReactFlow振荡器调音:合成声音
    • 84.AudioContext实现在线钢琴
    • 85.React服务端渲染:从SSR到hydrate
    • 86.小册总结

基本的布局完成了,我们来添加一些参数:

export interface CalendarProps {
    value: Dayjs;
    style?: CSSProperties;
    className?: string | string[];
    // 定制日期显示,会完全覆盖日期单元格
    dateRender?: (currentDate: Dayjs) => ReactNode;
    // 定制日期单元格,内容会被添加到单元格内,只在全屏日历模式下生效。
    dateInnerContent?: (currentDate: Dayjs) => ReactNode;
    // 国际化相关
    locale?: string;
    onChange?: (date: Dayjs) => void;
}

style 和 className 用于修改 Calendar 组件外层容器的样式。

内部的布局我们都是用的 flex,所以只要外层容器的样式变了,内部的布局会自动适应。

dateRender 是用来定制日期单元格显示的内容的。

比如加一些日程安排,加一些农历或者节日信息:

dateRender 是整个覆盖,连带日期的数字一起,而 dateInnerContent 只会在日期的数字下添加一些内容。

这两个 props 是不一样的。

locale 是用于国际化的,比如切换到中文显示或者是英文显示。

onChange 是当选择了日期之后会触发的回调。

然后实现下这些参数对应的逻辑。

首先是 className 和 style:

function Calendar(props: CalendarProps) {
    const { value, style, className } = props;

    const classNames = cs("calendar", className);

    return (
        <div className={classNames} style={style}>
            <Header></Header>
            <MonthCalendar {...props} />
        </div>
    );
}

这里用 classnames 这个包来做 className 的合并。

npm install classnames

它可以传入对象或者数组,会自动合并,返回最终的 className:

当 className 的确定需要一段复杂计算逻辑的时候,就用 classname 这个包。

测试下:

import dayjs from "dayjs";
import Calendar from "./Calendar";

function App() {
    return (
        <div className="App">
            <Calendar
                value={dayjs("2023-11-08")}
                className={"aaa"}
                style={{ background: "yellow" }}></Calendar>
        </div>
    );
}

export default App;

className 和 style 的处理没问题。

然后我们处理下一个 props: dateRender 和 dateInnerContent。

在 MonthCalendar 里把它取出来,传入到 renderDays 方法里:

const { dateRender, dateInnerContent } = props;
renderDays(allDays, dateRender, dateInnerContent);

dateRender 的处理也很简单,就是把渲染日期的逻辑换一下:

在 App.tsx 里传入 dateRender 参数:

import dayjs from "dayjs";
import Calendar from "./Calendar";

function App() {
    return (
        <div className="App">
            <Calendar
                value={dayjs("2023-11-08")}
                dateRender={(value) => {
                    return (
                        <div>
                            <p
                                style={{
                                    background: "yellowgreen",
                                    height: "50px",
                                }}>
                                {value.format("YYYY/MM/DD")}
                            </p>
                        </div>
                    );
                }}></Calendar>
        </div>
    );
}

export default App;

这样,渲染的内容就换成自定义的了:

不过现在我们没有做内容溢出时的处理:

加个 overflow: hidden 就好了:

而且之前加 padding 的位置也不对。

改一下渲染日期的逻辑,如果传了 dateRender 那就整个覆盖日期单元格,否则就是只在下面渲染 dateInnerContent 的内容:

function renderDays(
    days: Array<{ date: Dayjs; currentMonth: boolean }>,
    dateRender: MonthCalendarProps["dateRender"],
    dateInnerContent: MonthCalendarProps["dateInnerContent"]
) {
    const rows = [];
    for (let i = 0; i < 6; i++) {
        const row = [];
        for (let j = 0; j < 7; j++) {
            const item = days[i * 7 + j];
            row[j] = (
                <div
                    className={
                        "calendar-month-body-cell " +
                        (item.currentMonth
                            ? "calendar-month-body-cell-current"
                            : "")
                    }>
                    {dateRender ? (
                        dateRender(item.date)
                    ) : (
                        <div className="calendar-month-body-cell-date">
                            <div className="calendar-month-body-cell-date-value">
                                {item.date.date()}
                            </div>
                            <div className="calendar-month-body-cell-date-content">
                                {dateInnerContent?.(item.date)}
                            </div>
                        </div>
                    )}
                </div>
            );
        }
        rows.push(row);
    }
    return rows.map((row) => (
        <div className="calendar-month-body-row">{row}</div>
    ));
}

改下对应的样式:

把加 padding 的位置改为内部的元素。

测试下:

import dayjs from "dayjs";
import Calendar from "./Calendar";

function App() {
    return (
        <div className="App">
            <Calendar
                value={dayjs("2023-11-08")}
                dateInnerContent={(value) => {
                    return (
                        <div>
                            <p
                                style={{
                                    background: "yellowgreen",
                                    height: "30px",
                                }}>
                                {value.format("YYYY/MM/DD")}
                            </p>
                        </div>
                    );
                }}></Calendar>
        </div>
    );
}

export default App;

这样,dateRender 和 dateInnerContent 的逻辑就完成了。

接下来做国际化,也就是 locale 参数的处理。

国际化就是可以让日历支持中文、英文、日文等,其实也很简单,就是把写死的文案换成按照 key 从配置中取的文案就行了。

定义下用到的 ts 类型 src/Calendar/locale/interface.ts

export interface CalendarType {
    formatYear: string;
    formatMonth: string;
    today: string;
    month: {
        January: string;
        February: string;
        March: string;
        April: string;
        May: string;
        June: string;
        July: string;
        August: string;
        September: string;
        October: string;
        November: string;
        December: string;
    } & Record<string, any>;
    week: {
        monday: string;
        tuesday: string;
        wednesday: string;
        thursday: string;
        friday: string;
        saturday: string;
        sunday: string;
    } & Record<string, any>;
}

然后分别定义中文和英文的配置:

src/Calendar/locale/zh-CN.ts

import { CalendarType } from "./interface";

const CalendarLocale: CalendarType = {
    formatYear: "YYYY 年",
    formatMonth: "YYYY 年 MM 月",
    today: "今天",
    month: {
        January: "一月",
        February: "二月",
        March: "三月",
        April: "四月",
        May: "五月",
        June: "六月",
        July: "七月",
        August: "八月",
        September: "九月",
        October: "十月",
        November: "十一月",
        December: "十二月",
    },
    week: {
        monday: "周一",
        tuesday: "周二",
        wednesday: "周三",
        thursday: "周四",
        friday: "周五",
        saturday: "周六",
        sunday: "周日",
    },
};

export default CalendarLocale;

src/Calendar/locale/zh-CN.ts

把会用到的文案列出来。

然后再写个英文版:

src/Calendar/locale/en-US.ts

import { CalendarType } from "./interface";

const CalendarLocale: CalendarType = {
    formatYear: "YYYY",
    formatMonth: "MMM YYYY",
    today: "Today",
    month: {
        January: "January",
        February: "February",
        March: "March",
        April: "April",
        May: "May",
        June: "June",
        July: "July",
        August: "August",
        September: "September",
        October: "October",
        November: "November",
        December: "December",
    },
    week: {
        monday: "Monday",
        tuesday: "Tuesday",
        wednesday: "Wednesday",
        thursday: "Thursday",
        friday: "Friday",
        saturday: "Saturday",
        sunday: "Sunday",
    },
};

export default CalendarLocale;

我们先把上面的周一到周日的文案替换了:

在 MonthCalendar 引入中文的资源包:

然后把之前写死的文案,改成按照 key 从资源包中取值的方式:

function MonthCalendar(props: MonthCalendarProps) {
    const { dateRender, dateInnerContent } = props;

    const weekList = [
        "sunday",
        "monday",
        "tuesday",
        "wednesday",
        "thursday",
        "friday",
        "saturday",
    ];

    const allDays = getAllDays(props.value);

    return (
        <div className="calendar-month">
            <div className="calendar-month-week-list">
                {weekList.map((week) => (
                    <div className="calendar-month-week-list-item" key={week}>
                        {CalendarLocale.week[week]}
                    </div>
                ))}
            </div>
            <div className="calendar-month-body">
                {renderDays(allDays, dateRender, dateInnerContent)}
            </div>
        </div>
    );
}

现在渲染出来的是这样的:

只要改一下用的资源包:

文案就变了:

这就是国际化。

当然,现在我们是手动切换的资源包,其实应该是全局统一配置的。

这个可以通过 context 来做:

新建 src/Calendar/LocaleContext.tsx

import { createContext } from "react";

export interface LocaleContextType {
    locale: string;
}

const LocaleContext = createContext<LocaleContextType>({
    locale: "zh-CN",
});

export default LocaleContext;

然后在 Calendar 组件里用 provider 修改 context 的值:

如果传入了参数,就用指定的 locale,否则,就从浏览器取当前语言:

加一个国际化资源包的入口:

src/Calendar/locale/index.ts

import zhCN from "./zh-CN";
import enUS from "./en-US";
import { CalendarType } from "./interface";

const allLocales: Record<string, CalendarType> = {
    "zh-CN": zhCN,
    "en-US": enUS,
};

export default allLocales;

把 MonthCalendar 组件的 locale 改成从 context 获取的:

const localeContext = useContext(LocaleContext);

const CalendarLocale = allLocales[localeContext.locale];

这样,当不指定 locale 时,就会按照浏览器的语言来设置:

当指定 locale 时,就会切换为指定语言的资源包:

接下来,我们实现 value 和 onChange 参数的逻辑。

在 MonthCalendar 里取出 value 参数,传入 renderDays 方法:

用 classnames 的 api 来拼接 className,如果是当前日期,就加一个 xxx-selected 的 className:

function renderDays(
    days: Array<{ date: Dayjs; currentMonth: boolean }>,
    dateRender: MonthCalendarProps["dateRender"],
    dateInnerContent: MonthCalendarProps["dateInnerContent"],
    value: Dayjs
) {
    const rows = [];
    for (let i = 0; i < 6; i++) {
        const row = [];
        for (let j = 0; j < 7; j++) {
            const item = days[i * 7 + j];
            row[j] = (
                <div
                    className={
                        "calendar-month-body-cell " +
                        (item.currentMonth
                            ? "calendar-month-body-cell-current"
                            : "")
                    }>
                    {dateRender ? (
                        dateRender(item.date)
                    ) : (
                        <div className="calendar-month-body-cell-date">
                            <div
                                className={cs(
                                    "calendar-month-body-cell-date-value",
                                    value.format("YYYY-MM-DD") ===
                                        item.date.format("YYYY-MM-DD")
                                        ? "calendar-month-body-cell-date-selected"
                                        : ""
                                )}>
                                {item.date.date()}
                            </div>
                            <div className="calendar-month-cell-body-date-content">
                                {dateInnerContent?.(item.date)}
                            </div>
                        </div>
                    )}
                </div>
            );
        }
        rows.push(row);
    }
    return rows.map((row) => (
        <div className="calendar-month-body-row">{row}</div>
    ));
}

添加对应的样式:

&-selected {
    background: blue;
    width: 28px;
    height: 28px;
    line-height: 28px;
    text-align: center;
    color: #fff;
    border-radius: 50%;
    cursor: pointer;
}

现在渲染出来是这样的:

然后我们加上点击的处理:

interface MonthCalendarProps extends CalendarProps {
    selectHandler?: (date: Dayjs) => void;
}

添加一个 selectHandler 的参数,传给 renderDays 方法。

renderDays 方法里取出来,给日期添加上点击事件:

function renderDays(
    days: Array<{ date: Dayjs; currentMonth: boolean }>,
    dateRender: MonthCalendarProps["dateRender"],
    dateInnerContent: MonthCalendarProps["dateInnerContent"],
    value: Dayjs,
    selectHandler: MonthCalendarProps["selectHandler"]
) {
    const rows = [];
    for (let i = 0; i < 6; i++) {
        const row = [];
        for (let j = 0; j < 7; j++) {
            const item = days[i * 7 + j];
            row[j] = (
                <div
                    className={
                        "calendar-month-body-cell " +
                        (item.currentMonth
                            ? "calendar-month-body-cell-current"
                            : "")
                    }
                    onClick={() => selectHandler?.(item.date)}>
                    {dateRender ? (
                        dateRender(item.date)
                    ) : (
                        <div className="calendar-month-body-cell-date">
                            <div
                                className={cs(
                                    "calendar-month-body-cell-date-value",
                                    value.format("YYYY-MM-DD") ===
                                        item.date.format("YYYY-MM-DD")
                                        ? "calendar-month-body-cell-date-selected"
                                        : ""
                                )}>
                                {item.date.date()}
                            </div>
                            <div className="calendar-month-cell-body-date-content">
                                {dateInnerContent?.(item.date)}
                            </div>
                        </div>
                    )}
                </div>
            );
        }
        rows.push(row);
    }
    return rows.map((row) => (
        <div className="calendar-month-body-row">{row}</div>
    ));
}

然后这个参数是在 Calendar 组件传进来的:

我们添加一个 state 来存储当前日期,selectHandler 里调用 onChange 的参数,并且修改当前日期。

function Calendar(props: CalendarProps) {
    const {
        value,
        style,
        className,
        dateRender,
        dateInnerContent,
        locale,
        onChange,
    } = props;

    const [curValue, setCurValue] = useState<Dayjs>(value);

    const classNames = cs("calendar", className);

    function selectHandler(date: Dayjs) {
        setCurValue(date);
        onChange?.(date);
    }

    return (
        <LocaleContext.Provider
            value={{
                locale: locale || navigator.language,
            }}>
            <div className={classNames} style={style}>
                <Header></Header>
                <MonthCalendar
                    {...props}
                    value={curValue}
                    selectHandler={selectHandler}
                />
            </div>
        </LocaleContext.Provider>
    );
}

试一下,改下 App.tsx:

import dayjs from "dayjs";
import Calendar from "./Calendar";

function App() {
    return (
        <div className="App">
            <Calendar
                value={dayjs("2023-11-08")}
                onChange={(date) => {
                    alert(date.format("YYYY-MM-DD"));
                }}></Calendar>
        </div>
    );
}

export default App;

然后实现下 Header 组件里的日期切换:

根据传入的 value 来展示日期,点击上下按钮的时候会调用传进来的回调函数:

import { Dayjs } from "dayjs";
interface HeaderProps {
    curMonth: Dayjs;
    prevMonthHandler: () => void;
    nextMonthHandler: () => void;
}
function Header(props: HeaderProps) {
    const { curMonth, prevMonthHandler, nextMonthHandler } = props;

    return (
        <div className="calendar-header">
            <div className="calendar-header-left">
                <div
                    className="calendar-header-icon"
                    onClick={prevMonthHandler}>
                    &lt;
                </div>
                <div className="calendar-header-value">
                    {curMonth.format("YYYY 年 MM 月")}
                </div>
                <div
                    className="calendar-header-icon"
                    onClick={nextMonthHandler}>
                    &gt;
                </div>
                <button className="calendar-header-btn">今天</button>
            </div>
        </div>
    );
}

export default Header;

然后在 Calendar 组件创建 curMonth 的 state,点击上下按钮的时候,修改月份:

function Calendar(props: CalendarProps) {
    const {
        value,
        style,
        className,
        dateRender,
        dateInnerContent,
        locale,
        onChange,
    } = props;

    const [curValue, setCurValue] = useState<Dayjs>(value);

    const [curMonth, setCurMonth] = useState<Dayjs>(value);

    const classNames = cs("calendar", className);

    function selectHandler(date: Dayjs) {
        setCurValue(date);
        onChange?.(date);
    }

    function prevMonthHandler() {
        setCurMonth(curMonth.subtract(1, "month"));
    }

    function nextMonthHandler() {
        setCurMonth(curMonth.add(1, "month"));
    }

    return (
        <LocaleContext.Provider
            value={{
                locale: locale || navigator.language,
            }}>
            <div className={classNames} style={style}>
                <Header
                    curMonth={curMonth}
                    prevMonthHandler={prevMonthHandler}
                    nextMonthHandler={nextMonthHandler}></Header>
                <MonthCalendar
                    {...props}
                    value={curValue}
                    selectHandler={selectHandler}
                />
            </div>
        </LocaleContext.Provider>
    );
}

测试下:

但现在月份是变了,但下面的日历没有跟着变。

因为我们之前是拿到 value 所在月份来计算的日历,现在要改成 curMonth 所在的月份。

这样,月份切换时,就会显示那个月的日历了:

然后我们加上今天按钮的处理:

import { Dayjs } from "dayjs";
interface HeaderProps {
    curMonth: Dayjs;
    prevMonthHandler: () => void;
    nextMonthHandler: () => void;
    todayHandler: () => void;
}
function Header(props: HeaderProps) {
    const { curMonth, prevMonthHandler, nextMonthHandler, todayHandler } =
        props;

    return (
        <div className="calendar-header">
            <div className="calendar-header-left">
                <div
                    className="calendar-header-icon"
                    onClick={prevMonthHandler}>
                    &lt;
                </div>
                <div className="calendar-header-value">
                    {curMonth.format("YYYY 年 MM 月")}
                </div>
                <div
                    className="calendar-header-icon"
                    onClick={nextMonthHandler}>
                    &gt;
                </div>
                <button className="calendar-header-btn" onClick={todayHandler}>
                    今天
                </button>
            </div>
        </div>
    );
}

export default Header;

在 Calendar 里传入 todayHandler:

function todayHandler() {
    const date = dayjs(Date.now());

    setCurValue(date);
    setCurMonth(date);
    onChange?.(date);
}

同时修改日期和当前月份,并且还要调用 onChange 回调。

测试下:

此外,我们希望点击上下月份的日期的时候,能够跳转到那个月的日历:

这个也简单,切换日期的时候顺便修改下 curMonth 就好了:

测试下:

最后,还要加上 Header 的国际化:

就是把写死的文案,改成丛资源包取值的方式就好了。

function Header(props: HeaderProps) {
    const { curMonth, prevMonthHandler, nextMonthHandler, todayHandler } =
        props;

    const localeContext = useContext(LocaleContext);
    const CalendarContext = allLocales[localeContext.locale];

    return (
        <div className="calendar-header">
            <div className="calendar-header-left">
                <div
                    className="calendar-header-icon"
                    onClick={prevMonthHandler}>
                    &lt;
                </div>
                <div className="calendar-header-value">
                    {curMonth.format(CalendarContext.formatMonth)}
                </div>
                <div
                    className="calendar-header-icon"
                    onClick={nextMonthHandler}>
                    &gt;
                </div>
                <button className="calendar-header-btn" onClick={todayHandler}>
                    {CalendarContext.today}
                </button>
            </div>
        </div>
    );
}

试试看:

没啥问题。

这样,我们的 Calendar 组件就完成了。

最后我们再来优化下代码:

重复逻辑可以抽离出个方法:

function changeDate(date: Dayjs) {
    setCurValue(date);
    setCurMonth(date);
    onChange?.(date);
}

渲染逻辑抽离出来的函数,放在组件外需要传很多参数,而这个函数只有这里用,可以移到组件内:

这样就不用传那些参数了:

此外,我们的 Calendar 的 value 其实是 defaultValue:

image.png

和迷你 Calendar 一样,我们也用 ahooks 的 useControllableValue 来做。

安装 ahooks:

npm install --save ahooks

把 useState 换成 ahooks 的 useControllableValue:

image.png

export interface CalendarProps {
    value?: Dayjs;
    defaultValue?: Dayjs;
    style?: CSSProperties;
    className?: string | string[];
    // 定制日期显示,会完全覆盖日期单元格
    dateRender?: (currentDate: Dayjs) => ReactNode;
    // 定制日期单元格,内容会被添加到单元格内,只在全屏日历模式下生效。
    dateInnerContent?: (currentDate: Dayjs) => ReactNode;
    // 国际化相关
    locale?: string;
    onChange?: (date: Dayjs) => void;
}
const [curValue, setCurValue] = useControllableValue<Dayjs>(props, {
    defaultValue: dayjs(),
});

const [curMonth, setCurMonth] = useState < Dayjs > curValue;

用到 value 的地方加一下 ?:

image.png

这样就同时支持受控非受控,也就是 value 和 defaultValue 了。

试一下 defaultValue 非受控模式:

import dayjs from "dayjs";
import Calendar from "./Calendar";

function App() {
    return (
        <div className="App">
            <Calendar defaultValue={dayjs("2023-11-08")}></Calendar>
        </div>
    );
}

export default App;

2024-08-31 19.00.51.gif

value 受控模式:

import dayjs from "dayjs";
import Calendar from "./Calendar";
import { useState } from "react";

function App() {
    const [value, setValue] = useState(dayjs("2023-11-08"));

    return (
        <div className="App">
            <Calendar
                value={value}
                onChange={(val) => {
                    setValue(val);
                }}></Calendar>
        </div>
    );
}

export default App;

2024-08-31 19.02.47.gif

案例代码上传了小册仓库。

总结

上节我们实现了布局,这节加上了参数并且实现了这些参数对应的逻辑。

className 和 style 用于修改外层容器的样式,内部用的 flex 布局,只要容器大小变了,内容会自动适应。

dateRender 和 dateInnerConent 是用于修改日期单元格的内容的,比如显示节日、日程安排等。

locale 是切换语言,国际化就是把写死的文案换成从资源包取值的方式,我们创建了 zh-CN 和 en-US 两个资源包,并且可以通过 locale 参数来切换。

通过 createContext 创建 context 对象来保存 locale 配置,然后通过 Provider 修改其中的值,这样子组件里就通过 useContext 把它取出来就知道当前语言了。

最后我们用 ahooks 的 useControllableValue 同时支持了受控和非受控模式。

日历组件是一个常用组件,而且是经常需要定制的那种,因为各种场景下对它有不同的要求,所以能够自己实现各种日历组件是一个必备技能。

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
8.组件实战:Calendar日历组件(上)
Next
10.快速掌握Storybook