当前位置: 首页 > article >正文

WEB3全栈开发——面试专业技能点P7前端与链上集成

一、Next.js技术栈

✅ 概念介绍

Next.js 是一个基于 React服务端渲染(SSR)与静态网站生成(SSG) 框架,由 Vercel 开发。它简化了构建生产级 React 应用的过程,并内置了很多特性:

  • ✅ 文件系统路由(Pages / App Router)

  • ✅ 服务端渲染(SSR)、静态生成(SSG)、增量静态生成(ISR)

  • ✅ API 路由(后端接口)

  • ✅ 图片优化(next/image

  • ✅ CSS / Sass / Tailwind 等样式方案支持

  • ✅ 支持部署到 Vercel、Docker、任意 Node 环境

  • ✅ 新版 App Router 支持 React Server Components(RSC)


✅ 示例代码与讲解(基于 Next.js 13+ App Router)

📁 项目结构(部分)

/app/page.tsx               # 首页/about/page.tsx         # /about 页面/api/hello/route.ts     # API 路由
/components/Header.tsx
/public
/styles/globals.css
next.config.js
tsconfig.json

✅ 1. 页面组件(服务端渲染)

// app/page.tsx
export default function HomePage() {return <h1>Welcome to Web3 DApp</h1>;
}

访问 http://localhost:3000 即可看到页面。


✅ 2. 动态路由页面

// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {return <h2>Post: {params.slug}</h2>;
}

访问 /blog/hello-nextjs 会显示 Post: hello-nextjs


✅ 3. API 路由(可连接 Web3 后端)

// app/api/hello/route.ts
export async function GET() {return new Response(JSON.stringify({ msg: 'Hello API' }), {status: 200,headers: { 'Content-Type': 'application/json' },});
}

访问 /api/hello 可返回 JSON。


✅ 4. 组件拆分与复用

// components/Header.tsx
export default function Header() {return <header className="text-xl">My DApp</header>;
}

在页面中导入:

import Header from '@/components/Header';export default function HomePage() {return (<><Header /><main>Hello</main></>);
}

✅ 5. 链上交互集成(如连接 MetaMask)

'use client';
import { useEffect, useState } from 'react';export default function WalletConnect() {const [account, setAccount] = useState('');useEffect(() => {if (window.ethereum) {window.ethereum.request({ method: 'eth_requestAccounts' }).then((acc) => {setAccount(acc[0]);});}}, []);return <div>Connected Wallet: {account}</div>;
}

✅ 常见应用场景

场景使用说明
Web3 DApp 前端搭配 Ethers.js / wagmi 等连接钱包
博客/文档系统静态生成或服务端渲染
SSR SEO 优化提高搜索引擎收录
API 接口 + 前端集成一体化开发体验


✅ 总结

特性说明
兼容性与 React 完全兼容,TypeScript 原生支持
开发效率文件即路由,快速构建页面与接口
生产能力SSR/SSG/ISR、图片优化、代码分割等
Web3 友好性支持构建钱包连接、NFT 展示、区块数据查询的前端

二、Vue 技术栈

✅ 概念介绍

Vue.js 是一套用于构建用户界面的 渐进式 JavaScript 框架,其核心关注视图层,同时易于集成第三方库或现有项目。Vue 提供响应式数据绑定、组件化开发、指令系统等核心特性。

常见的 Vue 技术栈包括:

层级技术说明
框架核心Vue 2 / Vue 3前端 MVVM 框架
状态管理Vuex / Pinia全局状态管理
路由系统Vue Router单页应用(SPA)路由管理
组件框架Element Plus / VantPC / 移动端 UI 组件库
网络请求Axios数据交互
构建工具Vite / Webpack开发构建工具,Vite 为新一代构建方案
类型系统TypeScriptVue 3 原生支持
样式工具Sass / Tailwind CSS样式增强工具


✅ 示例代码与讲解

📁 项目结构(Vue 3 + Vite)

/src/componentsHelloWorld.vue/viewsHome.vueApp.vuemain.tsrouter/index.tsstore/index.ts

✅ 1. 创建组件并绑定数据

<!-- HelloWorld.vue -->
<template><div><h1>{{ title }}</h1><button @click="count++">Count: {{ count }}</button></div>
</template><script setup lang="ts">
import { ref } from 'vue'const title = 'Welcome to Web3 DApp'
const count = ref(0)
</script>

✅ 2. 使用 Vue Router 创建页面路由

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'const routes = [{ path: '/', component: Home }]export const router = createRouter({history: createWebHistory(),routes
})
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'createApp(App).use(router).mount('#app')

✅ 3. Vuex or Pinia 状态管理示例(Pinia)

// store/useCounter.ts
import { defineStore } from 'pinia'export const useCounter = defineStore('counter', {state: () => ({ count: 0 }),actions: {increment() {this.count++}}
})
<script setup lang="ts">
import { useCounter } from '@/store/useCounter'
const counter = useCounter()
</script><template><button @click="counter.increment()">Count: {{ counter.count }}</button>
</template>

✅ 4. 发送 API 请求(Axios)

