React中点击导航栏触发scrollIntoView读取null属性错误
解决Uncaught TypeError: Cannot read properties of null (reading 'scrollIntoView')错误
问题根源
你在Navbar.jsx的组件函数顶部直接调用document.getElementById获取home、about、skills元素,但此时Navbar组件先于Homepage、About、Skills完成渲染,这些元素的DOM还没生成,所以拿到的结果都是null。点击导航项时调用scrollIntoView自然会抛出错误。而VSCode热重载时,页面DOM已经渲染完成,这时候能正常获取元素,所以功能恢复正常,刷新后又回到初始状态,报错重现。
解决方案
方案1:点击时再获取DOM元素(最简单直接)
删掉Navbar.jsx顶部提前获取DOM的代码,把获取元素的逻辑放到点击事件里,确保点击时目标元素已经存在:
import { useState } from 'react'; import { Link } from 'react-router-dom'; import '../App.css'; import myAvatar from '../images/avataaars.png'; function Navbar() { const [navbar, setNavbar] = useState(true); const handleNavbarToggle = (e) =>{ setNavbar(!navbar); } return( <> <div className='navbar-container'> <Link to='/'><img src={myAvatar} className='nav-logo'/></Link> <ul> <li onClick={() => { const home = document.getElementById('home'); home?.scrollIntoView({behavior: 'smooth'}); }}>Home</li> <li onClick={() => { const aboutPage = document.getElementById('about'); aboutPage?.scrollIntoView({behavior: 'smooth'}); }}>About</li> <li onClick={() => { const skills = document.getElementById('skills'); skills?.scrollIntoView({behavior: 'smooth'}); }}>Skills</li> <li>Projects</li> <li id='navbar-login-btn'>Login</li> </ul> <div className={`navbar-toggle ${navbar ? '' : 'open'}`} onClick={handleNavbarToggle}> <span></span> <span></span> <span></span> </div> </div> <div className={`navbar-header ${navbar ? 'navbar-header-hide' : ''}`}> <ul> <Link className='navbar-header-item' to='/'>Home</Link> <Link className='navbar-header-item' to='/about'>About</Link> <Link className='navbar-header-item' to='/skills'>Skills</Link> <Link className='navbar-header-item' to='/projects'>Projects</Link> <Link className='navbar-header-item' to='/login'>Login</Link> </ul> </div> </> ) } export default Navbar;
这里用了可选链操作符?.,就算元素意外不存在,也不会抛出错误,提升了代码健壮性。
方案2:使用React的useRef+useEffect(更符合React范式)
通过useRef存储DOM引用,在useEffect中等待组件挂载完成(DOM渲染完毕)后再获取元素:
import { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import '../App.css'; import myAvatar from '../images/avataaars.png'; function Navbar() { const [navbar, setNavbar] = useState(true); const homeRef = useRef(null); const aboutRef = useRef(null); const skillsRef = useRef(null); useEffect(() => { // 组件挂载后,DOM已渲染完成,此时获取目标元素 homeRef.current = document.getElementById('home'); aboutRef.current = document.getElementById('about'); skillsRef.current = document.getElementById('skills'); }, []); // 空依赖数组确保只在组件挂载时执行一次 const handleNavbarToggle = (e) =>{ setNavbar(!navbar); } return( <> <div className='navbar-container'> <Link to='/'><img src={myAvatar} className='nav-logo'/></Link> <ul> <li onClick={() => homeRef.current?.scrollIntoView({behavior: 'smooth'})}>Home</li> <li onClick={() => aboutRef.current?.scrollIntoView({behavior: 'smooth'})}>About</li> <li onClick={() => skillsRef.current?.scrollIntoView({behavior: 'smooth'})}>Skills</li> <li>Projects</li> <li id='navbar-login-btn'>Login</li> </ul> <div className={`navbar-toggle ${navbar ? '' : 'open'}`} onClick={handleNavbarToggle}> <span></span> <span></span> <span></span> </div> </div> <div className={`navbar-header ${navbar ? 'navbar-header-hide' : ''}`}> <ul> <Link className='navbar-header-item' to='/'>Home</Link> <Link className='navbar-header-item' to='/about'>About</Link> <Link className='navbar-header-item' to='/skills'>Skills</Link> <Link className='navbar-header-item' to='/projects'>Projects</Link> <Link className='navbar-header-item' to='/login'>Login</Link> </ul> </div> </> ) } export default Navbar;
额外提示
你的App.js中做了响应式布局:桌面端是单页面滚动(同时渲染三个组件),移动端是路由跳转。如果需要让桌面端的滚动逻辑只在对应屏幕尺寸下生效,可以结合window.innerWidth或者媒体查询来做条件判断,避免移动端触发无效的滚动逻辑。
内容的提问来源于stack exchange,提问作者Blazing
相关产品推荐
相关产品推荐

