React组件初始化与DOM操作技巧
偷偷努力,悄无声息地变强,然后惊艳所有人!哈哈,小伙伴们又来学习啦~今天我将给大家介绍《React组件初始化与DOM操作技巧》,这篇文章主要会讲到等等知识点,不知道大家对其都有多少了解,下面我们就一起来看一吧!当然,非常希望大家能多多评论,给出合理的建议,我们一起学习,一起进步!

1. React组件渲染的挑战与常见误区
在React函数组件中,我们经常需要执行一些与DOM交互或数据获取相关的逻辑。一个常见的误区是将这些逻辑直接放在组件函数体内部,或者在组件返回JSX之前调用。这可能导致以下问题:
- DOM元素未就绪: 当组件函数首次执行时,其JSX描述的DOM元素可能尚未被React渲染到实际DOM中。此时如果尝试使用document.querySelector等方法去获取这些元素,将得到null或空对象,导致后续操作失败。
- “不可达代码”警告: 如果将函数调用放在return语句之前,并且该函数本身没有副作用(例如只计算值),React可能会将其视为“不可达代码”,因为其结果未被用于渲染或状态更新。
- 不一致的渲染: 直接在函数体中调用可能导致在组件重新渲染时重复执行,或者在某些情况下未按预期执行,造成UI显示异常。
- 违反React声明式范式: React推崇声明式UI,即通过状态(State)的变化来驱动UI的更新,而不是直接操作DOM。频繁使用document.querySelector和innerHTML等命令式API会破坏React的抽象层,使组件难以维护和理解。
在提供的日历组件示例中,renderCalendar()函数在组件的return语句之前被调用,并且该函数内部使用了document.querySelector来获取DOM元素并修改innerHTML。这种做法正是上述问题的典型体现。当组件首次渲染时,current-date和days这些DOM元素可能还未挂载到页面上,导致querySelector失败,从而日历无法正确显示。
2. 理解React的副作用与生命周期:useEffect
React组件的渲染过程是声明式的:你告诉React UI应该是什么样子,React负责将其高效地渲染到DOM上。然而,有些操作(如数据获取、订阅事件、手动修改DOM等)被称为“副作用”(Side Effects),它们不直接参与UI的渲染,但对组件的运行至关重要。
在React函数组件中,处理副作用的正确方式是使用useEffect Hook。useEffect允许你在组件渲染到DOM之后执行副作用清理或设置逻辑。它的基本语法是:
useEffect(() => {
// 副作用代码
// 例如:数据获取、订阅事件、DOM操作等
return () => {
// 清理函数(可选)
// 在组件卸载或依赖项变化前执行,用于清理副作用
};
}, [dependencies]); // 依赖项数组当依赖项数组为空([])时,useEffect中的副作用函数只会在组件首次挂载后执行一次,类似于类组件的componentDidMount。当依赖项数组包含变量时,副作用函数会在这些变量变化时重新执行。
将renderCalendar这样的DOM操作函数放入useEffect中是关键,因为它确保了:
- renderCalendar只在组件的DOM元素已经挂载到页面上之后才执行。
- renderCalendar的执行时机受React生命周期管理,避免不必要的重复执行。
3. 使用useEffect和useState管理初始化渲染
为了解决日历组件的问题,我们可以结合useState和useEffect来确保renderCalendar在正确的时机执行,并且DOM元素已经可用。
步骤一:引入useState来跟踪渲染状态
我们可以创建一个状态变量来指示日历是否已经成功渲染。
import React, { useState, useEffect, useCallback } from 'react';
// ...其他导入
export const Calendar = () => {
// ...原有的状态和变量
const [renderedCalendar, setRenderedCalendar] = useState(false); // 新增状态
// ... renderCalendar 函数定义
};步骤二:将renderCalendar放入useEffect
将renderCalendar()的调用移入useEffect,并确保在renderCalendar执行完毕后更新renderedCalendar状态。
// ...组件内部
const renderCalendar = useCallback(() => { // 使用 useCallback 优化
// ... renderCalendar 内部逻辑(保持不变,但请注意后续的最佳实践建议)
// 获取DOM元素,此时应该已经挂载
const currentDateElement = document.querySelector(".current-date");
const daysTagElement = document.querySelector(".days");
if (currentDateElement && daysTagElement) {
currentDateElement.innerText = `${months[currMonth]} ${currYear}`;
daysTagElement.innerHTML = liTag;
setRenderedCalendar(true); // 渲染成功后更新状态
} else {
console.warn("Calendar DOM elements not found for rendering.");
}
}, [currYear, currMonth, months]); // 依赖项,当这些变化时 renderCalendar 会更新
useEffect(() => {
// 确保在组件挂载后执行 renderCalendar
renderCalendar();
}, [renderCalendar]); // 将 renderCalendar 作为依赖项关于useCallback:renderCalendar函数本身依赖于currYear、currMonth和months等外部变量。如果这些变量发生变化,renderCalendar函数会重新创建。当renderCalendar作为useEffect的依赖项时,每次函数重新创建都会导致useEffect重新执行。使用useCallback可以记忆renderCalendar函数,只有当其依赖项(currYear, currMonth, months)发生变化时才重新创建,从而避免useEffect不必要的重复执行,优化性能。
4. 确保DOM元素可用:条件渲染
虽然useEffect确保了renderCalendar在DOM挂载后执行,但如果document.querySelector仍然找不到元素,那说明JSX渲染的DOM结构可能在renderCalendar执行时还没完全可用,或者存在其他问题。一种更健壮的方法是使用renderedCalendar状态进行条件渲染,确保只有在日历逻辑成功执行后才显示日历内容。
// ...组件内部
return (
<div className="auth flexContainer">
<h1>Calendar</h1>
{renderedCalendar ? ( // 只有在日历成功渲染后才显示内容
<div className="wrapper">
<header>
<p className="current-date"></p> {/* 初始为空,由 renderCalendar 填充 */}
<div className="icons">
<ArrowBackIosIcon id="prev" className="arrow"/>
<ArrowForwardIosIcon id="next" className="arrow"/>
</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"></ul> {/* 初始为空,由 renderCalendar 填充 */}
</div>
</div>
) : (
<div>Loading Calendar...</div> // 可以显示加载状态
)}
</div>
);注意: 在上述示例中,current-date和days的innerText和innerHTML最初是空的,因为它们将由renderCalendar函数填充。
5. 更佳实践:拥抱React的声明式UI
尽管上述解决方案能让日历正常工作,但它仍然保留了在React组件中使用document.querySelector和直接修改innerHTML的“命令式”代码。这在React中通常被认为是反模式。React的核心思想是通过管理组件的状态来声明式地描述UI,让React负责高效地更新DOM。
推荐的React化改造方向:
避免document.querySelector:
- 在React中,你几乎不需要直接使用document.querySelector。如果你需要直接访问DOM节点(例如,为了集成第三方库或进行测量),请使用useRef Hook。
- 然而,对于像日历这样完全由数据驱动的UI,通常不需要直接DOM操作。
将逻辑React化:通过状态驱动UI
- 将currYear、currMonth、months、以及日历中每个日期的liTag内容等都作为组件的状态来管理。
- renderCalendar函数应该被拆解,其逻辑变为根据当前状态(currYear, currMonth)计算出要渲染的日期数据数组。
- 然后,在JSX中通过map方法遍历这个日期数据数组,动态生成
- 元素,并根据数据设置其className和innerText。
示例(概念性,非完整实现):
import React, { useState, useEffect, useMemo } from 'react';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
export const Calendar = () => {
const [date, setDate] = useState(new Date());
const currYear = date.getFullYear();
const currMonth = date.getMonth();
const months = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
// 使用 useMemo 计算日历数据,当 currYear 或 currMonth 变化时重新计算
const calendarDays = useMemo(() => {
let days = [];
let firstDayofMonth = new Date(currYear, currMonth, 1).getDay();
let lastDateofMonth = new Date(currYear, currMonth + 1, 0).getDate();
let lastDateofLastMonth = new Date(currYear, currMonth, 0).getDate();
// 填充上个月的日期
for (let i = firstDayofMonth; i > 0; i--) {
days.push({ day: lastDateofLastMonth - i + 1, type: 'inactive' });
}
// 填充当前月的日期
for (let i = 1; i <= lastDateofMonth; i++) {
let isToday = i === new Date().getDate() && currMonth === new Date().getMonth() && currYear === new Date().getFullYear();
days.push({ day: i, type: isToday ? 'active' : '' });
}
// 填充下个月的日期
let lastDayofMonth = new Date(currYear, currMonth, lastDateofMonth).getDay();
for (let i = lastDayofMonth; i < 6; i++) { // 确保是6,因为一周是7天,索引0-6
days.push({ day: i - lastDayofMonth + 1, type: 'inactive' });
}
return days;
}, [currYear, currMonth]);
const handlePrevNextClick = (direction) => {
setDate(prevDate => {
let newMonth = prevDate.getMonth();
let newYear = prevDate.getFullYear();
if (direction === "prev") {
newMonth--;
} else {
newMonth++;
}
// 处理月份越界
if (newMonth < 0) {
newMonth = 11;
newYear--;
} else if (newMonth > 11) {
newMonth = 0;
newYear++;
}
return new Date(newYear, newMonth, 1); // 设置为新月份的第一天
});
};
return (
<div className="auth flexContainer">
<h1>Calendar</h1>
<div className="wrapper">
<header>
<p className="current-date">{`${months[currMonth]} ${currYear}`}</p>
<div className="icons">
<ArrowBackIosIcon id="prev" className="arrow" onClick={() => handlePrevNextClick("prev")}/>
<ArrowForwardIosIcon id="next" className="arrow" onClick={() => handlePrevNextClick("next")}/>
</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">
{calendarDays.map((dayData, index) => (
<li key={index} className={dayData.type}>{dayData.day}</li>
))}
</ul>
</div>
</div>
</div>
);
};上述代码展示了如何将命令式的renderCalendar逻辑转换为声明式的React组件。通过useState管理日期,useMemo计算日历数据,以及在JSX中直接渲染
总结
在React中,正确处理组件的初始化渲染和副作用至关重要。useEffect是执行副作用的官方钩子,它确保了代码在DOM元素可用后执行。结合useState可以管理组件的状态,包括渲染进度或数据。对于涉及DOM操作的函数,推荐使用useCallback进行记忆化,以优化性能。
然而,更深层次的最佳实践是避免直接的DOM操作。React鼓励我们通过管理组件的状态来声明式地描述UI。将复杂的渲染逻辑分解为由状态驱动的数据计算,然后在JSX中利用map等方法动态渲染,是构建健壮、可维护React组件的关键。通过采纳这些原则,你的React应用将更加高效、可预测。
好了,本文到此结束,带大家了解了《React组件初始化与DOM操作技巧》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多文章知识!
GovsCython:性能与应用对比解析
- 上一篇
- GovsCython:性能与应用对比解析
- 下一篇
- CSS中i标签是什么作用
-
- 文章 · 前端 | 8分钟前 |
- VB运行HTML的步骤及方法详解
- 337浏览 收藏
-
- 文章 · 前端 | 8分钟前 | flex flex-grow CSSFlexbox flex-basis flex-shrink
- CSSFlex子元素属性全解析
- 492浏览 收藏
-
- 文章 · 前端 | 9分钟前 | JavaScript 算法 链表 图 树
- JavaScript链表树图算法实现详解
- 357浏览 收藏
-
- 文章 · 前端 | 12分钟前 |
- 行高过高的排版问题怎么解决
- 339浏览 收藏
-
- 文章 · 前端 | 17分钟前 |
- 浏览器API通知功能实现方法
- 235浏览 收藏
-
- 文章 · 前端 | 23分钟前 |
- JS前端优化20个实用技巧分享
- 305浏览 收藏
-
- 文章 · 前端 | 32分钟前 |
- z-index作用及使用场景解析
- 420浏览 收藏
-
- 文章 · 前端 | 34分钟前 | 性能优化 无限滚动 scroll事件 IntersectionObserverAPI 哨兵元素
- HTML5无限滚动优化监听技巧
- 383浏览 收藏
-
- 文章 · 前端 | 38分钟前 |
- JavaScript实现i18n与l10n教程
- 324浏览 收藏
-
- 文章 · 前端 | 46分钟前 | 水平居中 FLEXBOX 导航栏 display:flex justify-content
- CSS导航栏居中无效?Flexbox组合解决方法
- 192浏览 收藏
-
- 文章 · 前端 | 47分钟前 |
- WebGL像素绘制技巧:顶点属性与调用解析
- 287浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3194次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3407次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3437次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4545次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3815次使用
-
- JavaScript函数定义及示例详解
- 2025-05-11 502浏览
-
- 优化用户界面体验的秘密武器:CSS开发项目经验大揭秘
- 2023-11-03 501浏览
-
- 使用微信小程序实现图片轮播特效
- 2023-11-21 501浏览
-
- 解析sessionStorage的存储能力与限制
- 2024-01-11 501浏览
-
- 探索冒泡活动对于团队合作的推动力
- 2024-01-13 501浏览

