React日历组件:状态管理更高效
今天golang学习网给大家带来了《React日历组件:状态驱动,告别DOM操作》,其中涉及到的知识点包括等等,无论你是小白还是老手,都适合看一看哦~有好的建议也欢迎大家在评论留言,若是看完有所收获,也希望大家能多多点赞支持呀!一起加油学习~
引言:React 中的 DOM 操作原则
React 的核心理念是声明式 UI。这意味着开发者通过描述 UI 的“状态”来构建界面,而不是直接操作 DOM 元素。React 会根据状态的变化自动、高效地更新 DOM。这种模式极大地简化了 UI 开发,并提高了应用的可维护性。
然而,当我们在 React 组件中直接使用诸如 document.querySelector、innerHTML 或 addEventListener 等原生 DOM API 时,就违背了 React 的声明式原则。这可能导致以下问题:
- 渲染不一致性: React 维护一个虚拟 DOM 来优化更新。直接操作真实 DOM 会绕过虚拟 DOM,导致 React 无法感知这些变化,进而引发视图与内部状态不一致的问题。
- 性能问题: React 有一套高效的协调(Reconciliation)算法来最小化 DOM 操作。直接操作 DOM 会破坏这种优化。
- 难以调试和维护: 逻辑分散在 React 的生命周期和原生 DOM 操作中,使得问题追踪变得复杂。
- 兼容性与可移植性差: 代码与特定 DOM 环境耦合,难以在不同平台(如 React Native)复用。
原始问题中的日历组件正是因为混合了命令式 DOM 操作与 React 组件模式,才导致了初始化渲染的困扰和后续难以维护的局面。
原问题分析与常见误区
在原始代码中,renderCalendar() 函数内部使用了 document.querySelector 来获取 DOM 元素,并使用 innerText 和 innerHTML 直接修改它们。
export const Calendar = () => { const currentDate = document.querySelector(".current-date") || {}; // 问题点1 let daysTag = document.querySelector(".days") || {}; // 问题点2 // ... const renderCalendar = () => { // ... currentDate.innerText = `${months[currMonth]} ${currYear}`; // 问题点3 daysTag.innerHTML = liTag; // 问题点4 }; renderCalendar(); // 首次调用位置 // ... prevNextIcon.forEach ( icon => { icon.addEventListener("click", () => { // 问题点5 // ... renderCalendar(); }); }); // ... return ( // ... JSX 结构 ); }
分析原代码中 renderCalendar() 的调用问题:
在 return 语句前调用 renderCalendar():
- 当 React 组件首次渲染时,其函数体从上到下执行。
- 在 return 语句执行并返回 JSX 结构之前,对应的真实 DOM 元素(如 .current-date 和 .days)尚未被创建并挂载到文档中。
- 因此,document.querySelector(".current-date") 和 document.querySelector(".days") 在此时会返回 null。尽管代码中使用了 || {} 来避免运行时错误,但 currentDate 和 daysTag 实际上是空对象,后续对它们的 innerText 或 innerHTML 赋值将无效。
- 这就是为什么首次加载时日历无法正确显示,而刷新后又可能暂时显示的原因(因为浏览器缓存或某种时序巧合)。
在 return 语句后调用 renderCalendar():
- JavaScript 函数的执行流程决定,一旦遇到 return 语句,函数就会立即终止并返回结果。
- 任何在 return 语句之后的代码都将是“不可达代码”(Unreachable code),永远不会被执行。终端中收到的“Unreachable code”警告正是对此的提示。
React 最佳实践:状态驱动与声明式渲染
解决上述问题的核心在于拥抱 React 的状态驱动和声明式渲染范式。我们将通过 useState 和 useEffect 钩子来重构日历组件。
useState:管理组件状态
- 所有影响组件渲染的数据(如当前的年份、月份、日历中的日期列表、当前日期文本等)都应该存储在 useState 中。当这些状态改变时,React 会自动重新渲染组件。
useEffect:处理副作用
- useEffect 钩子用于处理组件渲染后需要执行的“副作用”,例如数据获取、订阅事件、或基于状态变化进行复杂计算。
- 我们可以将计算日历日期列表的逻辑放入 useEffect 中。通过指定依赖项(如 currYear 和 currMonth),我们可以确保当年份或月份变化时,日历数据会重新计算。
JSX:声明式渲染 UI
- 不再使用 innerHTML 或 innerText 直接操作 DOM。
- 而是通过 JSX 语法,将状态数据直接绑定到 UI 元素上。例如,日期列表可以通过映射一个日期数据数组来动态生成
- 元素。
重构日历组件
我们将逐步重构 Calendar 组件,使其完全符合 React 的开发范式。
步骤一:定义状态
首先,我们需要定义组件所需的各种状态:当前的年份、月份、日历显示的日期文本,以及日历中所有日期(包括上月、本月、下月)的列表。
import React, { useState, useEffect, useCallback } from 'react'; import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; export const Calendar = () => { // 初始化当前日期 const today = new Date(); const [currYear, setCurrYear] = useState(today.getFullYear()); const [currMonth, setCurrMonth] = useState(today.getMonth()); // 0-11 const [currentDateText, setCurrentDateText] = useState(""); // 用于显示 "May 2023" const [daysInMonth, setDaysInMonth] = useState([]); // 用于存储日历中的所有日期数据 const months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; // ... (其他代码) };
步骤二:封装计算逻辑到 useEffect
将原 renderCalendar 的逻辑重写为一个计算函数,该函数不再直接操作 DOM,而是计算出日期数据并更新状态。然后,在 useEffect 中调用这个计算函数。
// 使用 useCallback 优化,避免每次渲染都重新创建函数 const generateCalendarDays = useCallback(() => { let firstDayofMonth = new Date(currYear, currMonth, 1).getDay(); // 获取当月第一天是星期几 (0-6) let lastDateofMonth = new Date(currYear, currMonth + 1, 0).getDate(); // 获取当月最后一天 let lastDayofLastMonth = new Date(currYear, currMonth, 0).getDate(); // 获取上月最后一天 let days = []; // 填充上月日期 for (let i = firstDayofMonth; i > 0; i--) { days.push({ date: lastDayofLastMonth - i + 1, type: 'inactive', key: `prev-${lastDayofLastMonth - i + 1}` // 添加唯一 key }); } // 填充当月日期 for (let i = 1; i <= lastDateofMonth; i++) { let isToday = i === today.getDate() && currMonth === today.getMonth() && currYear === today.getFullYear(); days.push({ date: i, type: isToday ? 'active' : '', key: `curr-${i}` }); } // 填充下月日期 (确保日历有 6 行,即 42 个格子) let totalDaysDisplayed = days.length; let remainingSlots = 42 - totalDaysDisplayed; // 假设日历固定显示 6 周 if (remainingSlots < 0) remainingSlots = 0; // 避免负数,虽然通常不会发生 for (let i = 1; i <= remainingSlots; i++) { days.push({ date: i, type: 'inactive', key: `next-${i}` }); } // 更新状态 setDaysInMonth(days); setCurrentDateText(`${months[currMonth]} ${currYear}`); }, [currYear, currMonth, today]); // 依赖项:当年份或月份变化时重新计算 // 使用 useEffect 在组件挂载或 currYear/currMonth 变化时执行日历生成 useEffect(() => { generateCalendarDays(); }, [generateCalendarDays]); // 依赖 generateCalendarDays 函数
步骤三:JSX 声明式渲染
现在,我们可以利用 currentDateText 和 daysInMonth 这两个状态来渲染 UI。
return ( <div className="auth flexContainer"> <h1>Calendar</h1> <div className="wrapper"> <header> <p className="current-date">{currentDateText}</p> {/* 直接绑定状态 */} <div className="icons"> <ArrowBackIosIcon id="prev" className="arrow" onClick={() => { setCurrMonth(prevMonth => { let newMonth = prevMonth - 1; if (newMonth < 0) { setCurrYear(prevYear => prevYear - 1); return 11; // 11代表12月 } return newMonth; }); }}/> <ArrowForwardIosIcon id="next" className="arrow" onClick={() => { setCurrMonth(prevMonth => { let newMonth = prevMonth + 1; if (newMonth > 11) { setCurrYear(prevYear => prevYear + 1); return 0; // 0代表1月 } return newMonth; }); }}/> </div> </header> <div className="calendar"> <ul className="weeks"> <li>Sunday</li> <li>Monday</li> <li>Tuesday</li> <li>Wednesday</li> <li>Thursday</li> <li>Friday</li> <li>Saturday</li> </ul> <ul className="days"> {daysInMonth.map(day => ( <li key={day.key} className={day.type}>{day.date}</li> // 映射数组生成列表 ))} </ul> </div> </div> </div> );
完整的重构代码示例
import React, { useState, useEffect, useCallback } from 'react'; import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; export const Calendar = () => { // 获取当前日期,用于标记“今天” const today = new Date(); // 使用 useState 管理当前显示的年份和月份 const [currYear, setCurrYear] = useState(today.getFullYear()); const [currMonth, setCurrMonth] = useState(today.getMonth()); // 月份从0开始 (0-11) // 用于显示日历
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于文章的相关知识,也可关注golang学习网公众号。

- 上一篇
- 电脑死机重启原因及检测方法

- 下一篇
- Golang端口复用配置SO_REUSEPORT详解
-
- 文章 · 前端 | 6分钟前 |
- clearTimeout与clearInterval区别详解
- 480浏览 收藏
-
- 文章 · 前端 | 16分钟前 |
- HTML颜色选择器提升可访问性方法大全
- 330浏览 收藏
-
- 文章 · 前端 | 20分钟前 |
- Vue.js健身打卡应用开发教程
- 486浏览 收藏
-
- 文章 · 前端 | 23分钟前 |
- 如何用HTML创建响应式网格布局
- 254浏览 收藏
-
- 文章 · 前端 | 32分钟前 |
- setTimeout与setInterval区别全解析
- 376浏览 收藏
-
- 文章 · 前端 | 35分钟前 |
- CSS卡片阴影与动画效果教程
- 276浏览 收藏
-
- 文章 · 前端 | 39分钟前 |
- JavaScript页面重定向无限循环解决方法
- 489浏览 收藏
-
- 文章 · 前端 | 56分钟前 | JavaScript trim() Polyfill 空白字符 String.prototype
- JS字符串去空格方法详解
- 479浏览 收藏
-
- 文章 · 前端 | 57分钟前 |
- HTML进度条如何提升可访问性
- 120浏览 收藏
-
- 文章 · 前端 | 57分钟前 |
- JavaScriptIntl多语言实现技巧
- 462浏览 收藏
-
- 文章 · 前端 | 59分钟前 |
- BOM如何获取设备用户信息?
- 219浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 畅图AI
- 探索畅图AI:领先的AI原生图表工具,告别绘图门槛。AI智能生成思维导图、流程图等多种图表,支持多模态解析、智能转换与高效团队协作。免费试用,提升效率!
- 27次使用
-
- TextIn智能文字识别平台
- TextIn智能文字识别平台,提供OCR、文档解析及NLP技术,实现文档采集、分类、信息抽取及智能审核全流程自动化。降低90%人工审核成本,提升企业效率。
- 33次使用
-
- 简篇AI排版
- SEO 简篇 AI 排版,一款强大的 AI 图文排版工具,3 秒生成专业文章。智能排版、AI 对话优化,支持工作汇报、家校通知等数百场景。会员畅享海量素材、专属客服,多格式导出,一键分享。
- 30次使用
-
- 小墨鹰AI快排
- SEO 小墨鹰 AI 快排,新媒体运营必备!30 秒自动完成公众号图文排版,更有 AI 写作助手、图片去水印等功能。海量素材模板,一键秒刷,提升运营效率!
- 29次使用
-
- Aifooler
- AI Fooler是一款免费在线AI音频处理工具,无需注册安装,即可快速实现人声分离、伴奏提取。适用于音乐编辑、视频制作、练唱素材等场景,提升音频创作效率。
- 35次使用
-
- 优化用户界面体验的秘密武器:CSS开发项目经验大揭秘
- 2023-11-03 501浏览
-
- 使用微信小程序实现图片轮播特效
- 2023-11-21 501浏览
-
- 解析sessionStorage的存储能力与限制
- 2024-01-11 501浏览
-
- 探索冒泡活动对于团队合作的推动力
- 2024-01-13 501浏览
-
- UI设计中为何选择绝对定位的智慧之道
- 2024-02-03 501浏览