React 路由组件多次调用函数

Posted

技术标签:

【中文标题】React 路由组件多次调用函数【英文标题】:React router component calls function multiple times 【发布时间】:2021-11-22 17:41:13 【问题描述】:

我正在尝试学习区块链开发,所以几周前我开始学习 Solidity,我不得不为我与 React 签订的合同创建一个前端应用程序,我也不知道。

所以,我已经阅读了文档并观看了使用 web3 库和一些页面转换的教程。现在我可以在应用程序的页面之间导航,但是当我路由到特定页面时,我的函数会被多次调用。

这是我每次运行应用程序时都会加载的 index.js。我已经这样设置了路线。

index.js(我没有 app.js 和 app.js 一样使用 index.js)

import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';
import Web3 from 'web3'
import  ConnectPage  from './ConnectPage';
import  MintPage  from './MintPage';
import  AllCryptonauts  from './AllCryptonauts';
import  MyCryptonauts  from './MyCryptonauts';
import  NotFound  from './NotFound';
import  BrowserRouter, Route, Switch  from 'react-router-dom'

function App() 
  if (window.ethereum) 
    window.web3 = new Web3(window.ethereum)
    window.ethereum.enable()
   else 
    alert("Could not detect MetaMask. Please install MetaMask before proceeding!");
  

  return (
    <div>
        <Switch>
        <Route exact path="/" component=ConnectPage />
        <Route exact path="/MintPage" component=MintPage />
        <Route exact path="/MyCryptonauts" component=MyCryptonauts />
        <Route exact path="/AllCryptonauts" component=AllCryptonauts />
        <Route path="*" component=NotFound />
      </Switch>
    </div>
  );


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter><App /></BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

我在第一页创建了一个连接按钮,它将我重定向到薄荷部分。

ConnectPage.js

import React from 'react'

export const ConnectPage = (props) => 

    async function Connect() 
        const web3 = window.web3
        const networkId = await web3.eth.net.getId()
        if (networkId === 4) 
            props.history.push("/MintPage")
         else 
            await window.ethereum.request(
                method: 'wallet_switchEthereumChain',
                params: [ chainId: '0x4' ],
            );
            props.history.push("/MintPage")
        
    

    return (
        <div>
            <div style= display: 'flex', justifyContent: 'center', alignItems: 'center' ><img src="https://nftornek.000webhostapp.com/frontend/cnlogo.png"></img></div>
            <div style= display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: '5%'><button className="connectButton" onClick=Connect>Enter the Universe</button></div>
        </div>
    )

这是我进行铸币交易的地方。我放了 console.log(" called checkChainID") 来查看 checkChainID 函数被调用了多少次。每次加载页面时它会被调用 12 次,在我尝试导航到同一页面后会调用两次。

作为所有这些的初学者,我收集了我从教程中获得的信息,这些信息并不明显,并尝试制作一个带有路由的测试应用程序(我也通过教程制作了它)

路由就像我在教程中所做的一样工作,我想继续为我的应用程序使用这个路由示例,但可能由于我缺乏 React 的基本知识,我在页面中做错了。我一直在研究这个问题几个小时,但无法真正理解我能做些什么来解决它。

我认为这是因为 useState,因为它每次调用它时都会呈现应用程序,但我不确定是否有任何方法可以防止这种情况发生,或者想出更聪明的方法。

MintPage.js

import React,  useState  from 'react';
import Web3 from 'web3'
import Contract from "../src/build/Contract.json";

