WEB3全栈开发——面试专业技能点P7前端与链上集成_web3开发技术栈
一、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.cssnext.config.jstsconfig.json
✅ 1. 页面组件(服务端渲染)
// app/page.tsxexport default function HomePage() { return Welcome to Web3 DApp
;}
访问 http://localhost:3000
即可看到页面。
✅ 2. 动态路由页面
// app/blog/[slug]/page.tsxexport default function BlogPost({ params }: { params: { slug: string } }) { return Post: {params.slug}
;}
访问 /blog/hello-nextjs
会显示 Post: hello-nextjs
✅ 3. API 路由(可连接 Web3 后端)
// app/api/hello/route.tsexport async function GET() { return new Response(JSON.stringify({ msg: \'Hello API\' }), { status: 200, headers: { \'Content-Type\': \'application/json\' }, });}
访问 /api/hello
可返回 JSON。
✅ 4. 组件拆分与复用
// components/Header.tsxexport default function Header() { return My DApp ;}
在页面中导入:
import Header from \'@/components/Header\';export default function HomePage() { return ( Hello );}
✅ 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 Connected Wallet: {account};}
✅ 常见应用场景
✅ 总结
二、Vue 技术栈
✅ 概念介绍
Vue.js 是一套用于构建用户界面的 渐进式 JavaScript 框架,其核心关注视图层,同时易于集成第三方库或现有项目。Vue 提供响应式数据绑定、组件化开发、指令系统等核心特性。
常见的 Vue 技术栈包括:
Vue 2 / Vue 3
Vuex
/ Pinia
Vue Router
Element Plus
/ Vant
Axios
Vite
/ Webpack
TypeScript
Sass
/ Tailwind CSS
✅ 示例代码与讲解
📁 项目结构(Vue 3 + Vite)
/src /components HelloWorld.vue /views Home.vue App.vue main.ts router/index.ts store/index.ts
✅ 1. 创建组件并绑定数据
{{ title }}
import { ref } from \'vue\'const title = \'Welcome to Web3 DApp\'const count = ref(0)
✅ 2. 使用 Vue Router 创建页面路由
// router/index.tsimport { createRouter, createWebHistory } from \'vue-router\'import Home from \'@/views/Home.vue\'const routes = [{ path: \'/\', component: Home }]export const router = createRouter({ history: createWebHistory(), routes})
// main.tsimport { 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.tsimport { defineStore } from \'pinia\'export const useCounter = defineStore(\'counter\', { state: () => ({ count: 0 }), actions: { increment() { this.count++ } }})
import { useCounter } from \'@/store/useCounter\'const counter = useCounter()
✅ 4. 发送 API 请求(Axios)
// api/user.tsimport axios from \'axios\'export const getUser = () => { return axios.get(\'/api/user\')}
import { onMounted, ref } from \'vue\'import { getUser } from \'@/api/user\'const user = ref(null)onMounted(async () => { const res = await getUser() user.value = res.data})
✅ Vue 技术栈常用于:
✅ 总结
如果你想我给出 Web3 场景中 Vue 技术栈的实战 DApp 示例页面,也可以告诉我。
三、Next.js 和 Vue 的 SSR(服务器端渲染)渲染
✅ 概念介绍(SSR 是什么?)
SSR(Server-Side Rendering)服务器端渲染 是一种将页面在服务器端生成 HTML 的方式,服务器将完整 HTML 页面返回给浏览器。
相比 CSR(客户端渲染):
🚀 Next.js 的 SSR 渲染
Next.js 默认支持 SSR(基于 React),你可以通过 getServerSideProps
来开启服务器端渲染页面。
✅ 示例代码:SSR 页面
// pages/ssr-page.tsximport 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 ( 🚀 SSR 渲染页面
服务器生成时间:{time}
)}
🔍 注释说明:
-
getServerSideProps
:每次请求此页面时都会执行,返回的props
会传给页面组件。 -
页面在服务端生成 HTML,然后发送给浏览器,因此服务端返回的时间是准确的。
-
SEO 友好,因为浏览器拿到的是完整 HTML 内容。
🌿 Vue + Nuxt 的 SSR 渲染
Vue 自身不是 SSR 框架,但 Nuxt.js 是官方推荐的 SSR 框架(对 Vue 的增强封装)。
✅ 示例代码:Nuxt 页面 SSR 渲染
🚀 SSR 页面
服务器生成时间:{{ time }}
defineProps([\'time\'])// 运行在服务端,每次请求都会执行export async function asyncData() { return { time: new Date().toISOString() }}
🔍 注释说明:
-
asyncData
:Nuxt 特有的生命周期函数,只能在服务端运行,用于数据预取。 -
页面请求到达时,服务器会调用
asyncData
,填充数据并生成 HTML。 -
响应发送给客户端时已带数据,无需客户端再请求。
🧩 对比总结:Next.js vs Nuxt.js SSR
getServerSideProps
asyncData
pages/
pages/
📌 使用 SSR 的注意事项
-
SSR 页面 每次请求都执行服务端代码,对服务器性能有要求。
-
需考虑 数据获取延迟 与 页面缓存(可结合 Redis 缓存或 ISR)。
-
页面必须具备 hydrate 机制:HTML 渲染完成后,还要激活前端逻辑(事件绑定、状态管理等)。
如你有 Web3 DApp 页面 SSR 的需求,我可以给你示例如何通过 SSR 预渲染钱包信息、合约状态等内容。是否需要?
四、Next.js 和 Vue 的组件通信
✅ 概念介绍
组件通信 是指组件之间如何传递数据、事件或共享状态。根据组件关系(父子、兄弟、跨层级),通信方式也不同。
1️⃣ Next.js(基于 React)组件通信
示例一:父子组件通信(Props + 回调)
// 父组件 Parent.tsximport React, { useState } from \'react\'import Child from \'./Child\'export default function Parent() { const [message, setMessage] = useState(\'Hello from parent!\') const handleChildClick = () => { alert(\'👦 子组件点击了按钮\') } return ( 👨 父组件
发送给子组件:{message}
)}
// 子组件 Child.tsxexport default function Child({ msg, onChildClick}: { msg: string onChildClick: () => void}) { return ( 👶 子组件
收到来自父组件的信息:{msg}
)}
🔍 注释说明
-
父组件通过
props
向子组件传值。 -
子组件通过
props
中的回调函数,通知父组件事件(如按钮点击)。
示例二:兄弟组件通信(状态提升)
// Page.tsximport React, { useState } from \'react\'import BrotherA from \'./BrotherA\'import BrotherB from \'./BrotherB\'export default function Page() { const [count, setCount] = useState(0) return ( )}
// BrotherA.tsxexport default function BrotherA({ count }: { count: number }) { return 我是 A 组件,我收到的值是:{count}
}
// BrotherB.tsxexport default function BrotherB({ setCount}: { setCount: (n: number) => void}) { return ( )}
2️⃣ Vue 组件通信
示例一:父子组件通信(Props + $emit)
👨 父组件
import Child from \'./Child.vue\'import { ref } from \'vue\'const message = ref(\'Hello from parent!\')function handleClick() { alert(\'👦 子组件点击了按钮\')}
👶 子组件
收到来自父组件的信息:{{ msg }}
defineProps([\'msg\'])defineEmits([\'child-click\'])
示例二:Provide / Inject(跨层级通信)
import { provide } from \'vue\'import Parent from \'./Parent.vue\'provide(\'theme\', \'dark\') // 提供 theme 给深层组件
🌙 当前主题:{{ theme }}
import { inject } from \'vue\'const theme = inject(\'theme\') // 跨组件获取
🔍 总结对比
$emit
useState
、useEffect
ref
, watch
, computed
如你需要基于 Web3 钱包连接或合约状态共享的组件通信设计,也可以继续告诉我,我能给出特定案例。
五、Next.js 和 Vue 的接口封装
✅ 概念介绍
接口封装 是指将网络请求(如 API 调用)统一管理,使得组件使用时只需调用方法,而无需关注底层请求细节。
好处包括:
-
提高复用性(比如统一请求逻辑)
-
方便维护和扩展
-
易于切换后端地址、设置 token、错误处理
1️⃣ Next.js 接口封装(基于 Axios)
示例代码:封装 API 请求(/lib/api.ts)
// /lib/api.tsimport axios from \'axios\'const instance = axios.create({ baseURL: \'/api\', // Next.js 本地 API 或代理地址 timeout: 5000})// 请求拦截器instance.interceptors.request.use( config => { // 你可以在这里添加 token const 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(null) useEffect(() => { getUserInfo().then(data => setUser(data)) }, []) return (
用户信息
{JSON.stringify(user, null, 2)})}
🔍 注释说明
-
lib/api.ts
集中封装所有后端请求。 -
使用
axios.create
创建实例,统一设置 baseURL 和拦截器。 -
使用组件中调用封装方法,避免直接写
axios.get(...)
。
2️⃣ Vue 接口封装(基于 Axios + Composition API)
示例代码:封装 API 请求(/src/utils/http.ts)
// /src/utils/http.tsimport 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.tsimport 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)
用户信息
{{ user }}import { ref, onMounted } from \'vue\'import { getUserInfo } from \'@/api/user\'const user = ref(null)onMounted(async () => { user.value = await getUserInfo()})
🔍 对比总结
项目 Next.js Vue 通用工具目录 /lib/api.ts
/src/api/*.ts
+/src/utils/http.ts
常用工具 Axios、fetch Axios 状态管理 useState / useSWR ref / reactive 拦截器 支持(用于 token 注入 / 错误处理) 支持
如需拓展支持 Web3 钱包签名、Token 插入、GraphQL 封装、或者与合约交互的 API 包装,请继续告诉我,我可补充更专业的封装案例。
六、Next.js 和 Vue 实现钱包连接(MetaMask)
✅ 概念介绍
在 DApp 中,连接钱包(如 MetaMask)是与区块链交互的第一步。
主要过程如下:
检测是否安装了 MetaMask
请求用户连接账户(授权)
获取当前账户地址
(可选)监听账户变化或网络变化
用于后续签名、交易、合约交互
我们将用 Next.js(React) 和 Vue 3 各写一个示例。
1️⃣ Next.js 连接 MetaMask 示例
📁 技术栈
-
Next.js 13+(App Router 或 Page Router)
-
使用
window.ethereum
对象(MetaMask 提供)
示例代码:组件中连接钱包
\'use client\' // 如果是 App Routerimport { useEffect, useState } from \'react\'export default function ConnectWallet() { const [account, setAccount] = useState(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 ( {account && 当前账户: {account}
} )}
🔍 注释说明
-
window.ethereum
是 MetaMask 插件注入的全局对象。 -
使用
eth_requestAccounts
方法让用户授权钱包访问。 -
setAccount
更新状态用于页面展示当前地址。
2️⃣ Vue 3 + Composition API 实现连接 MetaMask
示例代码(ConnectWallet.vue
)
当前账户:{{ account }}
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) }}
🔍 注释说明
-
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 列表,常见流程:
-
连接钱包,获取用户地址
-
调用区块链或第三方API(如 OpenSea API、Alchemy NFT API、Moralis 等)获取该地址持有的NFT
-
解析NFT数据,包括 Token ID、名称、图片 URL、描述等
-
在前端渲染 NFT 列表
下面给出 Next.js 和 Vue 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: string title: string image: string}export default function NFTList() { const [nfts, setNfts] = useState([]) const [address, setAddress] = useState(\'0x8ba1f109551bD432803012645Ac136ddd64DBA72\') // 示例地址 const [loading, setLoading] = useState(false) useEffect(() => { if (!address) return const fetchNFTs = async () => { setLoading(true) try { // Alchemy NFT API - 替换为自己的API key const apiKey = \'demo\' // 请换成自己的API KEY const 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 ( NFT 列表展示
{loading && 加载中...
} {!loading && nfts.length === 0 && 未找到NFT
} {nfts.map((nft) => ( -
{nft.title}
Token ID: {parseInt(nft.tokenId, 16)}
))}
)}
代码详解
-
地址(address):NFT 持有者地址,默认示例地址可替换
-
调用 Alchemy API:根据
owner
查询 NFT 持有信息 -
解析返回数据:提取 NFT tokenId、名称和封面图片 URL
-
渲染列表:图片+名称+Token ID
-
loading 状态管理:加载时显示提示
2️⃣ Vue 3 实现 NFT 列表展示示例
技术点:
-
Vue 3 + Composition API
-
使用
fetch
异步调用第三方API -
响应式数据绑定
-
基础样式布局
示例代码
NFT 列表展示
加载中... 未找到NFT -
{{ nft.title }}
Token ID: {{ parseInt(nft.tokenId, 16) }}
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 = true try { const apiKey = \'demo\' // 替换为你的Alchemy API Key const 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 }}.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;}.border { border: 1px solid #ddd; border-radius: 6px;}.object-cover { object-fit: cover;}
代码详解
-
响应式数据
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.js
或Ethers.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([]) 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 ( 合约事件监听 — Transfer 事件
{events.map((e, i) => ( - From: {e.from}
To: {e.to}
Value: {e.value}
TxHash: {e.txHash} ))}
)}
代码详解
-
ethers.providers.Web3Provider(window.ethereum)
:通过 MetaMask 提供的 provider 连接以太坊 -
new ethers.Contract(...)
:用合约地址和 ABI 创建合约实例 -
contract.on(\"Transfer\", callback)
:监听 Transfer 事件,事件参数和原始事件对象都会传入 -
监听到事件后,将事件数据加入状态数组,页面实时渲染
-
组件卸载时用
contract.off
移除监听,防止多次绑定导致重复事件
2️⃣ Vue 3 实现合约事件监听示例(使用 Ethers.js)
合约事件监听 — Transfer 事件
- From: {{ e.from }}
To: {{ e.to }}
Value: {{ e.value }}
TxHash: {{ e.txHash }}
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) }})
代码详解
-
使用 Vue 3 的生命周期函数
onMounted
、onUnmounted
-
事件监听同样通过
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 的详细描述、图片、历史交易记录(链下数据)
实现思路
-
链上数据读取:通过智能合约接口(如
Web3.js
或Ethers.js
)调用读取链上状态。 -
链下数据查询:调用后端 API 或数据库接口,获取链下存储的结构化数据。
-
结合展示:前端将链上数据与链下数据合并后,渲染完整的页面信息。
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(null) const [offChainData, setOffChainData] = useState(null) const [error, setError] = useState(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 错误:{error} if (!owner || !offChainData) return 加载中... return ( {offChainData.name}
描述: {offChainData.description}
链上拥有者: {owner}
更多链下信息: {JSON.stringify(offChainData.metadata)}
)}
代码详解
-
使用
ethers.Contract
读取链上 NFT 的拥有者地址 (ownerOf
) -
通过
fetch
请求自建 API 获取 NFT 详细的链下数据(名称、图片、描述等) -
页面将链上与链下数据合并显示,提供完整 NFT 详情
2️⃣ Vue 3 实现链上状态与链下数据结合示例
错误: {{ error }} 加载中... {{ offChainData.name }}
描述: {{ offChainData.description }}
链上拥有者: {{ owner }}
更多链下信息: {{ offChainData.metadata }}
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)// 合约ABIconst 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)})
代码详解
-
结合 Vue 的响应式
ref
,用onMounted
和watch
实现数据初始化和动态更新 -
同样通过
ethers.Contract
读取链上所有者,通过 fetch 获取链下数据 -
模板中将链上和链下数据合并展示
额外说明
-
链上读取使用客户端钱包提供的 provider,数据真实且防篡改
-
链下数据通过 API 获取,便于存储复杂、富媒体内容和扩展信息
-
结合后端数据库,可实现完整业务逻辑(用户行为记录、评论、链上事件索引等)
-
实际项目中可考虑缓存机制、错误处理、加载状态优化
-
也可以用服务器端渲染(SSR)提前获取链下数据,提高 SEO 和首屏速度