是否可以通过使用 HOC(高阶组件)在 class 组件中使用 React Hooks?

Is it possible to use React Hooks in class component by using HOC(Higher Order Component)?

我可以使用class组件中的功能组件吗?我将调用从 class 组件中的功能组件中提取的函数。但它给出了如下错误。

Unhandled Rejection (Error): Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons

所以我尝试在功能组件中调用它,但 即使在功能组件 中,我也遇到了与在 class 组件中调用时相同的错误。

Functional component

import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';

export function App() {
  useEffect(() => {
    async function GetBlockId() {
      const wallet = useWallet();
      console.log(wallet); // =====> This is not displaying.
      const { ethereum, connect } = wallet;
      const ethersProvider = new providers.Web3Provider(ethereum);
      const { blockNumber } = await ethersProvider.getTransaction(hash);
      console.log(blockNumber);
    };
    GetBlockId()
  }, []);

  return <div>
    <h1>{wallet}</h1>
  </div>
}

Class component

import React, { Component } from 'react'
import { GetBlockId } from './util';   // =====>> I hope to get result from here.
import { hash } from './hash'

export default class App extends Component {
    constructor(props) {
        super(props)
    }
    componentDidMount(): void {
        const blockNumber: any = GetBlockId(hash);
        console.log(blockNumber);
    }
    render() {
        return (
            <div>
                <h1>test</h1>
            </div>
        )
    }
}

util.tsx

import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
// import { Container } from './styles';

export function GetBlockId() {
  useEffect(() => {
    async function GetBlockId() {
      const wallet = useWallet();
      const { ethereum, connect } = wallet;
      const ethersProvider = new providers.Web3Provider(ethereum);
      const { blockNumber } = await ethersProvider.getTransaction(hash);
      return blockNumber;
    };
    GetBlockId()
  }, []);
}

所以最后希望在class组件中使用“use-wallet”包。那可能吗?如果是,如何在class组件中使用useWallet钩子?

GetBlockId 不是 React 功能组件。没有return方法;因此它会抛出一个错误,说你不能在非功能组件中使用钩子。将此功能更改为功能组件(通过 returning JSX 组件),它应该可以工作。

请注意,您的 getBlockId 函数是递归的,并且会失败。

根据文档。在您的 class 组件(父组件)中。您将需要使用 UseWalletProvider 并在您的功能组件中使用挂钩。

这是一个示例(未经测试),希望它能帮助您。

import React, {useState} from 'react';
import { useWallet, UseWalletProvider } from 'use-wallet';
import { ethers } from 'ethers';
import { hash } from './hash'
function App() {
  const wallet = useWallet()
  const blockNumber = wallet.getBlockNumber()
  const [blockId, setBlockId] = useState('')
  useEffect(()=>{
    const getBlockId = async() => {
      const { ethereum, connect } = wallet;
      const ethersProvider = new ethers.providers.Web3Provider(ethereum);
      return await ethersProvider.getTransaction(hash);
    }
    setBlockId(getBlockId());
  },[]);
  //Note that now blockId contains the blockId that you get.
  //You can use it with child components etc.
  return (
    <div>
        <p>{blockId}</p>
    </div>
  );
}

function Index() {
  return (
    <UseWalletProvider
    chainId={1}
    connectors={{
      // This is how connectors get configured
      portis: { dAppId: 'my-dapp-id-123-xyz' },
    }}
  >
    <App />
  </UseWalletProvider>
  );
}

你不能在 class 组件中调用 react hook。

根据 ReactJS Doc 您可以组合这些功能。

You can’t use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.

试试下面的代码,你最好不要在功能组件中的函数中使用useXXX,

export function App() {
  const wallet = useWallet();
  const getBlockId = useCallback(() => {
      console.log(wallet);
      const { ethereum, connect } = wallet;
      const ethersProvider = new providers.Web3Provider(ethereum);
      const { blockNumber } = await ethersProvider.getTransaction(hash);
      console.log(blockNumber);
  }, [wallet]);
  useEffect(() => {
    getBlockId()
  }, []);

  return <div>
    <h1>{wallet}</h1>
  </div>
}

React hooks 只兼容 React 函数组件,根本不能用在 class 组件中。您第一次尝试的问题是您试图在回调中调用 React 钩子,这违反了钩子规则之一。

Rules of Hooks

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)

Only Call Hooks from React Functions

Don’t call Hooks from regular JavaScript functions. Instead, you can:

  • ✅ Call Hooks from React function components.
  • ✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).

By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.

您的代码在传递给 useEffect 挂钩的回调函数中调用 useWallet。请注意,这与调用另一个挂钩的自定义挂钩不同。

useWallet 挂钩调用移到函数组件主体中。这将关闭渲染范围中的 wallet 值,并将在 useEffect 挂钩回调中变为 available/accessible。我假设当组件安装时你仍然只 want/need useEffect 挂钩到 运行 一次,所以我不考虑那个方面。

import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';

export function App() {
  const wallet = useWallet();

  useEffect(() => {
    console.log(wallet);
    const { ethereum, connect } = wallet;

    async function GetBlockId() {          
      const ethersProvider = new providers.Web3Provider(ethereum);
      const { blockNumber } = await ethersProvider.getTransaction(hash);
      console.log(blockNumber);
    };

    GetBlockId();
  }, []);

  return (
    <div>
      <h1>{wallet}</h1>
    </div>
  );
}

更新

要将 useWallet 挂钩与 class 组件一起使用,我建议创建一个可以使用它的高阶组件并将 wallet 值作为道具传递。

示例:

const withWallet = Component => props => {
  const wallet = useWallet();
  return <Component {...props} wallet={wallet} />;
};

装饰class组件并通过this.props.wallet

访问
class App extends Component {
  constructor(props) {
    super(props)
  }
  componentDidMount(): void {
    const { ethereum, connect } = this.props.wallet;

    ...
  }
  render() {
    return (
      ...
    );
  }
}

export default withWallet(App);