资讯详情

资讯详情

Vue3 + Web3.js 实战教程:从钱包连接到DApp前端开发

简介《区块链应用开发Vue3Webjs的DApp实战》是一份面向有一定前端基础、希望进入Web3领域的开发者的PDF教程系统讲解如何基于Vue3与Web3.js构建去中心化应用。内容从区块链基础和DApp架构讲起依次涵盖Vue3组合式API、Web3.js与以太坊交互、Solidity智能合约开发与测试、前端集成方案、安全机制、性能优化与部署运维并以去中心化投票系统作为综合案例串联全流程帮助读者理解合约设计、钱包接入、事件监听、状态管理等关键环节。文档共94页包含1个PDF文件压缩包约4.46MB支持阅读器大纲与章节快速定位目录层次清楚。当前已有90人学习适合作为入门路线图或开发速查手册在搭建DApp时可随时查阅对应模块。1. 区块链应用开发选型为什么 Vue3 Web3.js 是 DApp 前端的稳妥起点区块链应用开发这四个字看着宏大可拿到“Vue3 Web3.js 的 DApp 实战”这一方向时多数人卡在同一个位置页面能跑链上接口也查得到却不知道用户的钱包账户怎么变成页面状态里的一个值也不知道交易发出后回执要怎么解析才不会漏判成败。这个方向的本质是把“账户授权、余额读取、交易发送、回执解析”这条链路用一套成熟栈在浏览器里跑通。标题里的 Webjs 就是 Web3.js 的简写它是连接浏览器与链节点最常用的开源库之一。适合做存证、溯源、积分这类偏业务型 DApp 的前端开发者也适合正把区块链交互从黑匣子变成常规接口的一线工程师。文中的命令、代码和参数边界照抄就能起跑。2. 先搞懂 Web3.js 在 DApp 里的位置Provider、交易与合约三层模型DApp 前端实际只做两件事从链上读数据、往链上写交易。读数据要快写交易要稳两者对接口的要求完全不同。Web3.js 把这两类能力组织成三个各司其职的部分任何联调问题本质上都能归到其中一层。这一章先把分层模型讲清楚后面敲代码时你就可以按这个框架判断问题出在哪而不是靠猜。2.1 Vue3 的响应式状态到底帮 DApp 省了什么页面上的账户地址、交易哈希、余额、连接状态全部是异步数据。没有框架时你得手写一堆 then 回调去改 DOM账户切换要手动绑定组件销毁还得记得解绑。Vue3 的 ref/reactive 把状态变化变成可订阅的响应式数据链上回调里一赋值页面自动更新这是 DApp 最需要的体验。组合式 API 让“钱包状态”“合约状态”“交易状态”可以拆成独立的模块。我在一个模拟存证项目 X 里就是按这个思路做的后端只需要知道页面当前在哪个链、哪个账户前端把组合式函数的结果直接传给业务组件逻辑边界非常干净。相比之下选项式 API 要在 data、methods、watch 之间来回跳账户切换到重新拉余额的联动逻辑很容易散落。选型判断标准也很直接如果团队里前端占多数、不想维护自建网关Vue3 Web3.js 的前期成本最低。自建网关能省前端工作量但它把链路压在自己的服务上节点一旦不稳定问题反而难排查。中小团队做业务型 DApp让前端直接接链是最务实的起步姿势。等业务量大了再在中间加服务层也不迟。2.2 Web3.js 的三个职责边界Provider、Transaction、Contract第一层是 Provider负责网络连接。你执行 new Web3(url) 时传入的 HTTP 或 WebSocket 地址就是 Provider所有请求都从它出去。第二层是 web3.eth 命名空间下的一组方法负责转账、查余额、查区块这类底层 RPC 调用。第三层是 web3.eth.Contract负责把合约地址和 ABI 绑定起来让你用 JavaScript 函数直接调用合约方法。我习惯按“连接、原生交易、合约方法”来记忆这三个层次。排错时先判断题出在哪层请求根本发不出去问题在 Provider请求能发出但返回的数据不对问题多半在方法参数合约调用结果和预期不符问题大概率在 ABI。联调阶段 80% 的异常都能在这个顺序里找到答案不用打开控制台瞎猜。下表是分层的定位也是线上问题的排查顺序分层入口对象典型用法出错时看什么Providernew Web3(rpcUrl)创建连接节点地址、网络可达性原生交易web3.ethgetBalance / sendTransaction / getGasPrice参数单位、链 ID合约方法web3.eth.Contractmethods.balanceOf(addr).call()ABI、地址大小写先看连接、再看方法、最后查合约这个顺序能避免很多重复劳动。比如账户余额显示 0先确认节点连通再确认地址没拼错最后才怀疑合约问题每一步都有明确的验证对象。2.3 推荐目录结构把链上逻辑和 UI 解耦链上逻辑最怕散落在组件里。组件只负责展示和交互链上调用应该收敛到 services 层钱包状态收敛到组合式函数层。这样更换 RPC 节点、合约地址时不用改页面代码测试时也方便替换。我常用的目录结构如下路径职责src/services/web3.js统一创建 Web3 实例src/services/erc20.js代币余额、精度换算src/services/transfer.js转账、Gas 估算src/composables/useWallet.js账户、链 ID、连接状态src/composables/useBalance.js页面绑定的余额状态src/views/Dashboard.vue业务页面只调用组合式函数services 层里的函数全部接收 web3 实例作为第一个参数后续测试时直接传入 mock 对象就能模拟请求。composables 层只维护响应式状态不直接 new Web3。跑一段时间后你会发现新增一个合约只需要在 services 下加一个文件组件不需要感知底层连接细节。这个前端架构决定了后面几个章节的代码组织方式。3. 搭出最小可运行环境Vue3 项目初始化与第一个链上调用这一章的目标是让你在 10 分钟内跑通一个最小的链上调用。不聊复杂的业务只看三件事工程建起来、依赖装上、浏览器里能读到区块高度。这一步验证的是最底层的 Provider 通不通通了你才有信心继续做钱包和转账。3.1 初始化 Vue3 工程并安装 web3 依赖先用脚手架建一个 Vue3 项目。下面命令里的 dapp-demo 是项目名你可以按自己的业务改。# 使用 Vite 创建 Vue3 项目项目名设为 dapp-demo npm create vitelatest dapp-demo -- --template vue # 进入项目目录并安装基础依赖 cd dapp-demo npm install # 安装 Web3.js。当前主版本默认导出类导入方式要看配套文档 npm install web3脚手架命令里的 --template vue 表示生成 Vue3 模板如果你用 pnpm 或 yarn把 npm 换成对应的包管理器即可。web3 这个包体积偏大本地开发不需要额外配置构建时留意一下产物大小就行。装完后执行 npm run dev先确认页面能正常打开我们再往下走。3.2 最小连接读取最新区块高度作为连通性验证创建一个服务层文件把 Web3 实例的创建和调用封装起来。这是全项目所有链上请求的地基。// src/services/web3.js import Web3 from web3; // 创建连接指定节点的 Web3 实例 // rpcUrl 支持 HTTP 和 WebSocket 两种形式 export function createWeb3(rpcUrl) { if (!rpcUrl) { throw new Error(请先配置 RPC 地址); } return new Web3(rpcUrl); } // 读取最新区块高度作为连通性验证 export async function getLatestBlockNumber(web3) { try { // getBlockNumber 返回的是十进制数字类型 const height await web3.eth.getBlockNumber(); return height; } catch (err) { console.error(获取区块高度失败, err); throw err; } }这里的 createWeb3 接受一个 RPC 地址本地开发链默认暴露在 http://127.0.0.1:8545这个接口是业内统一的 JSON-RPC 格式。getBlockNumber 返回的是数字你可以直接用 toString() 转成字符串显示到页面上。有一个细节函数接收的 web3 实例要从外部传入而不是在函数内部 new这样测试时才能把 mock 对象传进来。如果你传的是 WebSocket 地址Web3.js 会自动启用订阅模式事件监听依赖这类连接如果只是查数据、发交易HTTP 地址就够了。最小验证跑通后你会看到区块高度一直往上跳说明节点连接、请求发送、结果解析整条链路已经通了。3.3 环境参数表本地节点、测试网与浏览器的配置差异开发过程中你会接触三类环境参数差别容易让人犯迷糊。我整理了一张对照表环境RPC 地址示例链 ID浏览器访问方式本地开发链http://127.0.0.1:8545通常为 1337浏览器直接访问可免钱包公共测试网节点服务商提供的 HTTPS 地址按测试网分配需浏览器钱包扩展绑定该网本地开发链适合日常调试启动快、出块快、随便刷余额。但你在浏览器里做转账测试时还是要让浏览器钱包扩展切到同一个链上不然账户和链对不上。公共测试网的流水是真实存在的能验证完整交易链路但要用从水龙头申请的小额测试币注意区分主网和测试网资产别搞混。提示本地开发链的 RPC 地址只用于开发环境生产环境必须换成 HTTPS 节点地址否则部署后会被浏览器安全策略直接拦掉。4. 手写钱包连接与转账账户授权、余额查询和交易回执环境通了接下来是 DApp 的核心交互连接钱包、读余额、发交易。这三段代码几乎是所有业务型 DApp 的通用骨架后面接任何合约都是在这个骨架上加方法而已。4.1 连接浏览器钱包eth_requestAccounts 与账户状态同步浏览器钱包扩展通过 window.ethereum 注入页面这是通用的浏览器接口。你不需要关心钱包品牌只需要按这个标准接口调。// src/composables/useWallet.js import { ref } from vue; const account ref(); const chainId ref(); const isConnected ref(false); export function useWallet() { async function connectWallet() { // 钱包扩展通过 window.ethereum 注入这是标准接口 if (!window.ethereum) { throw new Error(未检测到浏览器钱包扩展请先安装并解锁); } // request 方法统一封装钱包连接与账户授权 const accounts await window.ethereum.request({ method: eth_requestAccounts }); // 取第一个账户作为当前账户 account.value accounts[0]; // 链 ID 是十六进制字符串比如 0x1 表示主网 chainId.value await window.ethereum.request({ method: eth_chainId }); isConnected.value true; // 用户切换账户时同步更新页面状态 window.ethereum.on(accountsChanged, (accounts) { account.value accounts[0] || ; }); // 用户切换网络时重新读取链 ID window.ethereum.on(chainChanged, (id) { chainId.value id; }); return account.value; } return { account, chainId, isConnected, connectWallet }; }eth_requestAccounts 的作用是向用户发起授权弹窗用户确认后返回账户数组。你用 accounts[0] 作为当前账户浏览器地址栏里能看到用户当前选中的钱包账户就是这个值。chainId 是十六进制字符串如果要对比业务链记得先 parseInt 转成数字再比较。accountsChanged 和 chainChanged 这两个事件是钱包扩展主动推给页面的。它们解决了 DApp 最头疼的状态同步问题用户在你页面开着的时候切换了账户或者切了网络前端必须立即感知并重新拉数据否则页面显示的余额和用户钱包里的实际情况就不一致了。事件回调里只需要更新响应式变量Vue3 会自动重渲染。4.2 读取账户余额与 ERC-20 代币余额原生代币余额最简单一个方法就搞定// 原生代币余额返回 wei最小单位 const balanceWei await web3.eth.getBalance(account.value); // 换算成主单位注意要用字符串运算浮点数会丢精度 const balance web3.utils.fromWei(balanceWei, ether);getBalance 返回的是 wei 单位的字符串展示之前用 fromWei 换算成主单位。如果是 ERC-20 代币就得通过合约调用读取但是不需要完整 ABI只声明要用的方法即可// src/services/erc20.js // 用最小 ABI 描述要调用的方法 const minAbi [ { constant: true, inputs: [ { name: owner, type: address } ], name: balanceOf, outputs: [ { name: , type: uint256 } ], type: function } ]; export async function getTokenBalance(web3, tokenAddress, holderAddress) { // 基于地址和 ABI 构造合约对象 const contract new web3.eth.Contract(minAbi, tokenAddress); // call() 是只读调用不会产生链上交易 const balance await contract.methods.balanceOf(holderAddress).call(); return balance; }ABI 里的字段一个都不能写错name、type、inputs、outputs 都是合约方法的标准描述。业务开发时你会发现很多代币还带 decimals 字段换算余额时要除以 10 的 decimals 次方而不是写死 18。call() 方法只做链上读取、不收 Gas失败也不会扣用户的费用所以可以放心在页面加载时调用。4.3 发起转账估算 Gas、提交交易和解析回执转账是 DApp 里风险最高的操作因为它真的会花掉用户的资产。我的习惯是先把 Gas 估算清楚再提交避免交易卡住。// src/services/transfer.js export async function sendPayment(web3, fromAddress, toAddress, amountWei) { // 先估算 Gas避免提交后因为 Gas 不足失败 const gas await web3.eth.estimateGas({ from: fromAddress, to: toAddress, value: amountWei }); // 获取当前节点的建议 Gas 价格 const gasPrice await web3.eth.getGasPrice(); // 提交交易返回完整交易回执 const tx await web3.eth.sendTransaction({ from: fromAddress, to: toAddress, value: amountWei, gas: gas, gasPrice: gasPrice }); // 回执里的字段是判断成败的关键依据 console.log(交易哈希, tx.transactionHash); console.log(消耗 Gas, tx.gasUsed); return tx; }estimateGas 返回的是完成这笔交易所需的最小 Gas直接用它提交大概率成功但不建议完全不加余量因为节点状态可能在估算和提交之间发生变化。gasPrice 用 getGasPrice 动态获取别写死一个固定值否则网络拥堵时交易会在池子里等很久。sendTransaction 返回的 tx 是完整回执不是只有哈希。这里最容易误解的点是拿到 transactionHash 不等于交易成功。哈希只是代表交易被打包进了内存池真正成功要看回执里的 status 字段是否为 true。所以业务代码里必须等 sendTransaction 完全 resolve 后再展示“转账成功”中间状态统一显示为“处理中”。另外金额参数一定要用 web3.utils.toWei 换算成字符串别直接用浮点数做乘法精度丢了你连错在哪都查不出来。5. 避坑DApp 联调时最容易翻车的 5 个场景DApp 联调期的坑集中在浏览器安全策略、钱包注入时序、链 ID 匹配和 Gas 策略这几个地方。这些坑每个都能耗掉半天我把高频的 5 个整理成“现象、原因、解决”三条式排查记录你直接对照着查。5.1 页面一直提示未检测到钱包扩展现象页面反复提示“未检测到浏览器钱包扩展”按钮点击没有反应刷新也没用。原因脚本在钱包扩展注入 window.ethereum 之前就执行了。常见于开发服务器注入时机偏早、用户使用无痕模式时扩展被禁用或者浏览器刚启动时扩展脚本还没加载完。解决把钱包检测放到异步初始化函数里在页面挂载后执行检测不到时提示用户刷新一次页面。也可以监听钱包扩展主动广播的初始化事件等它发出信号后再初始化连接逻辑。不要只在全局作用域里读一次 window.ethereum把它当成动态注入的变量处理。5.2 本地节点能连上但请求被浏览器拦截现象本地开发链数据正常前端请求却报混合内容错误或跨域拦截拿不到任何返回。原因浏览器安全策略拦截了 HTTPS 页面访问 HTTP 本地地址。开发服务器是 http 时一般不拦页面一旦部署到 HTTPShttp://127.0.0.1:8545 就会被当成混合内容拦掉。解决开发阶段用 http://localhost 访问页面上线阶段改用公共测试网的 HTTPS 节点地址。生产环境暴露本地 RPC 不仅是安全问题浏览器这关也过不了。如果一定要用本地开发链就得给开发服务器配 HTTPS 证书联调阶段没必要折腾这个。5.3 切换链之后签名失败现象用户手动切到另一个链前端还停在旧链的数据上发交易时报错或者签名成功但节点拒绝打包。原因交易对象里没有显式声明当前链 ID钱包按用户活动的链去签名而前端的业务数据还停留在旧链上。解决发交易前先读一次 window.ethereum 的当前链 ID和业务要求的链 ID 比对不一致就先提示用户切换网络不要直接发交易。这个校验应该做成公共函数在所有发交易、写合约的入口处调用。曾经团队里把这类跨链问题当成玄学排查了很久最后发现就是少了一次链 ID 校验。5.4 交易发送后长时间 pending现象sendTransaction 一直不回调或者拿到了交易哈希但区块迟迟不确认用户反复点按钮也没有变化。原因Gas 估算结果偏低或者前端写的 gasPrice 低于节点当前接受值导致交易长期排在内存池里不被优先处理。另一个常见原因是小账户的 nonce 冲突通常是页面里同一个账户并发发起了多笔交易。解决gasPrice 用 getGasPrice 动态获取不要写死在 estimateGas 的结果上留有适量余量防止节点状态变化导致踩线。已经 pending 的交易无法取消只能等要避免这个局面就得在发交易前做好估算和对用户账户余额的校验。前端还要防止同一账户重复点击造成的并发提交。5.5 事件监听漏数据或者重复触发现象页面监听了合约事件切换网络后数据不更新或者同一笔交易的事件被回调了多次页面统计出错。原因事件监听还绑在旧网络的 Provider 上链一换连接还留在老网络另一种情况是节点做区块重组时短时间内的临时数据被回滚再重放事件就会重复到达。解决切换网络时先调用清理方法清掉旧订阅再重新连接新链建立监听。业务侧以回执里的 status 和区块确认数为准不以前端事件回调作为最终结果。事件里的 blockNumber 要和当前高度做对比差距过大时先拉历史数据补齐别直接信任单次回调。6. 上线前验证技巧把链上调用封装成可测试的服务层最后一章落在一个长期受益的习惯上让页面的每一个链上动作都能被 mock 替换。这里提一个可复现的封装思路我经手的几个项目都沿用这个结构联调成功率提升得很明显。6.1 用注入模式把 Web3 调用隔离出 UI把链上操作收敛成一个服务对象页面只调用业务方法完全不接触 Web3 实例// src/services/chainService.js export function createChainService(web3, wallet) { return { // 读取当前区块高度 async getHeight() { return web3.eth.getBlockNumber(); }, // 读取账户原生代币余额 async getBalance(address) { return web3.eth.getBalance(address); }, // 发起转账 async transfer(to, amountWei) { return web3.eth.sendTransaction({ from: wallet.account.value, to: to, value: amountWei }); } }; }这个服务层面对页面暴露的是语义清楚的方法页面不需要知道底层走的是 Web3 还是 mock。测试时传入一个模拟对象就能在浏览器里模拟账户、余额、交易回执UI 开发可以和链上联调并行前端先按 mock 数据把页面做完链上接口就绪后再替换。6.2 发版前过一遍这些问题业务型 DApp 上线前的验证清单我每次发版都会逐项过检查项验证方式钱包未安装状态关闭钱包扩展后刷新页面确认提示文案清晰可读切换账户和链手动切两次确认页面余额和状态自动刷新Gas 失败场景把账户余额消耗到接近 0确认报错文案交易回执解析在本地开发链上转账确认 status 字段判断逻辑事件监听切换网络后重新拉取数据确认旧订阅被清理每项都值得在测试环境完整跑一遍再上线尤其是切换账户和链那一条最能暴露响应式状态没绑好的问题。把服务层封装好这些验证就不需要每次改页面代码只要确认 mock 数据和真实数据表现一致就行。我个人习惯在项目启动第一天就把 services 层建好哪怕只有一个 getBalance 也要走服务层不直接写在组件里。这样今天踩过的坑比如链 ID 不一致、Gas 估算偏低、钱包注入时序都会变成服务层里的一段防御逻辑而不是聊天记录里的一次远程救火。DApp 开发的难点不在链上而在前端状态和异步交易之间的衔接把这条线理顺区块链应用开发里最高频的坑也就自然绕开了。希望这些命令和排错顺序能帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →