React+TypeScript:如何通过IntersectionObserver跨组件触发DOM操作
问题描述
需要实现:当Home.jsx中被react-intersection-observer的useInView钩子监测的div元素进入视口时,动态修改全局可见的Navbar.jsx的背景色。尝试通过Props传递状态,但Navbar始终收到undefined,无法触发样式变更,试过移除useEffect、不使用useInView、传递对象等方案都没解决。
组件结构:App.tsx负责渲染Navbar.jsx和Home.jsx子组件。
App.jsx 代码
import './App.css'; import { BrowserRouter as Router, useRoutes } from "react-router-dom"; import Home from './components/Home'; import Navbar from './components/Navbar'; import { useState } from 'react'; function App() { const [navbarScroll, setNavbarScrool] = useState(Object) const handleLocationChange = (navbarScroll: boolean) => { setNavbarScrool(navbarScroll) return navbarScroll } const AppRoutes = () => { let routes = useRoutes([ { path: "/", element: <Home handleLocationChange={handleLocationChange}/> }, { path: "component2", element: <></> }, ]); return routes; }; return ( <Router> <Navbar navbarScroll={navbarScroll} /> <AppRoutes /> </Router> ); } export default App;
Home.jsx 代码
import { useInView } from 'react-intersection-observer'; import HomeCSS from "../styles/home.module.css" import mePhoto from "../assets/me.png" import { useEffect, useState } from 'react'; interface AppProps { handleLocationChange: (values: any) => boolean; } export default function Home(props: AppProps) { const { ref: containerChange, inView: containerChangeIsVisible, entry } = useInView(); useEffect(() => { props.handleLocationChange(containerChangeIsVisible) console.log("Home " + containerChangeIsVisible) }, []) return ( <> <div className={`${ HomeCSS.container} ${containerChangeIsVisible? HomeCSS.container_variation: ''}`}> <div className={HomeCSS.container__children}> <h1 className={`${ HomeCSS.container__h1} ${containerChangeIsVisible? HomeCSS.container__h1_variation: ''}`}>My<br/> Name</h1> <p>Computer Science student.</p> </div> <img src={mePhoto} className={HomeCSS.image_style} /> </div> <div ref={containerChange} style={{ height: 800, background: "orange" }} > <p style={{marginTop:20}}>HIII</p> </div> </> ) }
Navbar.jsx 代码
import NavbarCSS from "../styles/navbar.module.css" import acPhoto from "../assets/ac.png" import { Link } from "react-router-dom"; import { useEffect, useState } from "react"; interface NavbarScroolProp{ navbarScroll:boolean } export default function Navbar(props: NavbarScroolProp) { const [scrollState, setScrollState] = useState(false) const [pVisible, setpVisible] = useState('') useEffect(() => { setTimeout(() => { setpVisible("") }, 3000) setpVisible("100%") }, []) console.log(props.navbarScroll) return ( <> <nav className={`${props.navbarScroll?NavbarCSS.nav__variation:NavbarCSS.nav}`}> <div className={NavbarCSS.nav_row}> <div className={NavbarCSS.nav_row_container}> <img src={acPhoto} className={NavbarCSS.image_style} /> <p className={NavbarCSS.p_style} style={{ maxWidth: pVisible }} > My name </p> </div> <div className={NavbarCSS.nav_row_tagcontainer}> <Link className={NavbarCSS.nav_row_tag} to="/"> Home</Link> <Link className={NavbarCSS.nav_row_tag} to="/"> About</Link> <Link className={NavbarCSS.nav_row_tag} to="/"> Contact</Link> </div> </div> </nav> </> ); }
解决方案
核心问题排查
- 初始状态错误:
App中useState(Object)将状态初始化为Object构造函数,而非布尔值,导致传递给Navbar的初始值不符合预期。 useEffect依赖缺失:Home中的useEffect仅在组件挂载时执行一次,未监听containerChangeIsVisible的变化,滚动时无法同步状态。- 冗余逻辑:
Navbar中多余的scrollState状态完全可以直接使用传入的props值。
步骤1:修正App.jsx的状态逻辑
将初始状态改为布尔值,修正拼写错误(setNavbarScrool → setNavbarScroll):
import './App.css'; import { BrowserRouter as Router, useRoutes } from "react-router-dom"; import Home from './components/Home'; import Navbar from './components/Navbar'; import { useState } from 'react'; function App() { // 初始化为布尔值false,符合业务逻辑 const [navbarScroll, setNavbarScroll] = useState(false) const handleLocationChange = (isVisible: boolean) => { setNavbarScroll(isVisible) } const AppRoutes = () => { let routes = useRoutes([ { path: "/", element: <Home handleLocationChange={handleLocationChange}/> }, { path: "component2", element: <></> }, ]); return routes; }; return ( <Router> <Navbar navbarScroll={navbarScroll} /> <AppRoutes /> </Router> ); } export default App;
步骤2:更新Home.jsx的状态监听
给useEffect添加containerChangeIsVisible依赖,确保可见性变化时同步状态:
import { useInView } from 'react-intersection-observer'; import HomeCSS from "../styles/home.module.css" import mePhoto from "../assets/me.png" import { useEffect } from 'react'; interface AppProps { handleLocationChange: (values: boolean) => void; } export default function Home(props: AppProps) { const { ref: containerChange, inView: containerChangeIsVisible, } = useInView({ // 可选:设置元素进入视口的比例阈值,按需调整 threshold: 0.5, }); // 监听可见性变化,实时同步状态到App useEffect(() => { props.handleLocationChange(containerChangeIsVisible) console.log("Home " + containerChangeIsVisible) }, [containerChangeIsVisible, props.handleLocationChange]) return ( <> <div className={`${ HomeCSS.container} ${containerChangeIsVisible? HomeCSS.container_variation: ''}`}> <div className={HomeCSS.container__children}> <h1 className={`${ HomeCSS.container__h1} ${containerChangeIsVisible? HomeCSS.container__h1_variation: ''}`}>My<br/> Name</h1> <p>Computer Science student.</p> </div> <img src={mePhoto} className={HomeCSS.image_style} /> </div> <div ref={containerChange} style={{ height: 800, background: "orange" }} > <p style={{marginTop:20}}>HIII</p> </div> </> ) }
步骤3:简化Navbar.jsx的渲染逻辑
直接使用传入的props.navbarScroll值,移除冗余的scrollState状态:
import NavbarCSS from "../styles/navbar.module.css" import acPhoto from "../assets/ac.png" import { Link } from "react-router-dom"; import { useEffect, useState } from "react"; interface NavbarScroolProp{ navbarScroll:boolean } export default function Navbar(props: NavbarScroolProp) { const [pVisible, setpVisible] = useState('') useEffect(() => { setTimeout(() => { setpVisible("") }, 3000) setpVisible("100%") }, []) console.log(props.navbarScroll) return ( <> <nav className={`${props.navbarScroll ? NavbarCSS.nav__variation : NavbarCSS.nav}`}> <div className={NavbarCSS.nav_row}> <div className={NavbarCSS.nav_row_container}> <img src={acPhoto} className={NavbarCSS.image_style} /> <p className={NavbarCSS.p_style} style={{ maxWidth: pVisible }} > My name </p> </div> <div className={NavbarCSS.nav_row_tagcontainer}> <Link className={NavbarCSS.nav_row_tag} to="/"> Home</Link> <Link className={NavbarCSS.nav_row_tag} to="/"> About</Link> <Link className={NavbarCSS.nav_row_tag} to="/"> Contact</Link> </div> </div> </nav> </> ); }
可选优化:用React Context实现全局状态
如果未来有更多组件需要共享此状态,可使用Context避免多层Props传递:
- 创建
ScrollContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react'; interface ScrollContextType { navbarScroll: boolean; setNavbarScroll: (value: boolean) => void; } const ScrollContext = createContext<ScrollContextType | undefined>(undefined); export function ScrollProvider({ children }: { children: ReactNode }) { const [navbarScroll, setNavbarScroll] = useState(false); return ( <ScrollContext.Provider value={{ navbarScroll, setNavbarScroll }}> {children} </ScrollContext.Provider> ); } export function useScrollContext() { const context = useContext(ScrollContext); if (!context) { throw new Error('useScrollContext must be used within a ScrollProvider'); } return context; }
- 在
App.tsx中包裹ScrollProvider,移除Props传递逻辑 - 在
Home和Navbar中通过useScrollContext直接获取/修改状态
内容的提问来源于stack exchange,提问作者AlexCav
相关产品推荐
相关产品推荐