export const MintPage = (props) => 

  const web3 = window.web3;
  var [currentAccount, setCurrentAccount] = useState("0x0");
  var [currentBalance, setCurrentBalance] = useState("0");
  var [mintAmount, setMintAmount] = useState(1);
  const [addAmount, setAddAmount] = useState(true);
  const [subtractAmount, setSubtractAmount] = useState(false);

  window.ethereum.on('chainChanged', (_chainId) => checkChainID());
  window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
  checkChainID();

  async function checkChainID() 
    const networkId = await web3.eth.net.getId();
    if (networkId !== 4) 
      props.history.push("/")
     else 
      loadBlockchainData();
    
    console.log("called checkChainID")
  

  async function loadBlockchainData() 

    window.web3 = new Web3(window.ethereum);
    const accounts = await web3.eth.getAccounts();
    setCurrentAccount(accounts[0]);
    getBalance(accounts[0]);
  

  async function getBalance(acc) 
    const balance = await web3.eth.getBalance(acc);
    var balanceEth = web3.utils.fromWei(balance, 'ether');
    setCurrentBalance(parseFloat(balanceEth).toFixed(3) + " ETH");

    const SmartContractObj = new web3.eth.Contract(Contract.abi, "0x187FF2d65dd7204f11ea0487F2EED36378946902");
  

  function MintPage() 
    props.history.push("/MintPage")
  

  function MyCryptonauts() 
    props.history.push("/MyCryptonauts")
  

  function AllCryptonauts() 
    props.history.push("/AllCryptonauts")
  

  function Disconnect() 
    props.history.push("/")

  

  return (
    <div>
      <div style= display: 'flex', justifyContent: 'center', alignItems: 'center' ><img src="https://nftornek.000webhostapp.com/frontend/cnlogo.png" width='500' height='180'></img></div>
      <div style= display: 'flex', justifyContent: 'center' >
        <button className="regularButton divide" onClick=MintPage>Mint</button>
        <button className="regularButton divide" onClick=MyCryptonauts>My Cryptonauts</button>
        <button className="regularButton divide" onClick=AllCryptonauts>All Cryptonauts</button>
        <button className="regularButton divide" onClick=Disconnect>Disconnect</button>
      </div>
      <div style= display: 'flex', justifyContent: 'center' ><p className="accountText">Current Account: currentAccount</p></div>
      <div style= marginTop: '25%' ></div>
      <div style= display: 'flex', justifyContent: 'center' ><p className="accountText">Mint mintAmount Cryptonaut for XXX ETH</p></div>
      <div style= display: 'flex', justifyContent: 'center' ><button className="amountButton divide" disabled=subtractAmount ? 0 : 1 onClick=() => 
        if (mintAmount <= 1) 
          setMintAmount(1);
          setSubtractAmount(false);
         else 
          setMintAmount(mintAmount - 1);
         if (mintAmount === 2) 
          setSubtractAmount(false);
         if (mintAmount >= 1) 
          setAddAmount(true);
        
      >-
      </button>
        <button className="mintButton divide" onClick=() => 
          console.log(mintAmount);
        >MINT
        </button>
        <button className="amountButton divide" disabled=addAmount ? 0 : 1 onClick=() => 
          if (mintAmount >= 5) 
            setMintAmount(5);
           else 
            setMintAmount(mintAmount + 1);
          
          if (mintAmount === 4) 
            setAddAmount(false);
           if (mintAmount >= 1) 
            setSubtractAmount(true);
          
        >+
        </button>
      </div>
      <div style= display: 'flex', justifyContent: 'center', marginTop: '7px' ><p className="accountText">Current Balance: currentBalance</p></div>
    </div>

  )

我将非常感谢任何帮助。请指出我正确的方向!不过,可能需要更多详细的解释,因为我对此太陌生了。 :) 谢谢大家。

【问题讨论】:

【参考方案1】:

有问题的部分是这三行:

window.ethereum.on('chainChanged', (_chainId) => checkChainID());
window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
checkChainID();

您通常不希望在组件的渲染周期中直接调用函数,因为组件可能会出于多种不同的原因重新渲染。每次此组件呈现时,它都会向全局 ethereum 对象添加一个事件侦听器,因此您将在每个组件呈现时获得一个额外的侦听器。由于这是一个功能组件,您应该将这些行包装在一个效果挂钩中,以便您可以控制它们何时运行。

useEffect(() => 
  window.ethereum.on('chainChanged', (_chainId) => checkChainID());
  window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
  checkChainID();

  return () => 
    // remove the 'chainChanged' and 'accountsChanged' event listeners
  
, [])

空数组告诉这个效果只在组件第一次渲染时运行一次。如果您需要它在某些状态发生变化时运行,或者您需要访问效果中运行的函数内的任何状态变量,则需要在依赖数组中列出这些变量。

您还需要从删除您的侦听器的钩子中返回一个函数,这样他们就不会在每次用户访问页面、离开和返回时继续增加。

你可以阅读更多关于useEffecthere的信息。

【讨论】:

哦,这就是我当时正在寻找的,useEffect!非常感谢,它解决了我的问题。我将立即阅读更多相关信息。

以上是关于React 路由组件多次调用函数的主要内容,如果未能解决你的问题,请参考以下文章

react中函数调用方法

多次调用函数时 setState 不会发生变化

React.js:将道具从函数传递到组件

为啥在初始页面加载时多次调用 React Ref 回调(作为箭头函数或内联函数)?

多次调用子组件构造函数

React 和 React-Router:通过函数调用进行路由,参数传递到路由中,而不是单击 <Link>