React开发倒计时组件提前调用未渲染DOM元素报错如何解决
问题原因
你在函数组件的同步执行阶段直接调用countdown(),此时return语句还未执行,对应的DOM元素还未渲染到页面中,document.querySelector无法匹配到对应元素会返回null,对null赋值innerText自然会抛出类型错误。就算你把countdown调用移到return语句之后也不会生效,因为return是函数执行的终止语句,后续代码不会运行。另外注意React自定义组件名必须采用大驼峰命名,否则会被识别为原生HTML标签导致渲染异常。
解决方案
方案1:保留原生DOM操作逻辑,使用useEffect确保DOM挂载后执行
借助React的useEffect钩子,其回调会在组件挂载完成后执行,此时DOM已经渲染完成,可以正常查询到对应元素:
import { useEffect } from 'react'; function Maintenance() { const countdown = () => { const countDate = new Date("August 10, 2021 00:00:00").getTime(); const now = new Date().getTime(); const gap = countDate - now; // 时间单位定义 const second = 1000; const minute = second * 60; const hour = minute * 60; const day = hour * 24; // 计算剩余时间,小于0时默认显示0 const textDay = Math.max(0, Math.floor(gap / day)); const textHour = Math.max(0, Math.floor((gap % day) / hour )); const textMinute = Math.max(0, Math.floor((gap % hour) / minute)); const textSecond = Math.max(0, Math.floor((gap % minute) / second )); document.querySelector('.day').innerText = textDay; document.querySelector('.hour').innerText = textHour; document.querySelector('.minute').innerText = textMinute; document.querySelector(".second").innerText = textSecond; }; // 组件挂载完成后执行,且每秒更新一次倒计时 useEffect(() => { countdown(); const timer = setInterval(countdown, 1000); // 组件卸载时清理定时器,避免内存泄漏 return () => clearInterval(timer); }, []); return ( <section className="coming-soon"> <div> <h1>A nossa equipa está a trabalhar para voltar a por o site no ar</h1> <div className="countdown"> <div className="container-day"> <h3 className="day">Time</h3> <h3>Dias</h3> </div> <div className="container-hour"> <h3 className="hour">Time</h3> <h3>Horas</h3> </div> <div className="container-minute"> <h3 className="minute">Time</h3> <h3>Minutos</h3> </div> <div className="container-seconds"> <h3 className="second">Time</h3> <h3>Segundos</h3> </div> </div> </div> <img className="waiting" src="https://res.cloudinary.com/dnho57ne8/image/upload/v1628004034/3685835_sp5t6h.svg" alt="等待插图"/> </section> ) } export default Maintenance;
方案2(推荐):使用React状态驱动,避免原生DOM操作
这是更符合React开发规范的写法,不需要手动操作DOM,直接把剩余时间存在state里,渲染到JSX中即可:
import { useState, useEffect } from 'react'; function Maintenance() { // 定义状态存储剩余时间 const [timeLeft, setTimeLeft] = useState({ day: 0, hour: 0, minute: 0, second: 0 }); const calculateTimeLeft = () => { const countDate = new Date("August 10, 2021 00:00:00").getTime(); const now = new Date().getTime(); const gap = countDate - now; const second = 1000; const minute = second * 60; const hour = minute * 60; const day = hour * 24; setTimeLeft({ day: Math.max(0, Math.floor(gap / day)), hour: Math.max(0, Math.floor((gap % day) / hour )), minute: Math.max(0, Math.floor((gap % hour) / minute)), second: Math.max(0, Math.floor((gap % minute) / second )) }); }; useEffect(() => { calculateTimeLeft(); const timer = setInterval(calculateTimeLeft, 1000); return () => clearInterval(timer); }, []); return ( <section className="coming-soon"> <div> <h1>A nossa equipa está a trabalhar para voltar a por o site no ar</h1> <div className="countdown"> <div className="container-day"> <h3 className="day">{timeLeft.day}</h3> <h3>Dias</h3> </div> <div className="container-hour"> <h3 className="hour">{timeLeft.hour}</h3> <h3>Horas</h3> </div> <div className="container-minute"> <h3 className="minute">{timeLeft.minute}</h3> <h3>Minutos</h3> </div> <div className="container-seconds"> <h3 className="second">{timeLeft.second}</h3> <h3>Segundos</h3> </div> </div> </div> <img className="waiting" src="https://res.cloudinary.com/dnho57ne8/image/upload/v1628004034/3685835_sp5t6h.svg" alt="等待插图"/> </section> ) } export default Maintenance;
注:示例中使用的目标时间为2021年8月10日,当前时间已超过该节点,倒计时会默认显示为0,可根据需求自行修改目标时间。
内容的提问来源于stack exchange,提问作者Tomas Mota
相关产品推荐
相关产品推荐

