React类组件转Hooks函数式组件实现方法
MinterCard类组件转Hooks函数组件实现
以下转换基于react-bootstrap、axios、Web3技术栈的NFT Mint通用场景,你可以直接对照原有类组件的业务字段调整参数。
类组件与Hooks核心映射规则
先明确对应关系,方便你理解底层逻辑:
- 类组件
this.state/this.setState对应函数组件useState,建议按业务维度拆分多个独立状态,无需像类组件一样把所有字段塞进同一个state对象 - 类组件生命周期
componentDidMount/componentDidUpdate/componentWillUnmount统一由useEffect承载,通过依赖数组控制触发时机,effect内部return的函数负责执行卸载时的清理逻辑 - 类组件挂载在
this上、不触发UI更新的实例变量,对应函数组件的useRef - 类组件自定义方法可直接在函数组件内声明,需要固定函数引用避免重复触发的场景用
useCallback包裹
转换后完整组件代码
import { useState, useEffect, useCallback, useRef } from 'react' import { Card, Button, Form, Alert, Spinner } from 'react-bootstrap' import axios from 'axios' import Web3 from 'web3' // 替换为你项目实际的合约ABI、合约地址 const NFT_CONTRACT_ABI = [] const NFT_CONTRACT_ADDRESS = '0x替换为你的合约地址' const MINT_PRICE = '0.01' // 单个NFT铸造价格,单位ETH const MAX_MINT_AMOUNT = 5 // 单次最大铸造数量 export default function MinterCard() { // 拆分声明状态,每个状态独立维护 const [web3Instance, setWeb3Instance] = useState(null) const [currentAccount, setCurrentAccount] = useState('') const [nftContract, setNftContract] = useState(null) const [mintCount, setMintCount] = useState(1) const [isMinting, setIsMinting] = useState(false) const [latestTxHash, setLatestTxHash] = useState('') const [error, setError] = useState('') const [mintedNftData, setMintedNftData] = useState(null) // 存储不需要触发重渲染的实例值 const axiosCancelRef = useRef(null) // 钱包连接逻辑 const connectWallet = useCallback(async () => { setError('') try { if (!window.ethereum) throw new Error('请先安装MetaMask钱包插件') const web3 = new Web3(window.ethereum) const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }) const contract = new web3.eth.Contract(NFT_CONTRACT_ABI, NFT_CONTRACT_ADDRESS) setWeb3Instance(web3) setCurrentAccount(accounts[0]) setNftContract(contract) } catch (err) { setError(err.message || '钱包连接失败,请重试') } }, []) // NFT铸造核心逻辑 const handleMint = useCallback(async () => { if (!nftContract || !currentAccount || !web3Instance) return setIsMinting(true) setError('') setLatestTxHash('') try { // 调用合约铸造方法,参数可根据你实际的合约逻辑调整 const txReceipt = await nftContract.methods.mint(mintCount).send({ from: currentAccount, value: web3Instance.utils.toWei(String(Number(MINT_PRICE) * mintCount), 'ether') }) setLatestTxHash(txReceipt.transactionHash) // 铸造成功后拉取NFT元数据 const newTokenId = txReceipt.events.Transfer.returnValues.tokenId const { data } = await axios.get(`/api/nft/metadata/${newTokenId}`, { cancelToken: axiosCancelRef.current.token }) setMintedNftData(data) } catch (err) { if (!axios.isCancel(err)) { setError(err.message || '铸造失败,请检查网络或钱包余额') } } finally { setIsMinting(false) } }, [nftContract, currentAccount, web3Instance, mintCount]) // 副作用:对应类组件挂载、更新、卸载生命周期 useEffect(() => { // 初始化axios取消令牌 axiosCancelRef.current = axios.CancelToken.source() // 页面加载时如果钱包已授权,自动连接 if (window.ethereum?.selectedAddress) { connectWallet() } // 监听钱包账户切换事件 const handleAccountSwitch = (accounts) => { setCurrentAccount(accounts[0] || '') } window.ethereum?.on('accountsChanged', handleAccountSwitch) // 清理逻辑:对应componentWillUnmount return () => { // 取消组件卸载时未完成的接口请求 axiosCancelRef.current?.cancel('组件已卸载') // 移除事件监听,避免内存泄漏 window.ethereum?.removeListener('accountsChanged', handleAccountSwitch) } }, [connectWallet]) return ( <Card style={{ width: '22rem', margin: '2rem auto' }}> <Card.Img variant="top" src={mintedNftData?.image || '/default-nft-cover.png'} /> <Card.Body> <Card.Title>NFT 铸造通道</Card.Title> {error && <Alert variant="danger">{error}</Alert>} {latestTxHash && <Alert variant="success">铸造成功!交易哈希:{latestTxHash.slice(0, 10)}...{latestTxHash.slice(-8)}</Alert>} {!currentAccount ? ( <Button variant="primary" className="w-100" onClick={connectWallet}> 连接钱包开始铸造 </Button> ) : ( <> <Form.Group className="mb-3"> <Form.Label>铸造数量(单次最多{MAX_MINT_AMOUNT}个)</Form.Label> <Form.Control type="number" min={1} max={MAX_MINT_AMOUNT} value={mintCount} onChange={(e) => setMintCount(Math.min(Number(e.target.value), MAX_MINT_AMOUNT))} /> </Form.Group> <Button variant="success" className="w-100" disabled={isMinting} onClick={handleMint} > {isMinting ? ( <><Spinner animation="border" size="sm" className="me-2" />铸造中...</> ) : ( `铸造 ${mintCount} 个NFT(${(Number(MINT_PRICE)*mintCount).toFixed(2)} ETH)` )} </Button> <div className="mt-2 text-muted small text-end"> 连接地址:{currentAccount.slice(0,6)}...{currentAccount.slice(-4)} </div> </> )} </Card.Body> </Card> ) }
关键逻辑说明
- 状态管理差异:和类组件全量state合并更新不同,
useState是单状态独立替换更新,拆分后状态依赖更清晰,不会出现无关字段更新触发不必要逻辑的问题 - 生命周期执行逻辑:
useEffect的执行时机是浏览器完成页面绘制之后,不会阻塞渲染;依赖数组为空时仅在组件挂载后执行一次,依赖数组传入变量时,变量引用变化就会重新执行effect - 内存泄漏规避:所有事件监听、未完成的异步请求,都必须在effect的return清理函数中移除/取消,这部分和类组件在
componentWillUnmount写清理逻辑的目标一致 - 性能注意点:传递给子组件的回调函数、作为effect依赖的函数,必须用
useCallback包裹,否则每次组件重渲染都会生成新的函数引用,导致effect重复执行、子组件无效重渲染
如果你原有类组件包含其他业务逻辑,按照上述映射规则逐块迁移即可,不要刻意模仿类组件的写法把所有状态塞进一个
useState,拆分状态是Hooks写法下更易维护的实践。
内容的提问来源于stack exchange,提问作者danielduv1990
相关产品推荐
相关产品推荐

