React组件初始化渲染与DOM操作的最佳实践

React组件初始化渲染与DOM操作的最佳实践

本文深入探讨了React函数组件中初始化渲染、副作用管理及DOM操作的正确姿势。针对在React中直接使用document.querySelector进行DOM操作导致的问题,文章详细介绍了如何利用useEffect、useState和useCallback等React Hooks来管理组件生命周期和状态,确保函数在DOM元素可用后正确执行,并引导读者避免非React范式的DOM操作,转向更声明式的UI构建方法。

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 (    

Calendar

{renderedCalendar ? ( // 只有在日历成功渲染后才显示内容

{/* 初始为空,由 renderCalendar 填充 */}
  • Sunday
  • Monday
  • Tuesday
  • Wednesday
  • Thursday
  • Friday
  • Saturday
    {/* 初始为空,由 renderCalendar 填充 */}
    ) : (
    Loading Calendar...
    // 可以显示加载状态 )}
    );

    注意: 在上述示例中,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  {    setDate(prevDate => {      let newMonth = prevDate.getMonth();      let newYear = prevDate.getFullYear();      if (direction === "prev") {        newMonth--;      } else {        newMonth++;      }      // 处理月份越界      if (newMonth  11) {        newMonth = 0;        newYear++;      }      return new Date(newYear, newMonth, 1); // 设置为新月份的第一天    });  };  return (    

    Calendar

    {`${months[currMonth]} ${currYear}`}

    handlePrevNextClick("prev")}/> handlePrevNextClick("next")}/>
    • Sunday
    • Monday
    • Tuesday
    • Wednesday
    • Thursday
    • Friday
    • Saturday
      {calendarDays.map((dayData, index) => (
    • {dayData.day}
    • ))}
    );};

    上述代码展示了如何将命令式的renderCalendar逻辑转换为声明式的React组件。通过useState管理日期,useMemo计算日历数据,以及在JSX中直接渲染

    元素,我们完全避免了document.querySelector和innerHTML,使得组件更符合React的范式,更易于理解和维护。

    总结

    在React中,正确处理组件的初始化渲染和副作用至关重要。useEffect是执行副作用的官方钩子,它确保了代码在DOM元素可用后执行。结合useState可以管理组件的状态,包括渲染进度或数据。对于涉及DOM操作的函数,推荐使用useCallback进行记忆化,以优化性能。

    然而,更深层次的最佳实践是避免直接的DOM操作。React鼓励我们通过管理组件的状态来声明式地描述UI。将复杂的渲染逻辑分解为由状态驱动的数据计算,然后在JSX中利用map等方法动态渲染,是构建健壮、可维护React组件的关键。通过采纳这些原则,你的React应用将更加高效、可预测。

    以上就是React组件初始化渲染与DOM操作的最佳实践的详细内容,更多请关注创想鸟其它相关文章!

    版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
    发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1509623.html

    (0)
    打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
    上一篇 2025年12月20日 05:32:16
    下一篇 2025年12月20日 05:32:26

    相关推荐

    • React 函数组件日历渲染:告别 DOM 操作,拥抱状态驱动

      本教程深入探讨了 React 函数组件中日历渲染的常见问题,特别是避免直接 DOM 操作(如 document.querySelector 和 innerHTML)。我们将详细阐述如何利用 React 的核心机制——状态管理 (useState) 和副作用钩子 (useEffect)——来构建一个完…

      2025年12月20日
      000
    • JavaScript如何用数组的copyWithin复制元素

      copywithin()方法用于在不改变数组长度的前提下复制数组内部元素到指定位置,其核心是原地修改数组。1. 它接受三个参数:target(目标起始位置)、start(复制起始位置,默认0)、end(复制结束位置,默认array.length)。2. 参数支持负数索引,表示从末尾倒数。3. 若源与…

      2025年12月20日 好文分享
      000
    • React 函数组件中DOM操作与副作用管理:构建可靠日历组件的实践

      本文旨在解决React函数组件中不当的DOM操作和副作用管理导致的组件初始化问题。通过深入探讨useEffect、useState和useCallback等React Hooks,我们将展示如何正确地在组件挂载和更新时执行逻辑,避免直接操作DOM的常见陷阱,并构建一个稳定、可预测的日历组件。文章将提…

      2025年12月20日
      000
    • Shopify:在集合列表中访问和筛选产品

      本文将深入探讨在 Shopify Liquid 模板中访问集合产品并进行筛选的技巧。正如摘要所述,我们将重点解决 Shopify 集合分页限制带来的问题,并介绍如何利用 where 过滤器,基于产品属性(如供应商)进行高效筛选。掌握这些方法,你将能够更精确地控制集合中产品的展示,从而显著提升店铺的用…

      2025年12月20日
      000
    • JavaScript如何用Promise.all处理多个异步

      promise.all用于处理多个异步操作,接收一个promise数组并在所有promise都resolve后返回结果数组;若任一promise reject,则立即返回该错误。1. promise.all适用于需所有异步操作均成功完成的场景,如并行请求多个api、加载多个资源、执行多个数据库查询等…

      2025年12月20日 好文分享
      000
    • BOM中如何操作浏览器的下载功能?

      前端无法直接控制浏览器下载细节,但可通过html的标签触发下载。1. 使用标签并设置download属性,指定文件名和href链接,可下载服务器文件;2. 利用blob对象封装数据,结合url.createobjecturl生成临时链接,实现客户端生成文件并下载;3. 避免使用window.open…

      2025年12月20日 好文分享
      000
    • 如何在SQL记录中存储重复的JSON数据行

      本文介绍了在关系型数据库(如PostgreSQL)中存储重复数据行(例如JSON格式)的最佳实践。针对需要将多个关联数据(如演员及其角色)存储在单个记录中的场景,提出了使用多对多关系表的设计方案,并阐述了其优势,对比了JSON存储方式,强调了关系型数据库在查询效率方面的优势,并提供示例代码展示了表结…

      2025年12月20日
      000
    • JavaScript中微任务与宏任务区别

      javascript中微任务优先于宏任务执行。事件循环先执行宏任务,完成后清空微任务队列,再进入下一宏任务。常见宏任务包括整体脚本、settimeout回调、i/o操作、ui渲染等;常见微任务包括promise回调、mutationobserver、queuemicrotask。理解两者执行顺序可避…

      2025年12月20日 好文分享
      000
    • 如何在SQL中存储重复行数据(JSON)

      本文旨在解决如何在PostgreSQL数据库中使用Prisma进行开发时,有效地存储包含重复行数据的场景。通常,这种场景出现在需要将多个相关联的数据项(例如演员及其角色)存储在一个记录中。虽然可以使用JSONB数据类型将数据存储为JSON数组,但这不是最佳实践,尤其是在需要对数据进行复杂查询时。本文…

      2025年12月20日
      000
    • 如何在SQL中存储重复数据行(JSON方式与关系型方式对比)

      本文旨在探讨如何在PostgreSQL数据库中有效地存储具有重复数据行的信息,特别是当涉及到多对多关系时。文章将对比JSON存储方式和关系型数据库的存储方式,分析各自的优缺点,并提供关系型数据库的表结构设计示例,帮助读者选择最适合自身需求的存储方案。 在处理具有重复数据行的信息时,例如演员列表及其在…

      2025年12月20日
      000
    • 如何处理异步函数的依赖关系

      处理异步函数依赖关系的核心在于确保操作顺序性与协调性,1.通过promise实现基础链式调用,明确任务顺序执行;2.使用async/await提升代码可读性与维护性,避免回调地狱;3.promise.all()用于并行执行多个独立任务并等待全部完成;4.promise.race()用于获取最先完成的…

      2025年12月20日 好文分享
      000
    • BOM中如何操作浏览器的画中画功能?

      操作浏览器画中画功能的核心在于使用htmlvideoelement的requestpictureinpicture()方法进入pip模式,以及document.exitpictureinpicture()退出;1. 进入pip需调用videoelement.requestpictureinpictu…

      2025年12月20日 好文分享
      000
    • JavaScript DOM操作:获取并插入指定元素的内部HTML内容

      本文详细介绍了如何使用JavaScript的DOM操作来获取特定HTML元素的内部HTML内容,并将其动态插入到另一个指定元素中。文章通过分析常见错误,阐明了document.getElementById()方法的正确用法和innerHTML属性的应用,并提供了清晰的步骤和完整的代码示例,旨在帮助开…

      2025年12月20日
      000
    • Shopify教程:在集合列表中筛选和展示特定产品

      本文旨在解决Shopify Liquid模板中,从集合列表中筛选并展示特定产品的问题。通过分析常见问题和提供示例代码,帮助开发者有效地根据产品标题或其他属性筛选产品,并解决因分页限制导致的产品显示不完整的问题。本文将重点介绍where过滤器,并提供实际应用示例,确保开发者能够精准地控制产品展示。 在…

      2025年12月20日
      000
    • 如何处理异步操作中的缓存问题

      处理异步操作中的缓存问题需在保证数据一致性的前提下提升响应速度并降低服务器压力,关键在于合理选择缓存策略与技术。1. 更新时机方面,可采用定时刷新或“cache-aside”模式确保数据同步;2. 失效策略上,ttl、lru、lfu等机制适用于不同访问模式;3. 并发一致性可通过互斥锁避免缓存击穿;…

      2025年12月20日 好文分享
      000
    • Shopify教程:高效筛选并展示特定Collection中的产品

      本文将围绕如何在Shopify Collection中筛选和展示特定产品展开。默认情况下,Shopify的Collection分页限制可能导致部分产品无法显示。本文将介绍如何使用Liquid模板语言的where过滤器,根据产品属性精确筛选产品,克服分页限制,确保所有符合条件的产品都能正确展示。 理解…

      2025年12月20日
      000
    • JavaScript的Object.freeze方法是什么?怎么用?

      object.freeze 是 javascript 中用于冻结对象的方法,它阻止添加、删除或修改对象的顶层属性,但对嵌套对象无效。1. 它接收一个对象并返回被冻结的对象;2. 冻结后,属性不可变,严格模式下修改会抛出错误;3. 实现的是浅冻结,嵌套对象仍可被修改。应用场景包括防止配置对象被篡改、提…

      2025年12月20日 好文分享
      000
    • JavaScript的removeEventListener方法是什么?怎么用?

      removeeventlistener方法用于卸载之前通过addeventlistener绑定的事件监听器,避免内存泄漏和重复触发问题。使用时需注意三点:1.传入与添加时完全相同的事件类型、处理函数引用及第三个参数;2.避免使用匿名函数,否则无法移除;3.确保捕获/冒泡阶段参数一致。常见问题包括th…

      2025年12月20日 好文分享
      000
    • Shopify教程:在集合列表中高效访问和筛选产品

      本文旨在解决Shopify Liquid模板中访问和筛选集合产品时遇到的问题,特别是当需要根据产品标题或其他属性进行特定产品展示时。我们将探讨如何克服默认分页限制,并使用where过滤器等技术更有效地筛选产品,从而确保在您的店铺中准确展示所需的产品。 在Shopify Liquid模板中,开发者经常…

      2025年12月20日
      000
    • Shopify教程:高效筛选和展示特定Collection中的商品

      本文档旨在解决Shopify Liquid模板中,从特定Collection中筛选并展示包含特定关键词的商品时遇到的问题。通过分析分页限制和where过滤器的使用,提供更精准的商品筛选方案,确保在店铺前端准确展示目标商品。 在使用Shopify Liquid模板开发店铺时,经常需要从Collecti…

      2025年12月20日
      000

    发表回复

    登录后才能评论
    关注微信