React 路由器组件多次调用函数

React router component calls function multiple times

我正在尝试学习区块链开发,所以我几周前开始学习 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")
);

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

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>

  )
}

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

有问题的是这三行:

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
  }
}, [])

空数组告诉此效果仅 运行 一次,即组件首次呈现时。如果你需要它 运行 当一些状态改变时,或者你需要访问函数中的任何状态变量 运行 在效果中,你需要列出依赖数组中的那些。

您还需要 return 从钩子中移除监听器的函数,这样他们就不会在每次用户访问页面、离开和 returns.

您可以阅读更多关于 useEffect here