我有一个简单的应用程序与报头.tsx组件,应该是可用于所有页面和家.tsx,其中大部分内容将是.

家.tsx承载一个intersectionObserver,它将带有use语境钩子(称为homeLinks)的数据发送到报头.tsxAPP.tsx,因此我可以使用当前相交部分的类更新导航.这里的问题是:只要观察者更新上下文,整个DOM就会重新呈现,因为家将首当其冲地占据应用程序的首位,这不可能是最佳解决方案.谁能给我解释一下如何以正确的方式做到这一点?

我可以用某种方式将裁判从首页发送到标题,然后在那里设置观察者吗?

CodeSandbox demo is available here

APP

import homeLinks from "./contexts/homeLinks";


function APP() {
  const [homePart, set家Part] = useState<string>(""); //家 part which is observed, setting function
  console.log("APP is rendered");

  return (
    <>
      <homeLinks.Provider value={{ homePart, set家Part }}>
        <报头 /> {/* Outside router, meant to be shared with other pages  */}
        <家 /> {/* Will be in react router */}
      </homeLinks.Provider>
    </>
  );
}
export default APP;

import homeLinks from "../contexts/homeLinks";

export const 家 = () => {
  const homeParts = useRef(new Array());
  const { set家Part } = use语境(homeLinks);

  useEffect(() => {
    const menuObserver = new IntersectionObserver(
      (entries) => {
        const entry = entries[0];
        if (entry.isIntersecting)
          set家Part(
            entry.target.getAttribute("id")
              ? entry.target.getAttribute("id")!
              : "ntn here",
          );
      },
      { rootMargin: "-40% 0px -50% 0px" },
    );

    homeParts.current.map((part) => {
      menuObserver.observe(part!);
    });
  }, []);

  console.log("家 is rendered");

  return (
    <>
      <div
        className="hero"
        id="home"
        ref={(element) => homeParts.current.push(element)}
      >
        <section>
          <div className="depth"></div>
          <div className="heroContent">
              <h1>This is 家</h1>
          </div>
        </section>
      </div>
      <div
        id="projects"
        style={{ height: "100vh", backgroundColor: "gray" }}
        ref={(element) => homeParts.current.push(element)}
      >
        This is Projects
      </div>

      <div
        id="contact"
        style={{ height: "100vh", backgroundColor: "cadetblue" }}
        ref={(element) => homeParts.current.push(element)}
      >
        This is contact
      </div>
    </>
  );
};

报头

import linkSection from "../contexts/homeLinks";

function 报头() {
  const lastHash = useRef("");

  const { homePart } = use语境(linkSection);
  const homeLinks = [
    { href: "home", title: "家" },
    { href: "projects", title: "Projects" },
    { href: "contact", title: "Contact" },
  ];

  return (
    <header>
      <div>
        <a href="/">Menu</a>
      </div>
      <nav>
        <ul>
          {homeLinks.map((homeLink) => (
            <li key={homeLink.href}>
              <a
                href={"/#" + homeLink.href}
                className={homePart == homeLink.href ? "active" : ""}
              >
                {homeLink.title}
              </a>
            </li>
          ))}
        </ul>
      </nav>
    </header>
  );
}

export default 报头;

语境

import { create语境 } from "react";

const homeLinks = create语境({
  homePart: "",
  set家Part: (part: string) => {},
});

export default homeLinks;

完整的代码也可以在https://codesandbox.io/p/devbox/9p6fyv?file=%2Fsrc%2Fcontexts%2FhomeLinks.tsx&embed=1中找到

推荐答案

每当IntersectionObserver检测到交集时,它就会在AppApp中设置状态,并重新渲染它的所有子对象:

function App() {
  // re-renders App and all children
  const [homePart, setHomePart] = useState<string>("");
  console.log("App is rendered");

  return (
    <>
      <homeLinks.Provider value={{ homePart, setHomePart }}>
        <Header /> {/* Outside router, meant to be shared with other pages  */}
        <Home /> {/* Will be in react router */}
      </homeLinks.Provider>
    </>
  );
}

相反,上下文应该是注册到交集事件的漏斗,而不是保持它自己的状态.如果组件需要设置状态(并触发重新呈现),它应该侦听事件,并设置自己的状态.

Sandbox

The homeLink context

该上下文导出两个挂钩:

  1. useRegisterListener-接受注册为监听程序的函数.
  2. useObserve-返回可用作函数引用以观察DOM元素的observe函数.

该上下文还输出HomeLinksProvider启动器IntersectionObserverlisteners集合.它为上下文使用者提供了添加/删除侦听器和观察DOM元素的所有相关函数.提供程序在卸载时处理所有清理.

它还会等待观察者准备好,然后再渲染包裹的子对象.

import React from "react";
import {
  createContext,
  useContext,
  useState,
  useRef,
  useEffect,
  useMemo,
} from "react";

export type Listener = (id: string) => void;

const HomeLinks = createContext({
  observe: (instance: HTMLElement | null) => {},
  addListener: (listener: Listener) => {},
  removeListener: (listener: Listener) => {},
});

// register listeners to intersection events
export const useRegisterListener = (listener: Listener) => {
  const { addListener, removeListener } = useContext(HomeLinks);

  useEffect(() => {
    addListener(listener);

    return () => {
      removeListener(listener);
    };
  }, [addListener, removeListener, listener]);
};

// returns a ref function to observe DOM elements
export const useObserve = () => {
  const { observe } = useContext(HomeLinks);

  return observe;
};

// The provider initiates the IntersectionObserver,
// and calls listeners when elements are visible.
// It also disconnects the observer and clears the listeners Set.
export const HomeLinksProvider = ({ children }) => {
  // delay rendering children until context is ready
  const [ready, setReady] = useState(false);
  const menuObserver = useRef<IntersectionObserver>();
  const listners = useRef<Set<Listener>>();

  useEffect(() => {
    listners.current = new Set(); // initiate the listeners Set
    // create a new observer
    menuObserver.current = new IntersectionObserver(
      (entries) => {
        const entry = entries[0];
        const id = entry.target.getAttribute("id") ?? "ntn here";

        if (entry.isIntersecting)
          listners.current?.forEach((listener) => listener(id));
      },
      { rootMargin: "-40% 0px -50% 0px" },
    );

    setReady(true); // allow rendering children when context is ready

    return () => {
      // cleannup
      menuObserver.current?.disconnect();
      listners.current?.clear();
    };
  }, []);

  const contextValue = useMemo(
    () => ({
      // obsere DOM elements
      observe: (instance: HTMLElement | null) => {
        if (instance) menuObserver.current?.observe(instance);
      },
      // register listeners to Listeners Set
      addListener: (listener: Listener) => {
        listners.current?.add(listener);
      },
      // remove listeners from Listeners Set
      removeListener: (listener: Listener) => {
        listners.current?.delete(listener);
      },
    }),
    [],
  );

  // render children when context is ready
  return (
    <HomeLinks.Provider value={contextValue}>
      {ready ? children : null}
    </HomeLinks.Provider>
  );
};

用途:

应用程序:

function App() {
  console.log("App is rendered");

  return (
    <>
      <HomeLinksProvider>
        <Header /> {/* Outside router, meant to be shared with other pages  */}
        <Home /> {/* Will be in react router */}
      </HomeLinksProvider>
    </>
  );
}

家:你在做什么?

export const Home = () => {
  const observe = useObserve();

  console.log("Home is rendered");

  return (
    <>
      <div
        className="hero"
        id="home"
        ref={observe}
      >
        <section style={{ height: "100vh", backgroundColor: "palevioletred" }}>
          <div className="depth"></div>
          <div className="heroContent">
            <main>
              <h1>This is Home</h1>
            </main>
          </div>
        </section>
      </div>
      <div
        id="projects"
        style={{ height: "100vh", backgroundColor: "gray" }}
        ref={observe}
      >
        This is Projects
      </div>

      <div
        id="contact"
        style={{ height: "100vh", backgroundColor: "cadetblue" }}
        ref={observe}
      >
        This is contact
      </div>
    </>
  );
};

标题:

const homeLinks = [
  { href: "home", title: "Home" },
  { href: "projects", title: "Projects" },
  { href: "contact", title: "Contact" },
];

function Header() {
  const [homePart, setHomePart] = useState('home');

  useRegisterListener(setHomePart);

  return (
    <header>
      <div>
        <a href="/">Menu</a>
      </div>
      <nav>
        <ul>
          {homeLinks.map((homeLink) => (
            <li key={homeLink.href}>
              <a
                href={"/#" + homeLink.href}
                className={homePart == homeLink.href ? "active" : ""}
              >
                {homeLink.title}
              </a>
            </li>
          ))}
        </ul>
      </nav>
    </header>
  );
}

Javascript相关问答推荐

fs. writeFile()vs fs.writeFile()vs fs.appendFile()

格式值未保存在redux持久切片中

调用removeEvents不起作用

Phaser 3 console. log()特定游戏角色的瓷砖属性

为什么ngModel不能在最后一个版本的Angular 17上工作?'

从页面到应用程序(NextJS):REST.STATUS不是一个函数

使用js构造一个html<;ath&>元素并不能使其正确呈现

如何使用TypeScrip设置唯一属性?

创建以键值对为有效负载的Redux Reducer时,基于键的类型检测

让chart.js饼图中的一个切片变厚?

无法重定向到Next.js中的动态URL

使用RxJS from Event和@ViewChild vs KeyUp事件和RxJS主题更改输入字段值

Reaction即使在重新呈现后也会在方法内部保留局部值

react 路由DOM有条件地呈现元素

更新文本区域行号

如何将对象推送到firestore数组?

在对象的嵌套数组中添加两个属性

使用VITE开发服务器处理错误

单击子按钮时将活动切换类添加到父分区

如何使用Angular JS双击按钮