// api/user.ts
import axios from 'axios'export const getUser = () => {return axios.get('/api/user')
}
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getUser } from '@/api/user'const user = ref(null)
onMounted(async () => {const res = await getUser()user.value = res.data
})
</script>

✅ Vue 技术栈常用于:

场景技术组合示例
管理后台系统Vue + Element Plus + Vuex/Pinia + Axios
移动端小程序Vue + Vant + Pinia + Vite
Web3 DApp 前端Vue + Ethers.js + Web3Modal + Tailwind CSS
可视化看板平台Vue + Echarts + WebSocket + Sass


✅ 总结

优点说明
学习曲线平缓易于入门,渐进式架构
生态完善官方路由、状态管理、CLI 工具
可组合性强支持组合式 API,利于组织逻辑
与 Web3 集成能力强搭配 ethers/web3js 可快速实现钱包连接、链上交互等功能


如果你想我给出 Web3 场景中 Vue 技术栈的实战 DApp 示例页面,也可以告诉我。

三、Next.js 和 Vue 的 SSR(服务器端渲染)渲染

✅ 概念介绍(SSR 是什么?)

SSR(Server-Side Rendering)服务器端渲染 是一种将页面在服务器端生成 HTML 的方式,服务器将完整 HTML 页面返回给浏览器。

相比 CSR(客户端渲染):

项目客户端渲染 CSR服务端渲染 SSR
初次加载速度快(首屏加载快)
SEO(搜索引擎优化)好(有完整 HTML)
动态交互能力SSR 页面需 hydrate(再激活)
实现复杂度稍高,需要服务端运行环境支持


🚀 Next.js 的 SSR 渲染

Next.js 默认支持 SSR(基于 React),你可以通过 getServerSideProps 来开启服务器端渲染页面。


✅ 示例代码:SSR 页面

