ReactJS: 当URL改变时,useEffect不会运行。

10

我有一个ReactJS网站,但我还是一个初学者。

我有两个组件:

  • 第一个是根路径/的页面,在用户访问该路由时必须从Web服务器渲染,我使用了useEffect实现这一点。
  • 第二个是/mostraMaterie/:id,也必须在另一个请求到Web服务器后进行渲染。

第一个组件工作得很好,而第二个组件不会触发useEffect函数。

当用户在/组件上单击按钮时,会被重定向到具有相应ID的/mostraMateria/:id

问题出现在/mostraMateria组件中,我有另一个useEffect函数,但它没有被触发。

如评论所建议的,以下是JSFiddle中的mostraMateria文件:mostraMaterie.js

App.js处理用户连接。

import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import MostraMaterie from "./Contenuti/MostraMaterie";
import "./App.css";

import Main from "./Contenuti/Main";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/mostraMateria/:id" component={MostraMaterie} />{" "}
        <Route exact path="/" component={Main} />{" "}
      </Switch>{" "}
    </Router>
  );
}

export default App;

Main.js 负责处理页面渲染,如果位置是/,则运行完美。

import React, { useEffect, useState } from "react";
import {  Link } from "react-router-dom";
import Indirizzi from "./Indirizzi";
import Bottone from "../Util/Bottone";
import "../App.css";
import FullNavBar from "../NavBar/FullNavBar";

function Main() {
  const [indirizzi, cambiaIndirizzi] = useState([]);

  const fetchIndirizzi = () => {
    async function prendiIndirizzi() {
      let indirizzi = await fetch(
        "https://vps.lellovitiello.tk/Riassunty/API/indirizzi.php"
      );
      let ind = await indirizzi.json();

      for (let i = 0; i < ind.length; i++) {
        async function prendiMaterie() {
          let materie = await fetch(
            `https://vps.lellovitiello.tk/Riassunty/API/materie.php?indirizzo=${ind[i].Indirizzo}`
          );
          materie = await materie.json();

          for (let materia of materie) {
            Object.defineProperty(
              materia,
              "nome",
              Object.getOwnPropertyDescriptor(materia, "Materia")
            );
            delete materia["Materia"];

            Object.defineProperty(
              materia,
              "id",
              Object.getOwnPropertyDescriptor(materia, "IDMateria")
            );
            delete materia["IDMateria"];
            delete materia["Indirizzo"];
          }

          Object.defineProperty(
            ind[i],
            "nome",
            Object.getOwnPropertyDescriptor(ind[i], "Indirizzo")
          );
          delete ind[i]["Indirizzo"];
          Object.assign(ind[i], { dati: materie });
          Object.assign(ind[i], { id: i });
        }

        await prendiMaterie();
      }
      console.log("Main.js", ind);
      cambiaIndirizzi(ind);
    }
    prendiIndirizzi();
  };
  useEffect(fetchIndirizzi, []);

  return (
    <React.Fragment>
      <FullNavBar elementi={indirizzi} />{" "}
      <Link to="/Login">
        <Bottone TestoBottone="Per caricare un riassunto" />
      </Link>{" "}
      {indirizzi.map(indirizzo => {
        console.log(indirizzo);
        return <Indirizzi dati={indirizzo} />;
      })}
    </React.Fragment>
  );
}

export default Main;

MostraMaterie.js 处理页面渲染,如果位置是 /mostraMaterie/:id ,但不起作用。

import React, { useEffect, useState } from "react";
import "../App.css";
import history from "../Util/history";
import { Link } from "react-router-dom";
import FullNavBar from "../NavBar/FullNavBar";
import Indirizzi from "./Indirizzi";
import Bottone from "../Util/Bottone";

function MostraMaterie(props) {
  const [anni, cambiaAnni] = useState([]);

  // *** this function is never called ***
  const fetchAnni = () => {
    async function prendiAnni() {
      let anniJson = await fetch(
        "https://vps.lellovitiello.tk/Riassunty/API/ottieniAnni.php"
      );
      let idMateria = props.match.params.id;
      let ann = [];
      anniJson = await anniJson.json();

      for (let anno of anniJson) {
        async function prendiAnteprime() {
          let anteprimeFetch = await fetch(
            `https://vps.lellovitiello.tk/Riassunty/API/anteprima.php?idMateria=${idMateria}&anno=${anno}`
          );

          anteprimeFetch = await anteprimeFetch.json();
          for (let anteprima of anteprimeFetch) {
            Object.defineProperty(
              anteprima,
              "nome",
              Object.getOwnPropertyDescriptor(anteprima, "Titolo")
            );

            Object.defineProperty(
              anteprima,
              "id",
              Object.getOwnPropertyDescriptor(anteprima, "ID")
            );
            delete anteprima["Titolo"];
            delete anteprima["ID"];
          }

          let annoOggetto = {
            nome: anno + "",
            anteprime: anteprimeFetch
          };
          ann.push(annoOggetto);
        }

        await prendiAnteprime();
      }
      debugger;
      cambiaAnni(ann);
    }
    console.log("Vengo eseguito");
    prendiAnni();
  };

  useEffect(fetchAnni, []); 
  console.log(history.location);

  return (
    <React.Fragment>
      <FullNavBar indirizzi={anni} />{" "}
      <Link to="/Login">
        <Bottone TestoBottone="Per caricare un riassunto" />
      </Link>{" "}
      <h1> CIao CIAO CIAO CIAOC </h1>{" "}
      {
        //*** this depends on the response of the web server ***
         anni.map(anno => {
              console.log("Ciao");
              return <Indirizzi dati={anno} />;
            })}{" "}
    </React.Fragment>
  );
}

export default MostraMaterie;

我该如何让这个工作起来?

编辑

我已经移除了<FullNavBar indirizzi={anni} />{" "},现在我的请求已发送到服务器并且触发了“fetchAnni”函数。

问题是,我需要被移除的那段代码,似乎<FullNavBar indirizzi={anni} />{" "}在调用fetchAnni之前就被渲染了,即使像Artur建议的添加await prendiAnniasync fetchAnni也不起作用。

我还改变了从useEffect(fetchAnni,[]);useEffect(fetchAnni, [props.location.pathname]);,它是一个字符串。


你的代码很长,也许可以制作一个简单的jsfiddle让其他人玩耍,这样他们可以更好地帮助你。 顺便说一句,我的猜测是你需要将history.location添加到mostraMaterie组件中useEffect的第二个参数中。然后当history对象发生变化时,useEffect也会被触发。(尽管可能存在性能问题?) - BertC
我尝试将[history.location]作为第二个参数添加到useEffect中,但仍然无法正常工作。 - Davide Vitiello
history.location 是一个对象,useEffect 的第二个参数应该只与原始变量数组(如字符串、数字等)一起使用,因此您应该选择在 useEffect 中使用的原始字段(例如 props.match.params.id)。 - Arthur
一个建议,为了性能考虑,请尝试使用useMemo来避免冗余的重新计算。 - Uladz Kha
5个回答

11
import React, { useEffect } from 'react' 
import { useHistory } from 'react-router-dom'

const Root = () => {
    const history = useHistory()
    useEffect(() => {
        history.listen((location) => {
            console.log(`You changed the page to: ${location.pathname}`)
        })
    }, [history])
}

简单且运行良好。谢谢。 - Mansoorkhan Cherupuzha
useHistory现在已经被弃用,请优先使用useNavigate。 - Fed

5

第二个 useEffect 参数 [] :

首先:将[]作为 useEffect 的第二个参数发送将表示“永远不要重新运行此函数”(仅在挂载和卸载时运行两次)。

不要删除它!它是必需的:

您不想删除它,否则您的 effect 将在每次呈现时都被调用,从而导致性能问题。

选择正确的变量来跟踪:

您必须向 React 提供所有会影响函数重新渲染的字段,例如“当 URL 改变时”。

提示: React 每次都会测试是否有一个变量自上次呈现以来已更新,但我们也知道非原始值不起作用:

const a = {name: "Foo"};
const b = {name: "Foo"};
const res = (a === b); // False 

因此,在选择基本值时您需要注意。在您的情况下,最简单的是在路由中使用 /:idprops.match.params.id

  useEffect(fetchAnni, [props.match.params.id]); 
  console.log(`Render for ID: ${props.match.params.id}`);

异步/等待

此外,您的函数 prendiAnni 被定义为 async,但您调用它的方式为:

console.log("Vengo eseguito");
prendiAnni();

与其

await prendiAnni();

感谢您的详细解释,我一有空就会尝试。我没有使用await关键字进行调用,因为我需要将整个函数设置为async,并且当作为useEffect参数传递时,React会在控制台中抛出警告。 - Davide Vitiello

4

useEffect Hook 的依赖数组中包含 URL。

示例:

import React, { useEffect } from 'react';

const Component = () => {
    
    const url = window.location.pathname.split('/').pop();

    useEffect(() => {
    
        // Function will retrigger on URL change

    }, [url]);

    return(
        <div className = 'Whatever'>Whatever</div>
    );

}

export default Component;

这会起作用吗?React 如何知道进行重新渲染以开始检查 useEffect?除非 URL 来自 ReactRouter 等东西... 对吧? - Jordan

2
看起来你将一个空数组作为 useEffect() 的第二个参数传递了进去, useEffect(fetchAnni, []); 来自于 https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects 如果你想要仅在挂载和卸载时运行 effect 并清除它,可以将一个空数组 ([]) 作为第二个参数传递进去。这告诉 React 你的 effect 不依赖于任何来自 props 或 state 的值,因此它永远不需要重新运行。
如果你想要在每次渲染时运行 effect,可以省略第二个参数。如果你想要它仅在特定的 props/state 值发生更改时运行,你可以将它们作为数组值传递给第二个参数。

1

代码 I_love_vegetables 已经可以正常工作,但是在 react-router-dom v6 中 useHistory 已被废弃,需要使用 useNavigate。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接