在 Web3 崛起的新浪潮中,前端开发者不再只是“做页面”的角色,而是连接链上世界与用户体验的桥梁。本文将带你重塑 Web3 世界观,厘清前端的全新定位与价值,发现属于你的时代机遇!
📚 作者:Henry 🧱 系列:《DApp 前端开发入门与架构实践》 · 第 1 篇 👨💻 受众:Web3 前端工程师 / Web3入门者 👉 系列持续更新中,建议收藏专栏或关注作者
随着互联网的发展,Web 应用经历了从 Web1.0 的信息展示,到 Web2.0 的用户参与,再到 Web3.0 的所有权反转。
时代 | 特征 | 用户角色 |
---|---|---|
Web1.0 | 只读网页,信息静态 | 消费者 |
Web2.0 | 社交互动、中心化服务 | 贡献者 |
Web3.0 | 所有权回归用户、去中心化 | 拥有者/参与者 |
DApp,即“Decentralized Application”,是 Web3 时代最具代表性的应用形态,它不再依赖中心化服务器,而是运行在区块链等去中心化网络之上。
DApp 的核心由三层组成:
Dapp架构图:
项目 | Web2 应用 | DApp(Web3 应用) |
---|---|---|
数据存储 | 集中式服务器 / 数据库 | 去中心化链上存储(如 Ethereum) |
用户认证 | 邮箱密码 / OAuth 登录 | 钱包地址 + 签名验证 |
权限控制 | 后端校验 + Session / Token | 智能合约逻辑 + on-chain 权限 |
应用逻辑 | 服务端 Node.js/Java/PHP | 智能合约(Solidity) |
用户资产 | 存于平台(如游戏道具) | 用户自持(如 NFT) |
拥有权 | 平台所有 | 用户所有 |
🔍 举例说明:
错误理解 | 正确认知 |
---|---|
DApp 不需要服务器 | 许多场景仍需后端辅助(如:索引服务、通知系统) |
数据都必须上链 | 实际上:链上存状态、链下存大数据、图像、评论等 |
钱包登录就是安全 | 钱包只负责签名认证,业务逻辑与权限仍需审慎设计 |
使用 MetaMask 就是 DApp | 连接钱包只是入口,核心在于链上逻辑与合约交互 |
随着 Web3 崛起,越来越多前端工程师希望转型,但常见困惑包括:
但实际上,Web3 中的前端角色,远比传统 Web2 更关键 —— TA 既是用户入口,也是链上交互的桥梁。
能力类别 | Web2 技术 | Web3 对应能力 |
---|---|---|
UI 框架 | React / Vue | React + wagmi/viem |
数据通信 | REST / GraphQL / Axios | JSON-RPC / subgraph / viem client |
状态管理 | Redux / Zustand | Zustand + chain state hooks |
用户认证 | JWT / OAuth / Session | 钱包连接 + 签名认证(EIP-191/712) |
服务调用 | fetch API | publicClient.readContract / walletClient.writeContract |
异常处理 | try-catch / toast提示 | 链上 Revert 原因提示 / 事件监听失败重试机制 |
安全意识 | XSS/CSRF 防御 | 签名钓鱼识别 / 合约调用前权限校验 |
常见误区 | 正确认知 |
---|---|
“我不懂 Solidity,没法搞 Web3 前端” | 合约由后端或协议团队提供,你只需理解 ABI 即可 |
“我不会写交易,只能写 UI” | wagmi 已将调用封装为 Hook,像 useQuery 一样易用 |
“Web3 前端是不是比 Web2 简单?” | Web3 多了链上状态、权限、安全、Gas、连接等复杂场景 |
“WalletConnect 就是连接钱包?” | 实际还包括多链连接、QR 协议、连接恢复等逻辑 |
作为 Web3 前端开发者,并不需要像节点工程师一样掌握区块链协议细节,但必须了解以下几个关键问题:
这些问题的答案,直接决定你是否能写出一个有“链上认知能力”的 DApp 前端。
Ethereum 是一种状态机驱动的去中心化网络。它的核心运行流程可以总结如下:
构造交易 → 用户签名 → 交易广播 → 节点打包进 mempool → 出块确认 → 状态更新 → UI 同步
阶段 | 描述 |
---|---|
构造交易 | 包括 to / value / data / gasLimit / chainId 等字段 |
用户签名 | 使用钱包(如 MetaMask)对交易 hash 进行签名 |
广播交易 | 将签名后的交易通过 JSON-RPC 发给节点,进入 mempool |
节点出块 | 区块生产者从 mempool 中选择交易打包,形成新区块 |
合约执行 + 状态更新 | 执行 EVM 字节码,状态树(State Trie)更新,事件触发 |
// Viem 示例:估算调用代价
const gasEstimate = await publicClient.estimateGas({
account,
to: contractAddress,
data: encodedData,
})
// Viem 示例:等待交易确认
const txHash = await walletClient.sendTransaction({...})
await publicClient.waitForTransactionReceipt({ hash: txHash }) // 默认确认 1 个块
错误理解 | 正确认知 |
---|---|
“链上是实时更新的,调用后立刻返回状态” | 区块出块有延迟,一般 3~15 秒,状态同步需谨慎处理 |
“失败交易不会扣钱” | 所有交易一旦被广播并被执行,即便失败也会消耗 Gas |
“等待一个区块就一定安全” | 区块可能重组(fork),关键操作建议等多个确认数 |
“RPC 请求就是调用链上” | 实际只是向节点发起调用或查询,是否进区块需额外确认逻辑 |
在 Web2 中,“账户”通常代表用户名 + 密码 + Session,但在 Web3 中,“账户”即地址,而身份认证、操作权限全部基于签名验证。必须理解:
类型 | 描述 | 是否能发起交易 | 是否含代码 | 关键区别 |
---|---|---|---|---|
EOA | 外部拥有账户,用户通过私钥控制 | ✅ | ❌ | 由用户私钥签名 |
合约账户 | 部署在链上的合约,自动执行代码 | ❌(默认) | ✅ | 无私钥,不能直接签名交易 |
用于纯文本或简单结构的签名验证
const sig = await walletClient.signMessage({
account,
message: 'Welcome to DApp!',
})
前端多用于“登录签名”,生成签名后发送到后端验证地址
结构化数据更安全、明确,尤其适用于合约调用、MetaTx、Permit 等场景。
const sig = await walletClient.signTypedData({
account,
domain: {
name: 'MyDApp',
version: '1',
chainId: 1,
verifyingContract: contractAddress,
},
types: {
Login: [
{ name: 'user', type: 'address' },
{ name: 'nonce', type: 'string' },
],
},
message: {
user: address,
nonce: '123456',
},
})
由于合约钱包没有私钥,它不能使用 signMessage
,必须通过合约接口验证签名有效性:
// EIP-1271 接口定义
function isValidSignature(bytes32 _hash, bytes memory _signature)
external
view
returns (bytes4 magicValue);
const isContract = await publicClient.getBytecode({ address })
const accountType = isContract ? 'ContractWallet' : 'EOA'
前端判断为合约钱包后,应使用合约方式验证签名(如调用 isValidSignature()
)
构造交易 → 钱包签名 → 发送 RPC → 链上确认 → 状态同步
用户发起 → 合约构建多签消息 → 所有者签名 → Relayer 执行 → 状态更新
🔍 合约钱包可能使用 Relayer / Paymaster / Bundler 发起交易(未来 AA 篇将详细讲)
// EOA 登录签名
const signature = await walletClient.signMessage({
account,
message: 'Login to DApp',
})
// 合约钱包判断 + 1271 验证
if (isContract) {
const isValid = await publicClient.readContract({
address,
abi: eip1271ABI,
functionName: 'isValidSignature',
args: [hashMessage('Login to DApp'), signature],
})
}
构造参数 → 发起签名 → 构造交易 → 发送到 RPC → 节点验证 → 出块 → 状态更新
调用类型 | 是否上链 | 是否需要签名 | 是否消耗 Gas | 适用场景 | wagmi Hook |
---|---|---|---|---|---|
read |
否 | 否 | 否 | 查询余额/状态 | useReadContract , readContract |
write |
是 | 是 | 是 | 执行转账/mint | useWriteContract , writeContract |
const { data: totalSupply } = useReadContract({
abi,
address: contractAddress,
functionName: 'totalSupply',
})
此类调用走 eth_call
,不会真正发起交易,适用于 UI 状态显示。
const { writeContractAsync } = useWriteContract()
const txHash = await writeContractAsync({
abi,
address: contractAddress,
functionName: 'mint',
args: [1],
})
txHash
,等待链上确认const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash })
if (receipt.status === 'success') {
toast.success('Mint success 🎉')
// 可更新链上数据
}
🚨 注意:交易上链成功 ≠ 合约执行成功,需看 receipt.status
是不是 "success"
状态阶段 | UI 行为 | 技术建议 |
---|---|---|
点击按钮 | 设置 loading | 使用 Zustand / React 状态更新按钮状态 |
钱包签名中 | 提示“等待钱包签名” | 使用 isPending / 弹窗 Toast |
签名完成 | 等待链上确认 | 使用 waitForTransactionReceipt |
确认成功 | 显示成功、更新状态 | 主动读取链上状态 → 更新 UI |
失败或拒绝 | 显示错误 | 捕获错误,展示 error.message |
误区描述 | 正确认知 |
---|---|
调用函数就能看到结果 | 写入调用需等待交易确认,且需手动查询链上状态 |
调用失败是前端 bug | 合约本身可 Revert,必须展示错误提示(如余额不足) |
钱包签名完成就算成功了 | 需等交易上链并执行成功,才能视为完成 |
链上数据总是即时的 | 节点同步状态/确认数有延迟,不能完全依赖回调结果 |
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!