通過前邊的學(xué)習(xí),DApp 的 Solidity 合約部分就完成了√暄酰現(xiàn)在我們來做一個基本的網(wǎng)頁好讓你的用戶能玩它。 要做到這一點,我們將使用以太坊基金發(fā)布的 JavaScript 庫 —— Web3.js.
一房交、Web3.js簡介
什么是 Web3.js?
還記得么?以太坊網(wǎng)絡(luò)是由節(jié)點組成的伐割,每一個節(jié)點都包含了區(qū)塊鏈的一份拷貝
候味。當(dāng)你想要調(diào)用一份智能合約的一個方法,你需要從其中一個節(jié)點中查找并告訴它:
- 1口猜、智能合約的地址
- 2负溪、你想調(diào)用的方法,以及
- 3济炎、你想傳入那個方法的參數(shù)
以太坊節(jié)點只能識別一種叫做 JSON-RPC
的語言川抡。這種語言直接讀起來并不好懂。當(dāng)你你想調(diào)用一個合約的方法的時候须尚,需要發(fā)送的查詢語句將會是這樣的:
// 哈……祝你寫所有這樣的函數(shù)調(diào)用的時候都一次通過
// 往右邊拉…… ==>
{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}],"id":1}
幸運的是 Web3.js
把這些令人討厭的查詢語句都隱藏起來了崖堤, 所以你只需要與方便易懂的 JavaScript
界面進行交互即可。
你不需要構(gòu)建上面的查詢語句耐床,在你的代碼中調(diào)用一個函數(shù)看起來將是這樣:
CryptoZombies.methods.createRandomZombie("Vitalik Nakamoto")
.send({ from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", gas: "3000000" })
我們將在接下來的幾章詳細(xì)解釋這些語句密幔,不過首先我們來把 Web3.js 環(huán)境搭建起來
準(zhǔn)備工作
取決于你的項目工作流程和你的愛好,你可以用一些常用工具把 Web3.js 添加進來:
// 用 NPM
npm install web3
// 用 Yarn
yarn add web3
// 用 Bower
bower install web3
// ...或者其他撩轰。
甚至胯甩,你可以從 github直接下載壓縮后的 .js
文件 然后包含到你的項目文件中:
<script language="javascript" type="text/javascript" src="web3.min.js">
因為我們不想讓你花太多在項目環(huán)境搭建上,在本教程中我們將使用上面的 script 標(biāo)簽來將 Web3.js 引入堪嫂。
實戰(zhàn)演練
新建一個HTML 項目空殼 —— index.html
偎箫。假設(shè)在和 index.html
同個文件夾里有一份 web3.min.js
使用上面的 script 標(biāo)簽代碼把 web3.js
添加進去以備接下來使用。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CryptoZombies front-end</title>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Include web3.js here -->
<script language="javascript" type="text/javascript" src="web3.min.js"></script>
</head>
<body>
</body>
</html>
二皆串、Web3提供者
現(xiàn)在我們的項目中有了Web3.js, 來初始化它然后和區(qū)塊鏈對話吧淹办。
首先我們需要 Web3 Provider
.
要記住,以太坊是由共享同一份數(shù)據(jù)的相同拷貝的 節(jié)點 構(gòu)成的恶复。 在 Web3.js 里設(shè)置 Web3 的 Provider
(提供者) 告訴我們的代碼應(yīng)該和 哪個節(jié)點 交互來處理我們的讀寫
怜森。這就好像在傳統(tǒng)的 Web 應(yīng)用程序中為你的 API 調(diào)用設(shè)置遠(yuǎn)程 Web 服務(wù)器的網(wǎng)址速挑。
你可以運行你自己的以太坊節(jié)點來作為 Provider。 不過副硅,有一個第三方的服務(wù)姥宝,可以讓你的生活變得輕松點,讓你不必為了給你的用戶提供DApp而維護一個以太坊節(jié)點— Infura
.
Infura
Infura
是一個服務(wù)想许,它維護了很多以太坊節(jié)點并提供了一個緩存層來實現(xiàn)高速讀取
伶授。你可以用他們的 API 來免費訪問這個服務(wù)。 用 Infura 作為節(jié)點提供者流纹,你可以不用自己運營節(jié)點就能很可靠地向以太坊發(fā)送糜烹、接收信息。
你可以通過這樣把 Infura 作為你的 Web3 節(jié)點提供者:
var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
不過漱凝,因為我們的 DApp 將被很多人使用疮蹦,這些用戶不單會從區(qū)塊鏈讀取信息,還會向區(qū)塊鏈 寫
入信息茸炒,我們需要用一個方法讓用戶可以用他們的私鑰給事務(wù)簽名
愕乎。
注意: 以太坊 (以及通常意義上的 blockchains )
使用一個公鑰/私鑰對來對給事務(wù)做數(shù)字簽名
。把它想成一個數(shù)字簽名的異常安全的密碼壁公。這樣當(dāng)我修改區(qū)塊鏈上的數(shù)據(jù)的時候感论,我可以用我的公鑰來 證明 我就是簽名的那個。但是因為沒人知道我的私鑰紊册,所以沒人能偽造我的事務(wù)比肄。
加密學(xué)非常復(fù)雜,所以除非你是個專家并且的確知道自己在做什么囊陡,你最好不要在你應(yīng)用的前端中管理你用戶的私鑰芳绩。
不過幸運的是,你并不需要撞反,已經(jīng)有可以幫你處理這件事的服務(wù)了: Metamask
.
Metamask
Metamask 是 Chrome 和 Firefox 的瀏覽器擴展妥色, 它能讓用戶安全地維護他們的以太坊賬戶和私鑰
, 并用他們的賬戶和使用 Web3.js 的網(wǎng)站互動(如果你還沒用過它遏片,你肯定會想去安裝的——這樣你的瀏覽器就能使用 Web3.js 了嘹害,然后你就可以和任何與以太坊區(qū)塊鏈通信的網(wǎng)站交互了)
作為開發(fā)者,如果你想讓用戶從他們的瀏覽器里通過網(wǎng)站和你的DApp交互(就像我們在 CryptoZombies 游戲里一樣)吮便,你肯定會想要兼容 Metamask 的笔呀。
注意: Metamask 默認(rèn)使用 Infura 的服務(wù)器做為 web3 提供者。 就像我們上面做的那樣线衫。不過它還為用戶提供了選擇他們自己 Web3 提供者的選項凿可。所以使用 Metamask 的 web3 提供者惑折,你就給了用戶選擇權(quán)授账,而自己無需操心這一塊枯跑。
使用Metamask的web3提供者
Metamask 把它的 web3 提供者注入到瀏覽器的全局 JavaScript對象web3中。所以你的應(yīng)用可以檢查 web3
是否存在白热。若存在就使用 web3.currentProvider
作為它的提供者敛助。
這里是一些 Metamask 提供的示例代碼,用來檢查用戶是否安裝了MetaMask屋确,如果沒有安裝就告訴用戶需要安裝MetaMask來使用我們的應(yīng)用纳击。
window.addEventListener('load', function() {
// 檢查web3是否已經(jīng)注入到(Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// 使用 Mist/MetaMask 的提供者
web3js = new Web3(web3.currentProvider);
} else {
// 處理用戶沒安裝的情況, 比如顯示一個消息
// 告訴他們要安裝 MetaMask 來使用我們的應(yīng)用
}
// 現(xiàn)在你可以啟動你的應(yīng)用并自由訪問 Web3.js:
startApp()
})
你可以在你所有的應(yīng)用中使用這段樣板代碼攻臀,好檢查用戶是否安裝以及告訴用戶安裝 MetaMask焕数。
注意: 除了MetaMask,你的用戶也可能在使用其他他的私鑰管理應(yīng)用刨啸,比如 Mist 瀏覽器堡赔。不過,它們都實現(xiàn)了相同的模式來注入 web3 變量设联。所以我這里描述的方法對兩者是通用的善已。
實戰(zhàn)演練
我們在HTML文件中的 </body>
標(biāo)簽前面放置了一個空的 script 標(biāo)簽±肜可以把這節(jié)課的 JavaScript 代碼寫在里面换团。
把上面用來檢測 MetaMask 是否安裝的模板代碼粘貼進來。請粘貼到以 window.addEventListener
開頭的代碼塊中宫蛆。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CryptoZombies front-end</title>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="web3.min.js"></script>
</head>
<body>
<script>
// Start here
window.addEventListener('load', function() {
// 檢查web3是否已經(jīng)注入到(Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// 使用 Mist/MetaMask 的提供者
web3js = new Web3(web3.currentProvider);
} else {
// 處理用戶沒安裝的情況艘包, 比如顯示一個消息
// 告訴他們要安裝 MetaMask 來使用我們的應(yīng)用
}
// 現(xiàn)在你可以啟動你的應(yīng)用并自由訪問 Web3.js:
startApp()
})
</script>
</body>
</html>
三、和合約對話
現(xiàn)在洒扎,我們已經(jīng)用 MetaMask 的 Web3 提供者初始化了 Web3.js辑甜。接下來就讓它和我們的智能合約對話吧。
Web3.js 需要兩個東西來和你的合約對話: 它的 地址
和它的 ABI
袍冷。
合約地址
在你寫完了你的智能合約后磷醋,你需要編譯它并把它部署到以太坊。我們將在下一課中詳述部署胡诗,因為它和寫代碼是截然不同的過程邓线,所以我們決定打亂順序,先來講 Web3.js煌恢。
在你部署智能合約以后骇陈,它將獲得一個以太坊上的永久地址。如果你還記得第二課瑰抵,CryptoKitties 在以太坊上的地址是 YOUR_CONTRACT_ADDRESS
你雌。
你需要在部署后復(fù)制這個地址以來和你的智能合約對話。
合約ABI
另一個 Web3.js 為了要和你的智能合約對話而需要的東西是 ABI。
ABI
意為應(yīng)用二進制接口(Application Binary Interface)婿崭。 基本上拨拓,它是以 JSON 格式表示合約的方法,告訴 Web3.js 如何以合同理解的方式格式化函數(shù)調(diào)用氓栈。
當(dāng)你編譯你的合約向以太坊部署時(我們將后邊詳述)渣磷, Solidity 編譯器會給你 ABI,所以除了合約地址授瘦,你還需要把這個也復(fù)制下來醋界。
因為我們這一課不會講述部署,所以現(xiàn)在我們已經(jīng)幫你編譯了 ABI 并放在了名為cryptozombies_abi.js
提完,文件中形纺,保存在一個名為 cryptozombiesABI
的變量中。
如果我們將cryptozombies_abi.js
包含進我們的項目徒欣,我們就能通過那個變量訪問 CryptoZombies ABI 挡篓。
cryptozombies_abi.js
文件:
var cryptozombiesABI = [
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_zombieId",
"type": "uint256"
}
],
"name": "levelUp",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_zombieId",
"type": "uint256"
},
{
"name": "_kittyId",
"type": "uint256"
}
],
"name": "feedOnKitty",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "uint256"
}
],
"name": "zombies",
"outputs": [
{
"name": "name",
"type": "string"
},
{
"name": "dna",
"type": "uint256"
},
{
"name": "level",
"type": "uint32"
},
{
"name": "readyTime",
"type": "uint32"
},
{
"name": "winCount",
"type": "uint16"
},
{
"name": "lossCount",
"type": "uint16"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "withdraw",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "getZombiesByOwner",
"outputs": [
{
"name": "",
"type": "uint256[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "uint256"
}
],
"name": "zombieToOwner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_address",
"type": "address"
}
],
"name": "setKittyContractAddress",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_zombieId",
"type": "uint256"
},
{
"name": "_newDna",
"type": "uint256"
}
],
"name": "changeDna",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"name": "_owner",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "_balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_name",
"type": "string"
}
],
"name": "createRandomZombie",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_tokenId",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getAllZombies",
"outputs": [
{
"name": "",
"type": "uint256[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_tokenId",
"type": "uint256"
}
],
"name": "takeOwnership",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_zombieId",
"type": "uint256"
},
{
"name": "_newName",
"type": "string"
}
],
"name": "changeName",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_fee",
"type": "uint256"
}
],
"name": "setLevelUpFee",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_zombieId",
"type": "uint256"
},
{
"name": "_targetId",
"type": "uint256"
}
],
"name": "attack",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_approved",
"type": "address"
},
{
"indexed": false,
"name": "_tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "attackResult",
"type": "bool"
},
{
"indexed": false,
"name": "winCount",
"type": "uint16"
},
{
"indexed": false,
"name": "lossCount",
"type": "uint16"
}
],
"name": "AttackResult",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "zombieId",
"type": "uint256"
},
{
"indexed": false,
"name": "name",
"type": "string"
},
{
"indexed": false,
"name": "dna",
"type": "uint256"
}
],
"name": "NewZombie",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
}
]
實例化Web3.js
一旦你有了合約的地址和 ABI,你可以像這樣來實例化 Web3.js帚称。
// 實例化 myContract
var myContract = new web3js.eth.Contract(myABI, myContractAddress);
實戰(zhàn)演練
- 1官研、在文件的 <head> 標(biāo)簽塊中,用 script 標(biāo)簽引入
cryptozombies_abi.js
闯睹,好把 ABI 的定義引入項目戏羽。 - 2、在 <body> 里的 <script> 開頭 , 定義一個
var
楼吃,取名cryptoZombies
始花, 不過不要對其賦值,稍后我們將用這個這個變量來存儲我們實例化合約孩锡。 - 3酷宵、接下來,創(chuàng)建一個名為
startApp()
的function
躬窜。 接下來兩步來完成這個方法浇垦。 - 4、
startApp()
里應(yīng)該做的第一件事是定義一個名為cryptoZombiesAddress
的變量并賦值為"你的合約地址" (這是你的合約在以太坊主網(wǎng)上的地址)荣挨。 - 5男韧、最后,來實例化我們的合約默垄。模仿我們上面的代碼此虑,將
cryptoZombies
賦值為new web3js.eth.Contract
(使用我們上面代碼中通過 script 引入的cryptoZombiesABI
和cryptoZombiesAddress
)。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CryptoZombies front-end</title>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="web3.min.js"></script>
<!-- 1\. Include cryptozombies_abi.js here -->
<script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script>
</head>
<body>
<script>
// 2\. Start code here
var cryptoZombies;
function startApp() {
var cryptoZombiesAddress = "你的合約地址";
cryptoZombies = new Web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);
}
window.addEventListener('load', function() {
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider
web3js = new Web3(web3.currentProvider);
} else {
// Handle the case where the user doesn't have Metamask installed
// Probably show them a message prompting them to install Metamask
}
// Now you can start your app & access web3 freely:
startApp()
})
</script>
</body>
</html>
四口锭、調(diào)用和合約函數(shù)
我們的合約配置好了朦前!現(xiàn)在來用 Web3.js 和它對話。
Web3.js 有兩個方法來調(diào)用我們合約的函數(shù): call
and send
.
call
call
用來調(diào)用 view
和 pure
函數(shù)。它只運行在本地節(jié)點韭寸,不會在區(qū)塊鏈上創(chuàng)建事務(wù)这溅。
復(fù)習(xí):
view
和pure
函數(shù)是只讀的并不會改變區(qū)塊鏈的狀態(tài)。它們也不會消耗任何gas棒仍。用戶也不會被要求用MetaMask對事務(wù)簽名。
使用 Web3.js臭胜,你可以如下 call
一個名為myMethod
的方法并傳入一個 123 作為參數(shù):
myContract.methods.myMethod(123).call()
Send
send
將創(chuàng)建一個事務(wù)并改變區(qū)塊鏈上的數(shù)據(jù)莫其。你需要用 send
來調(diào)用任何非 view
或者 pure
的函數(shù)。
注意:
send
一個事務(wù)將要求用戶支付gas耸三,并會要求彈出對話框請求用戶使用 Metamask 對事務(wù)簽名乱陡。在我們使用 Metamask 作為我們的 web3 提供者的時候,所有這一切都會在我們調(diào)用send()
的時候自動發(fā)生仪壮。而我們自己無需在代碼中操心這一切憨颠,挺爽的吧。
使用 Web3.js, 你可以像這樣 send 一個事務(wù)調(diào)用myMethod 并傳入 123 作為參數(shù):
myContract.methods.myMethod(123).send()
語法幾乎 call()一模一樣积锅。
獲取僵尸數(shù)據(jù)
來看一個使用 call
讀取我們合約數(shù)據(jù)的真實例子
回憶一下爽彤,我們定義我們的僵尸數(shù)組為 公開(public):
Zombie[] public zombies;
在 Solidity 里,當(dāng)你定義一個 public
變量的時候缚陷, 它將自動定義一個公開的 "getter" 同名方法适篙, 所以如果你像要查看 id 為 15 的僵尸,你可以像一個函數(shù)一樣調(diào)用它: zombies(15)
.
這是如何在外面的前端界面中寫一個 JavaScript 方法來傳入一個僵尸 id箫爷,在我們的合同中查詢那個僵尸并返回結(jié)果
注意: 本課中所有的示例代碼都使用 Web3.js 的 1.0 版嚷节,此版本使用的是 Promises 而不是回調(diào)函數(shù)。你在線上看到的其他教程可能還在使用老版的 Web3.js虎锚。在1.0版中硫痰,語法改變了不少。如果你從其他教程中復(fù)制代碼窜护,先確保你們使用的是相同版本的Web3.js效斑。
function getZombieDetails(id) {
return cryptoZombies.methods.zombies(id).call()
}
// 調(diào)用函數(shù)并做一些其他事情
getZombieDetails(15)
.then(function(result) {
console.log("Zombie 15: " + JSON.stringify(result));
});
我們來看看這里都做了什么
cryptoZombies.methods.zombies(id).call()
將和 Web3 提供者節(jié)點通信,告訴它返回從我們的合約中的 Zombie[] public zombies
柱徙,id為傳入?yún)?shù)的僵尸信息鳍悠。
注意這是 異步的,就像從外部服務(wù)器中調(diào)用API坐搔。所以 Web3 在這里返回了一個 Promises. (如果你對 JavaScript的 Promises 不了解藏研,最好先去學(xué)習(xí)一下這方面知識再繼續(xù))。
一旦那個 promise
被 resolve
, (意味著我們從 Web3 提供者那里獲得了響應(yīng))概行,我們的例子代碼將執(zhí)行 then 語句中的代碼蠢挡,在控制臺打出 result。
result 是一個像這樣的 JavaScript 對象:
{
"name": "H4XF13LD MORRIS'S COOLER OLDER BROTHER",
"dna": "1337133713371337",
"level": "9999",
"readyTime": "1522498671",
"winCount": "999999999",
"lossCount": "0" // Obviously.
}
我們可以用一些前端邏輯代碼來解析這個對象并在前端界面友好展示。
實戰(zhàn)演練
我們已經(jīng)把 getZombieDetails
復(fù)制進了代碼业踏。
- 1禽炬、先為zombieToOwner 創(chuàng)建一個類似的函數(shù)。如果你還記得 ZombieFactory.sol勤家,我們有一個長這樣的映射:
- `mapping (uint => address) public zombieToOwner;
- 定義一個 JavaScript 方法腹尖,起名為 zombieToOwner。和上面的 getZombieDetails 類似伐脖, 它將接收一個id 作為參數(shù)热幔,并返回一個 Web3.js call 我們合約里的zombieToOwner 。
- 2讼庇、之后在下面绎巨,為
getZombiesByOwner
定義一個方法。如果你還能記起 ZombieHelper.sol蠕啄,這個方法定義像這樣: function getZombiesByOwner(address _owner)
- 我們的 getZombiesByOwner 方法將接收 owner 作為參數(shù)场勤,并返回一個對我們函數(shù) getZombiesByOwner的 Web3.js call
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CryptoZombies front-end</title>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="web3.min.js"></script>
<script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script>
</head>
<body>
<script>
var cryptoZombies;
function startApp() {
var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS";
cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);
}
function getZombieDetails(id) {
return cryptoZombies.methods.zombies(id).call()
}
// 1\. Define `zombieToOwner` here
function zombieToOwner(id) {
return cryptoZombies.methods.zombieToOwner(id).call()
}
// 2\. Define `getZombiesByOwner` here
function getZombiesByOwner(owner) {
return cryptoZombies.methods.getZombiesByOwner(owner).call()
}
window.addEventListener('load', function() {
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider
web3js = new Web3(web3.currentProvider);
} else {
// Handle the case where the user doesn't have Metamask installed
// Probably show them a message prompting them to install Metamask
}
// Now you can start your app & access web3 freely:
startApp()
})
</script>
</body>
</html>
Promise學(xué)習(xí)
promise
是異步編程
的一種解決方案,比傳統(tǒng)的解決方案–回調(diào)函數(shù)和事件--更合理和更強大歼跟。它由社區(qū)最早提出和實現(xiàn)和媳,ES6將其寫進了語言標(biāo)準(zhǔn),統(tǒng)一了語法哈街,原生提供了Promise
所謂Promise 窗价,簡單說就是一個容器,里面保存著某個未來才回結(jié)束的事件(通常是一個異步操作)的結(jié)果叹卷。從語法上說撼港,Promise是一個對象,從它可以獲取異步操作的消息骤竹。
Promise 對象的狀態(tài)不受外界影響
三種狀態(tài):
- pending:進行中
- fulfilled :已經(jīng)成功
- rejected 已經(jīng)失敗
狀態(tài)改變:
Promise對象的狀態(tài)改變帝牡,只有兩種可能:
- 從pending變?yōu)閒ulfilled
- 從pending變?yōu)閞ejected。
這兩種情況只要發(fā)生蒙揣,狀態(tài)就凝固
了靶溜,不會再變了,這時就稱為resolved
(已定型)
基本用法
ES6規(guī)定懒震,Promise對象是一個構(gòu)造函數(shù)
罩息,用來生成Promise實例:
const promist = new Promise(function(resolve,reject){
if(/*異步操作成功*/){
resolve(value);
}else{
reject(error);
}
})
-
resolve
函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved)个扰,在異步操作成功時調(diào)用瓷炮,并將異步操作的結(jié)果,作為參數(shù)傳遞出去递宅; -
reject
函數(shù)的作用是娘香,將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)苍狰,在異步操作失敗時調(diào)用,并將異步操作報出的錯誤烘绽,作為參數(shù)傳遞出去淋昭。
Promise 實例生成以后,可以用then
方法分別指定resolved
狀態(tài)和rejected
狀態(tài)的回調(diào)函數(shù)安接。
promise.then(function(value){
//success
},function(error){
//failure
});
示例:
function timeout(ms){
return new Promise((resolve,reject)=>{
setTimeout(resolve,ms,'done');
});
}
timeout(100).then((value)=>{
console.log(value);
});
let promise = new Promise(function(resolve,reject){
console.log('Promise');
resolve();
});
promise.then(function(){
console.log('resolved');
});
console.log('Hi!');
//Promise
//Hi!
//resolved
//異步加載圖片
function loadImageAsync(url){
return new Promise(function(resolve,reject){
const image = new Image();
image.onload = function(){
resolve(image);
};
image.onerror = function(){
reject(new Error('error');
};
image.src = url;
});
}
下面是一個用Promise對象實現(xiàn)的 Ajax 操作的例子翔忽。
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯了', error);
});
五、MetaMask和賬戶
接下來我們綜合一下——比如我們想讓我們應(yīng)用的首頁顯示用戶的整個僵尸大軍盏檐。
毫無疑問我們首先需要用 getZombiesByOwner(owner)
來查詢當(dāng)前用戶的所有僵尸ID歇式。
但是我們的 Solidity 合約需要 owner
作為 Solidity address
。我們?nèi)绾文苤缿?yīng)用用戶的地址呢糯笙?
獲得MetaMask中的用戶賬戶
MetaMask 允許用戶在擴展中管理多個賬戶。
我們可以通過這樣來獲取 web3 變量中激活的當(dāng)前賬戶:
var userAccount = web3.eth.accounts[0]
因為用戶可以隨時在 MetaMask 中切換賬戶撩银,我們的應(yīng)用需要監(jiān)控這個變量给涕,一旦改變就要相應(yīng)更新界面。例如额获,若用戶的首頁展示它們的僵尸大軍够庙,當(dāng)他們在 MetaMask 中切換了賬號,我們就需要更新頁面來展示新選擇的賬戶的僵尸大軍抄邀。
我們可以通過 setInterval
方法來做:
var accountInterval = setInterval(function() {
// 檢查賬戶是否切換
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// 調(diào)用一些方法來更新界面
updateInterface();
}
}, 100);
這段代碼做的是耘眨,每100毫秒檢查一次 userAccount 是否還等于 web3.eth.accounts[0]
(比如:用戶是否還激活了那個賬戶)。若不等境肾,則將 當(dāng)前激活用戶賦值給 userAccount
剔难,然后調(diào)用一個函數(shù)來更新界面。
實戰(zhàn)演練
我們來讓應(yīng)用在頁面第一次加載的時候顯示用戶的僵尸大軍奥喻,監(jiān)控當(dāng)前 MetaMask 中的激活賬戶偶宫,并在賬戶發(fā)生改變的時候刷新顯示。
- 1环鲤、定義一個名為
userAccount
的變量纯趋,不給任何初始值。 - 2冷离、在
startApp()
函數(shù)的最后吵冒,復(fù)制粘貼上面樣板代碼中的accountInterval
方法進去。 - 3西剥、將
updateInterface();
替換成一個getZombiesByOwner
的call
函數(shù)痹栖,并傳入userAccount
。 - 4瞭空、在 getZombiesByOwner 后面鏈?zhǔn)秸{(diào)用then 語句结耀,并將返回的結(jié)果傳入名為 displayZombies 的函數(shù)留夜。 (語句像這樣: .then(displayZombies);).
我們還沒有 displayZombies 函數(shù),將于下一章實現(xiàn)图甜。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CryptoZombies front-end</title>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="web3.min.js"></script>
<script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script>
</head>
<body>
<script>
var cryptoZombies;
// 1\. declare `userAccount` here
var userAccount;
function startApp() {
var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS";
cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);
// 2\. Create `setInterval` code here
var accountInterval = setInterval(function() {
// 檢查賬戶是否切換
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// 調(diào)用一些方法來更新界面
// updateInterface();
getZombiesByOwner(userAccount).then(displayZombies);
}
}, 100);
}
function getZombieDetails(id) {
return cryptoZombies.methods.zombies(id).call()
}
function zombieToOwner(id) {
return cryptoZombies.methods.zombieToOwner(id).call()
}
function getZombiesByOwner(owner) {
return cryptoZombies.methods.getZombiesByOwner(owner).call()
}
window.addEventListener('load', function() {
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider
web3js = new Web3(web3.currentProvider);
} else {
// Handle the case where the user doesn't have Metamask installed
// Probably show them a message prompting them to install Metamask
}
// Now you can start your app & access web3 freely:
startApp()
})
</script>
</body>
</html>
六碍粥、顯示合約數(shù)據(jù)
如果我們不向你展示如何顯示你從合約獲取的數(shù)據(jù),那這個教程就太不完整了黑毅。
在實際應(yīng)用中嚼摩,你肯定想要在應(yīng)用中使用諸如 React 或 Vue.js 這樣的前端框架來讓你的前端開發(fā)變得輕松一些。不過要教授 React 或者 Vue.js 知識的話矿瘦,就大大超出了本教程的范疇——它們本身就需要幾節(jié)課甚至一整個教程來教學(xué)枕面。
所以為了讓 CryptoZombies.io 專注于以太坊和智能合約,我們將使用 JQuery 來做一個快速示例缚去,展示如何解析和展示從智能合約中拿到的數(shù)據(jù)潮秘。
顯示僵尸數(shù)據(jù)
我們已經(jīng)在代碼中添加了一個空的代碼塊 <div id="zombies"></div>
, 在 displayZombies
方法中也同樣有一個易结。
回憶一下在之前章節(jié)中我們在 startApp() 方法內(nèi)部調(diào)用了 displayZombies 并傳入了 call getZombiesByOwner 獲得的結(jié)果枕荞,它將被傳入一個僵尸ID數(shù)組,像這樣:
[0, 13, 47]
因為我們想讓我們的 displayZombies 方法做這些事:
- 1搞动、首先清除 #zombies 的內(nèi)容以防里面已經(jīng)有什么內(nèi)容(這樣當(dāng)用戶切換賬號的時候躏精,之前賬號的僵尸大軍數(shù)據(jù)就會被清除)
- 2、循環(huán)遍歷 id鹦肿,對每一個id調(diào)用 getZombieDetails(id)矗烛, 從我們的合約中獲得這個僵尸的數(shù)據(jù)。
- 3箩溃、將獲得的僵尸數(shù)據(jù)放進一個HTML模板中以格式化顯示瞭吃,追加進 #zombies 里面。
再次聲明涣旨,我們只用了 JQuery虱而,沒有任何模板引擎,所以會非常丑开泽。不過這只是一個如何展示僵尸數(shù)據(jù)的示例而已牡拇。
// 在合約中查找僵尸數(shù)據(jù),返回一個對象
getZombieDetails(id)
.then(function(zombie) {
// 用 ES6 的模板語法來向HTML中注入變量
// 把每一個都追加進 #zombies div
$("#zombies").append(`<div class="zombie">
<ul>
<li>Name: ${zombie.name}</li>
<li>DNA: ${zombie.dna}</li>
<li>Level: ${zombie.level}</li>
<li>Wins: ${zombie.winCount}</li>
<li>Losses: ${zombie.lossCount}</li>
<li>Ready Time: ${zombie.readyTime}</li>
</ul>
</div>`);
});
如何來展示僵尸元素呢穆律?
在上面的例子中惠呼,我們只是簡單地用字符串來顯示 DNA。不過在你的 DApp 中峦耘,你將需要把 DNA 轉(zhuǎn)換成圖片來顯示你的僵尸剔蹋。
我們通過把 DNA 字符串分割成小的字符串來做到這一點,每2位數(shù)字代表一個圖片辅髓,類似這樣:
// 得到一個 1-7 的數(shù)字來表示僵尸的頭:
var head = parseInt(zombie.dna.substring(0, 2)) % 7 + 1
// 我們有7張頭部圖片:
var headSrc = "../assets/zombieparts/head-" + i + ".png"
每一個模塊都用 CSS 絕對定位來顯示泣崩,在一個上面疊加另外一個少梁。
如果你想看我們的具體實現(xiàn),我們將用來展示僵尸形象的 Vue.js 模塊開源了: 點擊這里.
不過矫付,因為那個文件中有太多行代碼凯沪, 超出了本教程的討論范圍。我們依然還是使用上面超級簡單的 JQuery 實現(xiàn)买优,把美化僵尸的工作作為家庭作業(yè)留給你了
實戰(zhàn)演練
我們?yōu)槟銊?chuàng)建了一個空的 displayZombies
方法妨马。來一起實現(xiàn)它。
- 1杀赢、首先我們需要清空 #zombies 的內(nèi)容烘跺。 用JQuery,你可以這樣做: $("#zombies").empty();脂崔。
- 2滤淳、接下來,我們要循環(huán)遍歷所有的 id砌左,循環(huán)這樣用: for (id of ids) {
- 3脖咐、在循環(huán)內(nèi)部,復(fù)制粘貼上面的代碼绊困,對每一個id調(diào)用 getZombieDetails(id)文搂,然后用 $("#zombies").append(...) 把內(nèi)容追加進我們的 HTML 里面适刀。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CryptoZombies front-end</title>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="web3.min.js"></script>
<script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script>
</head>
<body>
<div id="zombies"></div>
<script>
var cryptoZombies;
var userAccount;
function startApp() {
var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS";
cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);
var accountInterval = setInterval(function() {
// Check if account has changed
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// Call a function to update the UI with the new account
getZombiesByOwner(userAccount)
.then(displayZombies);
}
}, 100);
}
function displayZombies(ids) {
// Start here
$("#zombies").empty();
/*
for(id of ids) {
var ele = getZombieDetails(id);
$("#zombies").append(ele);
}
*/
for (id of ids) {
// 獲取到的結(jié)果通過then之后傳給閉包函數(shù)做參數(shù)
getZombieDetails(id)
.then(function(zombie) {
$("#zombies").append(`<div class="zombie">
<ul>
<li>Name: ${zombie.name}</li>
<li>DNA: ${zombie.dna}</li>
<li>Level: ${zombie.level}</li>
<li>Wins: ${zombie.winCount}</li>
<li>Losses: ${zombie.lossCount}</li>
<li>Ready Time: ${zombie.readyTime}</li>
</ul>
</div>`);
});
}
}
function getZombieDetails(id) {
return cryptoZombies.methods.zombies(id).call()
}
function zombieToOwner(id) {
return cryptoZombies.methods.zombieToOwner(id).call()
}
function getZombiesByOwner(owner) {
return cryptoZombies.methods.getZombiesByOwner(owner).call()
}
window.addEventListener('load', function() {
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider
web3js = new Web3(web3.currentProvider);
} else {
// Handle the case where the user doesn't have Metamask installed
// Probably show them a message prompting them to install Metamask
}
// Now you can start your app & access web3 freely:
startApp()
})
</script>
</body>
</html>