// pages/ssr-page.tsx
import React from 'react'interface Props {time: string
}// getServerSideProps 会在每次请求时运行
export async function getServerSideProps() {return {props: {time: new Date().toISOString() // 服务端生成时间}}
}export default function SSRPage({ time }: Props) {return (<div><h1>🚀 SSR 渲染页面</h1><p>服务器生成时间:{time}</p></div>)
}
🔍 注释说明:
  • getServerSideProps:每次请求此页面时都会执行,返回的 props 会传给页面组件。

  • 页面在服务端生成 HTML,然后发送给浏览器,因此服务端返回的时间是准确的。

  • SEO 友好,因为浏览器拿到的是完整 HTML 内容。


🌿 Vue + Nuxt 的 SSR 渲染

Vue 自身不是 SSR 框架,但 Nuxt.js 是官方推荐的 SSR 框架(对 Vue 的增强封装)。


✅ 示例代码:Nuxt 页面 SSR 渲染

<!-- pages/ssr.vue -->
<template><div><h1>🚀 SSR 页面</h1><p>服务器生成时间:{{ time }}</p></div>
</template><script setup>
defineProps(['time'])
</script><script>
// 运行在服务端,每次请求都会执行
export async function asyncData() {return {time: new Date().toISOString()}
}
</script>
🔍 注释说明:
  • asyncData:Nuxt 特有的生命周期函数,只能在服务端运行,用于数据预取。

  • 页面请求到达时,服务器会调用 asyncData,填充数据并生成 HTML。

  • 响应发送给客户端时已带数据,无需客户端再请求。


🧩 对比总结:Next.js vs Nuxt.js SSR

对比项Next.jsNuxt.js
框架基础ReactVue
数据获取 APIgetServerSidePropsasyncData
页面文件夹pages/pages/
默认渲染方式SSR 默认可选 SSR/静态
类型支持TypeScript 完美支持Vue 3 中 Composition API 支持


📌 使用 SSR 的注意事项

  1. SSR 页面 每次请求都执行服务端代码,对服务器性能有要求。

  2. 需考虑 数据获取延迟页面缓存(可结合 Redis 缓存或 ISR)。

  3. 页面必须具备 hydrate 机制:HTML 渲染完成后,还要激活前端逻辑(事件绑定、状态管理等)。


如你有 Web3 DApp 页面 SSR 的需求,我可以给你示例如何通过 SSR 预渲染钱包信息、合约状态等内容。是否需要?

四、Next.js 和 Vue 的组件通信

✅ 概念介绍

组件通信 是指组件之间如何传递数据、事件或共享状态。根据组件关系(父子、兄弟、跨层级),通信方式也不同。

场景通信方式(React / Vue)
父子组件props(传值) + 事件回调
兄弟组件状态提升 / 全局状态管理(Context、Pinia 等)
跨层级组件Context API(React)/ Provide-Inject(Vue)


1️⃣ Next.js(基于 React)组件通信


示例一:父子组件通信(Props + 回调)

// 父组件 Parent.tsx
import React, { useState } from 'react'
import Child from './Child'export default function Parent() {const [message, setMessage] = useState('Hello from parent!')const handleChildClick = () => {alert('👦 子组件点击了按钮')}return (<div><h2>👨 父组件</h2><p>发送给子组件:{message}</p><Child msg={message} onChildClick={handleChildClick} /></div>)
}
// 子组件 Child.tsx
export default function Child({msg,onChildClick
}: {msg: stringonChildClick: () => void
}) {return (<div><h3>👶 子组件</h3><p>收到来自父组件的信息:{msg}</p><button onClick={onChildClick}>告诉父组件我点击了</button></div>)
}

🔍 注释说明

  • 父组件通过 props 向子组件传值。

  • 子组件通过 props 中的回调函数,通知父组件事件(如按钮点击)。


示例二:兄弟组件通信(状态提升)

// Page.tsx
import React, { useState } from 'react'
import BrotherA from './BrotherA'
import BrotherB from './BrotherB'export default function Page() {const [count, setCount] = useState(0)return (<><BrotherA count={count} /><BrotherB setCount={setCount} /></>)
}
// BrotherA.tsx
export default function BrotherA({ count }: { count: number }) {return <p>我是 A 组件,我收到的值是:{count}</p>
}
// BrotherB.tsx
export default function BrotherB({setCount
}: {setCount: (n: number) => void
}) {return (<button onClick={() => setCount(prev => prev + 1)}>我是 B 组件,点击我修改 count</button>)
}

2️⃣ Vue 组件通信


示例一:父子组件通信(Props + $emit)

<!-- Parent.vue -->
<template><div><h2>👨 父组件</h2><Child :msg="message" @child-click="handleClick" /></div>
</template><script setup>
import Child from './Child.vue'
import { ref } from 'vue'const message = ref('Hello from parent!')function handleClick() {alert('👦 子组件点击了按钮')
}
</script>
<!-- Child.vue -->
<template><div><h3>👶 子组件</h3><p>收到来自父组件的信息:{{ msg }}</p><button @click="$emit('child-click')">告诉父组件我点击了</button></div>
</template><script setup>
defineProps(['msg'])
defineEmits(['child-click'])
</script>

示例二:Provide / Inject(跨层级通信)

<!-- App.vue -->
<template><Parent />
</template><script setup>
import { provide } from 'vue'
import Parent from './Parent.vue'provide('theme', 'dark') // 提供 theme 给深层组件
</script>
<!-- DeepChild.vue -->
<template><p>🌙 当前主题:{{ theme }}</p>
</template><script setup>
import { inject } from 'vue'
const theme = inject('theme') // 跨组件获取
</script>

🔍 总结对比

对比项Next.js(React)Vue 3
父子通信props + 回调props + $emit
跨层级Context APIprovide/inject
全局状态Context, Redux, Zustand 等Pinia, Vuex(旧)
类型支持TypeScript 一流Composition API 强大
响应式系统手动 useStateuseEffect自动响应式 ref, watch, computed


如你需要基于 Web3 钱包连接或合约状态共享的组件通信设计,也可以继续告诉我,我能给出特定案例。

五、Next.js 和 Vue 的接口封装

✅ 概念介绍

接口封装 是指将网络请求(如 API 调用)统一管理,使得组件使用时只需调用方法,而无需关注底层请求细节。

好处包括:

  • 提高复用性(比如统一请求逻辑)

  • 方便维护和扩展

  • 易于切换后端地址、设置 token、错误处理


1️⃣ Next.js 接口封装(基于 Axios)


示例代码:封装 API 请求(/lib/api.ts)

// /lib/api.ts
import axios from 'axios'const instance = axios.create({baseURL: '/api', // Next.js 本地 API 或代理地址timeout: 5000
})// 请求拦截器
instance.interceptors.request.use(config => {// 你可以在这里添加 tokenconst token = localStorage.getItem('token')if (token) config.headers.Authorization = `Bearer ${token}`return config},error => Promise.reject(error)
)// 响应拦截器
instance.interceptors.response.use(res => res.data,err => {console.error('❌ API 请求出错:', err)return Promise.reject(err)}
)// 封装用户请求
export const getUserInfo = () => instance.get('/user/info')
export const login = (data: { username: string; password: string }) =>instance.post('/auth/login', data)

示例代码:调用接口(组件中)

'use client'
import { useEffect, useState } from 'react'
import { getUserInfo } from '@/lib/api'export default function UserProfile() {const [user, setUser] = useState<any>(null)useEffect(() => {getUserInfo().then(data => setUser(data))}, [])return (<div><h2>用户信息</h2><pre>{JSON.stringify(user, null, 2)}</pre></div>)
}

🔍 注释说明

  • lib/api.ts 集中封装所有后端请求。

  • 使用 axios.create 创建实例,统一设置 baseURL 和拦截器。

  • 使用组件中调用封装方法,避免直接写 axios.get(...)


2️⃣ Vue 接口封装(基于 Axios + Composition API)


示例代码:封装 API 请求(/src/utils/http.ts)

// /src/utils/http.ts
import axios from 'axios'const http = axios.create({baseURL: '/api',timeout: 5000
})// 请求拦截器
http.interceptors.request.use(config => {const token = localStorage.getItem('token')if (token) config.headers.Authorization = `Bearer ${token}`return config
})// 响应拦截器
http.interceptors.response.use(res => res.data,err => {console.error('❌ 请求出错:', err)return Promise.reject(err)}
)export default http

示例代码:定义 API 模块(/src/api/user.ts)

// /src/api/user.ts
import http from '@/utils/http'export const getUserInfo = () => http.get('/user/info')
export const login = (data: { username: string; password: string }) =>http.post('/auth/login', data)

示例代码:Vue 组件中调用(User.vue)

<template><div><h2>用户信息</h2><pre>{{ user }}</pre></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import { getUserInfo } from '@/api/user'const user = ref(null)onMounted(async () => {user.value = await getUserInfo()
})
</script>

🔍 对比总结

项目Next.jsVue
通用工具目录/lib/api.ts/src/api/*.ts + /src/utils/http.ts
常用工具Axios、fetchAxios
状态管理useState / useSWRref / reactive
拦截器支持(用于 token 注入 / 错误处理)支持


如需拓展支持 Web3 钱包签名、Token 插入、GraphQL 封装、或者与合约交互的 API 包装,请继续告诉我,我可补充更专业的封装案例。

六、Next.js 和 Vue 实现钱包连接(MetaMask)

✅ 概念介绍

在 DApp 中,连接钱包(如 MetaMask)是与区块链交互的第一步。
主要过程如下:

  1. 检测是否安装了 MetaMask

  2. 请求用户连接账户(授权)

  3. 获取当前账户地址

  4. (可选)监听账户变化或网络变化

  5. 用于后续签名、交易、合约交互

我们将用 Next.js(React)Vue 3 各写一个示例。


1️⃣ Next.js 连接 MetaMask 示例

📁 技术栈

  • Next.js 13+(App Router 或 Page Router)

  • 使用 window.ethereum 对象(MetaMask 提供)


示例代码:组件中连接钱包

'use client' // 如果是 App Router
import { useEffect, useState } from 'react'export default function ConnectWallet() {const [account, setAccount] = useState<string | null>(null)// 连接钱包函数const connectWallet = async () => {if (typeof window.ethereum === 'undefined') {alert('请先安装 MetaMask 插件')return}try {// 请求用户授权连接const accounts = await window.ethereum.request({method: 'eth_requestAccounts'})setAccount(accounts[0]) // 设置当前连接账户} catch (err) {console.error('用户拒绝连接', err)}}return (<div className="p-4"><button onClick={connectWallet} className="px-4 py-2 bg-blue-500 text-white rounded">连接 MetaMask 钱包</button>{account && <p className="mt-2">当前账户: {account}</p>}</div>)
}

🔍 注释说明

  • window.ethereum 是 MetaMask 插件注入的全局对象。

  • 使用 eth_requestAccounts 方法让用户授权钱包访问。

  • setAccount 更新状态用于页面展示当前地址。


2️⃣ Vue 3 + Composition API 实现连接 MetaMask


示例代码(ConnectWallet.vue

<template><div><button @click="connectWallet">连接 MetaMask 钱包</button><p v-if="account">当前账户:{{ account }}</p></div>
</template><script setup>
import { ref } from 'vue'const account = ref('')// 连接钱包函数
const connectWallet = async () => {if (typeof window.ethereum === 'undefined') {alert('请先安装 MetaMask 插件')return}try {// 请求账户连接const accounts = await window.ethereum.request({method: 'eth_requestAccounts'})account.value = accounts[0] // 设置当前连接账户} catch (error) {console.error('连接失败:', error)}
}
</script>

🔍 注释说明

  • ref 用于定义响应式状态。

  • 调用 window.ethereum.request 请求用户授权。

  • 授权成功后设置 account 用于显示。


✅ 小贴士

  • MetaMask 安装检测:用 typeof window.ethereum !== 'undefined'

  • 监听账户变化

window.ethereum.on('accountsChanged', (accounts) => {// 账户发生变化,更新状态
})
  • 监听网络变化

window.ethereum.on('chainChanged', (chainId) => {// 网络变化后建议 reload 页面window.location.reload()
})

如果你还希望实现:

  • 钱包断开连接(如 WalletConnect)

  • Ethers.js 连接 MetaMask 并进行签名或交易

  • 合约调用(如获取余额、调用函数)

可以继续告诉我,我可以逐步扩展讲解。

七、Next.js 和 Vue 实现 NFT 列表展示

✅ 概念介绍

NFT(Non-Fungible Token,非同质化代币)通常基于 ERC-721 或 ERC-1155 标准,代表独特的数字资产。DApp 需要展示用户或某个地址持有的 NFT 列表,常见流程:

  1. 连接钱包,获取用户地址

  2. 调用区块链或第三方API(如 OpenSea API、Alchemy NFT API、Moralis 等)获取该地址持有的NFT

  3. 解析NFT数据,包括 Token ID、名称、图片 URL、描述等

  4. 在前端渲染 NFT 列表

下面给出 Next.jsVue 3 简单示例,演示如何调用公共API获取 NFT 并展示。


1️⃣ Next.js 实现 NFT 列表展示示例


技术点:

  • React + Next.js

  • 使用 fetch 调用第三方API(这里用 Alchemy NFT API 示例)

  • 处理异步数据加载

  • 展示图片和文字信息


示例代码

'use client'
import { useEffect, useState } from 'react'interface NFT {tokenId: stringtitle: stringimage: string
}export default function NFTList() {const [nfts, setNfts] = useState<NFT[]>([])const [address, setAddress] = useState('0x8ba1f109551bD432803012645Ac136ddd64DBA72') // 示例地址const [loading, setLoading] = useState(false)useEffect(() => {if (!address) returnconst fetchNFTs = async () => {setLoading(true)try {// Alchemy NFT API - 替换为自己的API keyconst apiKey = 'demo' // 请换成自己的API KEYconst url = `https://eth-mainnet.alchemyapi.io/v2/${apiKey}/getNFTs/?owner=${address}`const res = await fetch(url)const data = await res.json()const nftList = data.ownedNfts.map((item: any) => ({tokenId: item.id.tokenId,title: item.title || item.contract.address,image: item.media[0]?.gateway || '/default-nft.png',}))setNfts(nftList)} catch (error) {console.error('获取NFT失败:', error)}setLoading(false)}fetchNFTs()}, [address])return (<div><h1 className="text-xl font-bold mb-4">NFT 列表展示</h1>{loading && <p>加载中...</p>}{!loading && nfts.length === 0 && <p>未找到NFT</p>}<ul className="grid grid-cols-3 gap-4">{nfts.map((nft) => (<li key={nft.tokenId} className="border p-2 rounded"><img src={nft.image} alt={nft.title} className="w-full h-48 object-cover mb-2" /><h3 className="text-sm font-medium">{nft.title}</h3><p>Token ID: {parseInt(nft.tokenId, 16)}</p></li>))}</ul></div>)
}

代码详解

  • 地址(address):NFT 持有者地址,默认示例地址可替换

  • 调用 Alchemy API:根据 owner 查询 NFT 持有信息

  • 解析返回数据:提取 NFT tokenId、名称和封面图片 URL

  • 渲染列表:图片+名称+Token ID

  • loading 状态管理:加载时显示提示


2️⃣ Vue 3 实现 NFT 列表展示示例


技术点:

  • Vue 3 + Composition API

  • 使用 fetch 异步调用第三方API

  • 响应式数据绑定

  • 基础样式布局


示例代码

<template><div><h1>NFT 列表展示</h1><input v-model="address" placeholder="输入钱包地址" class="mb-2 p-1 border" /><button @click="fetchNFTs" class="mb-4 px-3 py-1 bg-blue-500 text-white rounded">查询NFT</button><div v-if="loading">加载中...</div><div v-else-if="nfts.length === 0">未找到NFT</div><ul class="grid grid-cols-3 gap-4"><li v-for="nft in nfts" :key="nft.tokenId" class="border p-2 rounded"><img :src="nft.image" :alt="nft.title" class="w-full h-48 object-cover mb-2" /><h3 class="text-sm font-medium">{{ nft.title }}</h3><p>Token ID: {{ parseInt(nft.tokenId, 16) }}</p></li></ul></div>
</template><script setup>
import { ref } from 'vue'const address = ref('0x8ba1f109551bD432803012645Ac136ddd64DBA72') // 示例地址
const nfts = ref([])
const loading = ref(false)const fetchNFTs = async () => {if (!address.value) {alert('请输入钱包地址')return}loading.value = truetry {const apiKey = 'demo' // 替换为你的Alchemy API Keyconst url = `https://eth-mainnet.alchemyapi.io/v2/${apiKey}/getNFTs/?owner=${address.value}`const res = await fetch(url)const data = await res.json()nfts.value = data.ownedNfts.map((item) => ({tokenId: item.id.tokenId,title: item.title || item.contract.address,image: item.media[0]?.gateway || '/default-nft.png',}))} catch (error) {console.error('获取NFT失败:', error)alert('获取NFT失败')} finally {loading.value = false}
}
</script><style scoped>
.grid {display: grid;grid-template-columns: repeat(3, 1fr);gap: 1rem;
}
.border {border: 1px solid #ddd;border-radius: 6px;
}
.object-cover {object-fit: cover;
}
</style>

代码详解

  • 响应式数据 address 用于绑定用户输入的钱包地址

  • fetchNFTs 函数调用 Alchemy NFT API,处理数据赋值给 nfts

  • 使用 v-for 渲染 NFT 列表,显示图片、标题和 Token ID

  • 通过 loading 控制加载状态展示


其他说明

  • Alchemy API 是以太坊生态常用的 NFT 查询服务,你需要注册获取自己的 API Key

  • 也可以用其他服务如 Moralis、OpenSea API,但可能需要注册和鉴权

  • Token ID 通常是十六进制,显示时转成十进制更易读

  • 图片可能存在跨域或加载失败,建议设置默认图备用


如果你想要我帮你写完整的合约交互方式(用 Web3.js 或 Ethers.js 从链上直接查询NFT),或者集成钱包连接 + NFT 列表展示,我也可以帮你。

八、Next.js 和 Vue 实现合约事件监听

✅ 概念介绍

智能合约在链上执行时会产生事件(Event),事件是区块链中重要的日志机制,通常用于通知前端某些链上状态变化,比如代币转账、NFT铸造等。

前端监听合约事件,能实现实时响应链上变化,提升用户体验。


合约事件监听的关键点:

  • 通过合约 ABI 和地址,创建合约实例

  • 使用 Web3.jsEthers.js 监听特定事件

  • 处理事件回调,更新 UI 状态


1️⃣ Next.js 实现合约事件监听示例(使用 Ethers.js)


'use client'import { useEffect, useState } from 'react'
import { ethers } from 'ethers'// 示例合约ABI,只包含事件部分
const abi = ["event Transfer(address indexed from, address indexed to, uint256 value)"
]// 合约地址(示例地址,需替换成真实合约地址)
const contractAddress = "0xYourContractAddressHere"export default function EventListener() {const [events, setEvents] = useState<any[]>([])useEffect(() => {// 检查浏览器是否支持以太坊钱包(MetaMask等)if (!window.ethereum) {alert('请安装 MetaMask 钱包')return}// 创建 Ethers provider 和合约实例const provider = new ethers.providers.Web3Provider(window.ethereum)const contract = new ethers.Contract(contractAddress, abi, provider)// 定义事件处理函数const onTransfer = (from: string, to: string, value: ethers.BigNumber, event: any) => {console.log('Transfer事件触发:', { from, to, value: value.toString(), event })setEvents((prev) => [...prev,{ from, to, value: value.toString(), txHash: event.transactionHash }])}// 监听 Transfer 事件contract.on("Transfer", onTransfer)// 组件卸载时移除事件监听,防止内存泄漏return () => {contract.off("Transfer", onTransfer)}}, [])return (<div><h2>合约事件监听 — Transfer 事件</h2><ul>{events.map((e, i) => (<li key={i}>From: {e.from} <br />To: {e.to} <br />Value: {e.value} <br />TxHash: <a href={`https://etherscan.io/tx/${e.txHash}`} target="_blank" rel="noreferrer">{e.txHash}</a></li>))}</ul></div>)
}

代码详解

  • ethers.providers.Web3Provider(window.ethereum):通过 MetaMask 提供的 provider 连接以太坊

  • new ethers.Contract(...):用合约地址和 ABI 创建合约实例

  • contract.on("Transfer", callback):监听 Transfer 事件,事件参数和原始事件对象都会传入

  • 监听到事件后,将事件数据加入状态数组,页面实时渲染

  • 组件卸载时用 contract.off 移除监听,防止多次绑定导致重复事件


2️⃣ Vue 3 实现合约事件监听示例(使用 Ethers.js)


<template><div><h2>合约事件监听 — Transfer 事件</h2><ul><li v-for="(e, index) in events" :key="index">From: {{ e.from }} <br />To: {{ e.to }} <br />Value: {{ e.value }} <br />TxHash: <a :href="`https://etherscan.io/tx/${e.txHash}`" target="_blank" rel="noreferrer">{{ e.txHash }}</a></li></ul></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ethers } from 'ethers'const events = ref([])// 示例合约ABI,只包含Transfer事件
const abi = ["event Transfer(address indexed from, address indexed to, uint256 value)"
]// 合约地址(示例,替换为实际地址)
const contractAddress = '0xYourContractAddressHere'let contractconst onTransfer = (from, to, value, event) => {console.log('Transfer事件:', { from, to, value: value.toString(), event })events.value.push({from,to,value: value.toString(),txHash: event.transactionHash})
}onMounted(async () => {if (!window.ethereum) {alert('请安装 MetaMask 钱包')return}const provider = new ethers.providers.Web3Provider(window.ethereum)contract = new ethers.Contract(contractAddress, abi, provider)contract.on('Transfer', onTransfer)
})onUnmounted(() => {if (contract) {contract.off('Transfer', onTransfer)}
})
</script>

代码详解

  • 使用 Vue 3 的生命周期函数 onMountedonUnmounted

  • 事件监听同样通过 contract.on 注册,事件触发时更新响应式变量 events

  • 组件卸载时通过 contract.off 解除监听,避免内存泄漏


额外说明

  • 事件监听依赖客户端钱包环境(MetaMask)

  • 监听的事件名称必须与合约ABI定义一致

  • 你可以监听多个事件,或者监听特定条件过滤事件

  • 事件回调参数由合约事件定义决定,一般带有 indexed 的参数可作为过滤条件

  • 生产环境建议用 WebSocket provider(如 Infura、Alchemy WebSocket)保持稳定连接

  • 监听合约事件适合前端实时交互场景,也可以结合后端服务做事件存储与通知


如果需要,我可以帮你写更复杂的事件监听示例,比如:

  • 使用 WebSocket provider 实现稳定监听

  • 监听多个事件并分类展示

  • 用 React Hook 或 Vue Composition API 抽象复用监听逻辑

九、Next.js 和 Vue 实现链上状态与链下数据结合

✅ 概念介绍

链上状态 指的是存储在区块链智能合约中的数据,比如账户余额、NFT 所有权、交易状态等。这些数据去中心化且不可篡改,但读取速度和成本较高。

链下数据 指的是存储在区块链外的数据库或存储系统中的数据,如用户的个人资料、评论、交易历史等丰富的扩展信息。链下数据访问速度快,存储成本低,但安全性和可信度依赖应用设计。

结合链上状态与链下数据,可以实现完整且高效的 DApp 用户体验。例如:

  • 显示某个 NFT 的链上所有权信息(链上状态)

  • 显示该 NFT 的详细描述、图片、历史交易记录(链下数据)


实现思路

  1. 链上数据读取:通过智能合约接口(如 Web3.jsEthers.js)调用读取链上状态。

  2. 链下数据查询:调用后端 API 或数据库接口,获取链下存储的结构化数据。

  3. 结合展示:前端将链上数据与链下数据合并后,渲染完整的页面信息。


1️⃣ Next.js 实现链上状态与链下数据结合示例


'use client'import { useEffect, useState } from 'react'
import { ethers } from 'ethers'// 示例合约 ABI,只包含读取 NFT 所有者函数
const abi = ["function ownerOf(uint256 tokenId) view returns (address)"
]// 合约地址示例
const contractAddress = "0xYourContractAddressHere"// 假设链下API接口,返回NFT详细信息
async function fetchOffChainData(tokenId: number) {const res = await fetch(`/api/nft/${tokenId}`)if (!res.ok) throw new Error('链下数据获取失败')return res.json()
}export default function NFTDetail({ tokenId }: { tokenId: number }) {const [owner, setOwner] = useState<string | null>(null)const [offChainData, setOffChainData] = useState<any>(null)const [error, setError] = useState<string | null>(null)useEffect(() => {async function loadData() {try {if (!window.ethereum) {setError('请安装MetaMask钱包')return}// 1. 链上状态:查询 NFT 所有者const provider = new ethers.providers.Web3Provider(window.ethereum)const contract = new ethers.Contract(contractAddress, abi, provider)const ownerAddress = await contract.ownerOf(tokenId)setOwner(ownerAddress)// 2. 链下数据:查询后端详细信息const offChain = await fetchOffChainData(tokenId)setOffChainData(offChain)} catch (err: any) {setError(err.message)}}loadData()}, [tokenId])if (error) return <div>错误:{error}</div>if (!owner || !offChainData) return <div>加载中...</div>return (<div><h1>{offChainData.name}</h1><img src={offChainData.image} alt={offChainData.name} width={300} /><p>描述: {offChainData.description}</p><p>链上拥有者: {owner}</p><p>更多链下信息: {JSON.stringify(offChainData.metadata)}</p></div>)
}

代码详解

  • 使用 ethers.Contract 读取链上 NFT 的拥有者地址 (ownerOf)

  • 通过 fetch 请求自建 API 获取 NFT 详细的链下数据(名称、图片、描述等)

  • 页面将链上与链下数据合并显示,提供完整 NFT 详情


2️⃣ Vue 3 实现链上状态与链下数据结合示例


<template><div v-if="error" class="error">错误: {{ error }}</div><div v-else-if="!owner || !offChainData">加载中...</div><div v-else><h1>{{ offChainData.name }}</h1><img :src="offChainData.image" :alt="offChainData.name" width="300" /><p>描述: {{ offChainData.description }}</p><p>链上拥有者: {{ owner }}</p><p>更多链下信息: {{ offChainData.metadata }}</p></div>
</template><script setup>
import { ref, onMounted, watch } from 'vue'
import { ethers } from 'ethers'const props = defineProps({tokenId: {type: Number,required: true}
})const owner = ref(null)
const offChainData = ref(null)
const error = ref(null)// 合约ABI
const abi = ["function ownerOf(uint256 tokenId) view returns (address)"
]
const contractAddress = '0xYourContractAddressHere'async function fetchOffChainData(tokenId) {const res = await fetch(`/api/nft/${tokenId}`)if (!res.ok) throw new Error('链下数据获取失败')return res.json()
}async function loadData(tokenId) {try {if (!window.ethereum) {error.value = '请安装MetaMask钱包'return}const provider = new ethers.providers.Web3Provider(window.ethereum)const contract = new ethers.Contract(contractAddress, abi, provider)owner.value = await contract.ownerOf(tokenId)offChainData.value = await fetchOffChainData(tokenId)} catch (e) {error.value = e.message}
}onMounted(() => {loadData(props.tokenId)
})watch(() => props.tokenId, (newId) => {loadData(newId)
})
</script>

代码详解

  • 结合 Vue 的响应式 ref,用 onMountedwatch 实现数据初始化和动态更新

  • 同样通过 ethers.Contract 读取链上所有者,通过 fetch 获取链下数据

  • 模板中将链上和链下数据合并展示


额外说明

  • 链上读取使用客户端钱包提供的 provider,数据真实且防篡改

  • 链下数据通过 API 获取,便于存储复杂、富媒体内容和扩展信息

  • 结合后端数据库,可实现完整业务逻辑(用户行为记录、评论、链上事件索引等)

  • 实际项目中可考虑缓存机制、错误处理、加载状态优化

  • 也可以用服务器端渲染(SSR)提前获取链下数据,提高 SEO 和首屏速度

相关文章:

WEB3全栈开发——面试专业技能点P7前端与链上集成

一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染&#xff08;SSR&#xff09;与静态网站生成&#xff08;SSG&#xff09; 框架&#xff0c;由 Vercel 开发。它简化了构建生产级 React 应用的过程&#xff0c;并内置了很多特性&#xff1a; ✅ 文件系…...

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…...

第八部分:阶段项目 6:构建 React 前端应用

现在&#xff0c;是时候将你学到的 React 基础知识付诸实践&#xff0c;构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段&#xff0c;你可以先使用模拟数据&#xff0c;或者如果你的后端 API&#xff08;阶段项目 5&#xff09;已经搭建好&#xff0c;可以直接连…...

6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙

Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

macOS 终端智能代理检测

&#x1f9e0; 终端智能代理检测&#xff1a;自动判断是否需要设置代理访问 GitHub 在开发中&#xff0c;使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新&#xff0c;例如&#xff1a; fatal: unable to access https://github.com/ohmyzsh/oh…...

前端高频面试题2:浏览器/计算机网络

本专栏相关链接 前端高频面试题1&#xff1a;HTML/CSS 前端高频面试题2&#xff1a;浏览器/计算机网络 前端高频面试题3&#xff1a;JavaScript 1.什么是强缓存、协商缓存&#xff1f; 强缓存&#xff1a; 当浏览器请求资源时&#xff0c;首先检查本地缓存是否命中。如果命…...

智能职业发展系统:AI驱动的职业规划平台技术解析

智能职业发展系统&#xff1a;AI驱动的职业规划平台技术解析 引言&#xff1a;数字时代的职业革命 在当今瞬息万变的就业市场中&#xff0c;传统的职业规划方法已无法满足个人和企业的需求。据统计&#xff0c;全球每年有超过2亿人面临职业转型困境&#xff0c;而企业也因此遭…...

​​企业大模型服务合规指南:深度解析备案与登记制度​​

伴随AI技术的爆炸式发展&#xff0c;尤其是大模型&#xff08;LLM&#xff09;在各行各业的深度应用和整合&#xff0c;企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者&#xff0c;还是积极拥抱AI转型的传统企业&#xff0c;在面向公众…...

高防服务器价格高原因分析

高防服务器的价格较高&#xff0c;主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因&#xff1a; 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器&#xff0c;因此…...

Unity VR/MR开发-VR开发与传统3D开发的差异

视频讲解链接&#xff1a;【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例

目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码&#xff1a;冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

Pydantic + Function Calling的结合

1、Pydantic Pydantic 是一个 Python 库&#xff0c;用于数据验证和设置管理&#xff0c;通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发&#xff08;如 FastAPI&#xff09;、配置管理和数据解析&#xff0c;核心功能包括&#xff1a; 数据验证&#xff1a;通过…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

Spring Boot + MyBatis 集成支付宝支付流程

Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例&#xff08;电脑网站支付&#xff09; 1. 添加依赖 <!…...

云安全与网络安全:核心区别与协同作用解析

在数字化转型的浪潮中&#xff0c;云安全与网络安全作为信息安全的两大支柱&#xff0c;常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异&#xff0c;并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全&#xff1a;聚焦于保…...

ui框架-文件列表展示

ui框架-文件列表展示 介绍 UI框架的文件列表展示组件&#xff0c;可以展示文件夹&#xff0c;支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项&#xff0c;适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…...

算术操作符与类型转换:从基础到精通

目录 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符&#xff1a;、-、*、/、% 赋值操作符&#xff1a;和复合赋值 单⽬操作符&#xff1a;、--、、- 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...

DAY 26 函数专题1

函数定义与参数知识点回顾&#xff1a;1. 函数的定义2. 变量作用域&#xff1a;局部变量和全局变量3. 函数的参数类型&#xff1a;位置参数、默认参数、不定参数4. 传递参数的手段&#xff1a;关键词参数5 题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一…...

jdbc查询mysql数据库时,出现id顺序错误的情况

我在repository中的查询语句如下所示&#xff0c;即传入一个List<intager>的数据&#xff0c;返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致&#xff0c;会导致返回的id是从小到大排列的&#xff0c;但我不希望这样。 Query("SELECT NEW com…...

sshd代码修改banner

sshd服务连接之后会收到字符串&#xff1a; SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢&#xff1f; 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头&#xff0c…...

前端开发者常用网站

Can I use网站&#xff1a;一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use&#xff1a;Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站&#xff1a;MDN JavaScript权威网站&#xff1a;JavaScript | MDN...

在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7

在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤&#xff1a; 第一步&#xff1a; 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为&#xff1a; // 改为 v…...

如何在Windows本机安装Python并确保与Python.NET兼容

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...