You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
    </>
  )
}
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>
    </>
  );
}
解决方案

核心问题排查

  1. 初始状态错误:App中useState(Object)将状态初始化为Object构造函数,而非布尔值,导致传递给Navbar的初始值不符合预期。
  2. useEffect依赖缺失:Home中的useEffect仅在组件挂载时执行一次,未监听containerChangeIsVisible的变化,滚动时无法同步状态。
  3. 冗余逻辑: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传递:

  1. 创建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;
}
  1. 在App.tsx中包裹ScrollProvider,移除Props传递逻辑
  2. 在Home和Navbar中通过useScrollContext直接获取/修改状态

内容的提问来源于stack exchange,提问作者AlexCav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 19:45:36