[譯] 使用 Web3 和 Vue.js 來建立你的第一個以太坊 dAPP(第二部分)

L9m發表於2018-03-27

點此在 LinkedIn 分享本文 »

歡迎回到這個很棒的系列教程的第二部分,在個教程中我們要親身實踐,建立我們的第一個去中心化應用(decentralized application)。在第二部分中,我們將介紹 VueJS 和 VueX 的核心概念以及 web3js 與 metamask 的互動。

如果你錯過了第一部分,你可以在下面找到,也請在 Twitter 上關注我們。

Snipaste_2018-03-18_17-25-07.png

進入正題:VueJS

VueJS 是一個用於構建使用者介面的 JavaScript 框架。初看起來,它類似傳統的 mustache(譯者注:原文為 moustache)模板,但其實 Vue 在後面做了很多工作。

<div id=”app”>
 {{ message }}
</div>

var app = new Vue({
 el: `#app`,
 data: {
 message: `Hello Vue!`
 }
})
複製程式碼

這是一個很基本的 Vue 應用的結構。資料物件中的 message 屬性會被渲染到螢幕上 id 為「app」的元素中,當我們改變 message 時,螢幕上的值也會實時更新。你可以去這個 jsfiddle 上檢視(開啟自動執行):jsfiddle.net/tn1mfxwr/2/

VueJS 的另一個重要特徵是元件。元件是小的、可複用的並且可巢狀的小段程式碼。本質上,一個 Web 應用是由較小元件組成的元件樹構成的。當我們著手編寫我們前端應用時,我們會愈加清楚。

[譯] 使用 Web3 和 Vue.js 來建立你的第一個以太坊 dAPP(第二部分)

這個頁面示例是由元件構成的。頁面由三個元件組成的,其中的兩個有子元件。

狀態的整合: Vuex

我們使用 Vuex 管理應用的狀態。類似於 Redux,Vuex 實現了一個對於我們應用資料「單一資料來源」的容器。Vuex 允許我們使用一種可預見的方法操作和提供應用程式使用的資料。

它工作的方式是非常直觀的。當元件需要資料進行渲染時,它會觸發(dispatch)一個 action 獲取所需的資料。Action 中獲取資料的 API 呼叫是非同步的。一旦取得資料,action 會將資料提交(commit)給一個變化(mutation)。然後,Mutation 會使得我們容器(store)的狀態發生改變(alert the state)。當元件使用的容器中的資料改變時,它會重新進行渲染。

[譯] 使用 Web3 和 Vue.js 來建立你的第一個以太坊 dAPP(第二部分)

Vuex 的狀態管理模式。

在我們繼續之前…

在第一部分中,我們已經通過 vue-cli 生成了一個 Vue 應用,我們也安裝了所需的依賴。如果你沒有這樣做的話,請檢視上面第一部分的連結。

如果你正確完成了各項的話,你的目錄看起來應該是這樣的:

[譯] 使用 Web3 和 Vue.js 來建立你的第一個以太坊 dAPP(第二部分)

新生成的 vue 應用。

小提示:如果你要從這裡複製貼上程式碼段的話,請在你的 .eslintignore 檔案中新增 /src/,以免出現縮排錯誤。

你可以在終端中輸入 npm start 執行這個應用。首先我們需要清理它包含的這個預設的 Vue 應用。
註解:儘管只有一個路由,但是我們還是會使用 Vue Router,雖然我們並不需要,但是因為這個教程相當簡單,我想將其保留會更好。
貼士:在你的 Atom 編輯器右下角中將 .vue 檔案設定為 HTML 語法(高亮)

現在處理這個剛生成的應用:

  • 在 app.vue 中刪除 img 標籤和 style 標籤中的內容。
  • 刪除 components/HelloWorld.vue,建立兩個名為 casino-dapp.vue(我們的主元件)和 hello-metamask.vue(將包含我們的 Metamask 資料)的兩個新檔案。
  • 在我們的新 hello-metamask.vue 檔案中貼上下面的程式碼,它現在只顯示了在一個 p 標籤內的「hello」文字。
<template>
 <p>Hello</p>
</template>

<script>
export default {
 name: `hello-metamask`
}
</script>

<style scoped>

</style>
複製程式碼
  • 現在我們首先匯入 hello-metamask 元件檔案,通過匯入檔案將其載入到主元件 casino-app 中,然後在我們的 vue 例項中,引用它作為模板中一個標籤。在 casino-dapp.vue 中貼上這些程式碼:
<template>
 <hello-metamask/>
</template>

<script>
import HelloMetamask from `@/components/hello-metamask`
export default {
 name: `casino-dapp`,
 components: {
 `hello-metamask`: HelloMetamask
 }
}
</script>

<style scoped>

</style>
複製程式碼
  • 現在如果你開啟 router/index.js 你會看到 root 下只有一個路由,它現在仍指向我們已刪除的 HelloWorld.vue 元件。我們需要將其指向我們主元件 casino-app.vue。
import Vue from `vue`
import Router from `vue-router`
import CasinoDapp from `@/components/casino-dapp`

Vue.use(Router)

export default new Router({
 routes: [
 {
 path: `/`,
 name: `casino-dapp`,
 component: CasinoDapp
 }
 ]
})
複製程式碼

關於 Vue Router:你可以增加額外的路徑併為其繫結元件,當你訪問定義的路徑時,在 App.vue 檔案中的 router-view 標籤中,對應的元件會被渲染,並進行顯示。

  • src 中建立一個名為 util 的新資料夾,在這個資料夾中建立另一個名為 constants 的新資料夾,並建立一個名為 networks.js 的檔案,貼上下面的程式碼。我們用 ID 來代替以太坊(Ethereum)網路名稱顯示,這樣做會保持我們程式碼的整潔。
export const NETWORKS = {
 `1`: `Main Net`,
 `2`: `Deprecated Morden test network`,
 `3`: `Ropsten test network`,
 `4`: `Rinkeby test network`,
 `42`: `Kovan test network`,
 `4447`: `Truffle Develop Network`,
 `5777`: `Ganache Blockchain`
}
複製程式碼
  • 最後的但同樣重要的(實際上現在用不到)是,在 src 中建立一個名為 store 的新資料夾。我們將在下一節繼續討論。

如果你在終端中執行 npm start,並在瀏覽器中訪問 localhost:8000,你應該可以看到「Hello」出現在螢幕上。如果是這樣的話,就表示你準備好進入下一步了。

設定我們的 Vuex 容器

在這一節中,我們要設定我們的容器(store)。首先從在 store 目錄(上一節的最後一部分)下建立兩個檔案開始:index.jsstate.js;我們先從 state.js 開始,它是我們所檢索的資料一個空白表示(Blank representation)。

let state = {
 web3: {
 isInjected: false,
 web3Instance: null,
 networkId: null,
 coinbase: null,
 balance: null,
 error: null
 },
 contractInstance: null
}
export default state
複製程式碼

好了,現在我們要對 index.js 進行設定。我們會匯入 Vuex 庫並且告訴 VueJS 使用它。我們也會把 state 匯入到我們的 store 檔案中。

import Vue from `vue`
import Vuex from `vuex`
import state from `./state`

Vue.use(Vuex)

export const store = new Vuex.Store({
 strict: true,
 state,
 mutations: {},
 actions: {}
})
複製程式碼

最後一步是編輯 main.js ,以包含我們的 store 檔案:

import Vue from `vue`
import App from `./App`
import router from `./router`
import { store } from `./store/`

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
 el: `#app`,
 router,
 store,
 components: { App },
 template: `<App/>`
})
複製程式碼

幹得好!因為這裡有很多設定,(所以請)給你自己一點鼓勵。現在已經準備好通過 web3 API 獲取我們 Metamask 的資料,並使其在我們的應用發揮作用了。該來點真的了!

入門 Web3 和 Metamask

就像前面提到的,為了讓 Vue 應用能獲取到資料,我們需要觸發(dispatch)一個 action 執行非同步的 API 呼叫。我們會使用 promise 將幾個方法鏈式呼叫,並將這些程式碼提取(封裝)到檔案 util/getWeb3.js 中。貼上以下的程式碼,其中包含了一些有助你遵循的註釋。我們會在程式碼塊下面對它進行解析:

import Web3 from `web3`

/*
* 1. Check for injected web3 (mist/metamask)
* 2. If metamask/mist create a new web3 instance and pass on result
* 3. Get networkId - Now we can check the user is connected to the right network to use our dApp
* 4. Get user account from metamask
* 5. Get user balance
*/

let getWeb3 = new Promise(function (resolve, reject) {
  // Check for injected web3 (mist/metamask)
  var web3js = window.web3
  if (typeof web3js !== `undefined`) {
    var web3 = new Web3(web3js.currentProvider)
    resolve({
      injectedWeb3: web3.isConnected(),
      web3 () {
        return web3
      }
    })
  } else {
    // web3 = new Web3(new Web3.providers.HttpProvider(`http://localhost:7545`)) GANACHE FALLBACK
    reject(new Error(`Unable to connect to Metamask`))
  }
})
  .then(result => {
    return new Promise(function (resolve, reject) {
      // Retrieve network ID
      result.web3().version.getNetwork((err, networkId) => {
        if (err) {
          // If we can`t find a networkId keep result the same and reject the promise
          reject(new Error(`Unable to retrieve network ID`))
        } else {
          // Assign the networkId property to our result and resolve promise
          result = Object.assign({}, result, {networkId})
          resolve(result)
        }
      })
    })
  })
  .then(result => {
    return new Promise(function (resolve, reject) {
      // Retrieve coinbase
      result.web3().eth.getCoinbase((err, coinbase) => {
        if (err) {
          reject(new Error(`Unable to retrieve coinbase`))
        } else {
          result = Object.assign({}, result, { coinbase })
          resolve(result)
        }
      })
    })
  })
  .then(result => {
    return new Promise(function (resolve, reject) {
      // Retrieve balance for coinbase
      result.web3().eth.getBalance(result.coinbase, (err, balance) => {
        if (err) {
          reject(new Error(`Unable to retrieve balance for address: ` + result.coinbase))
        } else {
          result = Object.assign({}, result, { balance })
          resolve(result)
        }
      })
    })
  })

export default getWeb3
複製程式碼

第一步要注意的是我們使用 promise 連結了我們的回撥方法,如果你不太熟悉 promise 的話,請參考此連結。下面我們要檢查使用者是否有 Metamask(或 Mist)執行。Metamask 注入 web3 本身的例項,所以我們要檢查 window.web3(注入的例項)是否有定義。如果是否的話,我們會用 Metamask 作為當前提供者(currentProvider)建立一個 web3 的例項,這樣一來,例項就不依賴於注入物件的版本。我們把新建立的例項傳遞給接下來的 promise,在那裡我們做幾個 API 呼叫:

  • web3.version.getNetwork() 將返回我們連線的網路 ID。
  • web3.eth.coinbase() 返回我們節點挖礦的地址,當使用 Metamask 時,它應該會是已選擇的賬戶。
  • web3.eth.getBalance(<address>) 返回作為引數傳入的該地址的餘額。

還記得我們說過 Vuex 容器中的 action 需要非同步地進行 API 呼叫嗎?我們在這裡將其聯絡起來,然後再從元件中將其觸發。在 store/index.js 中,我們會匯入 getWeb3.js 檔案,呼叫它,然後將其(結果)commit 給一個 mutation,並讓其(狀態)保留在容器中。

在你的 import 宣告中增加:

import getWeb3 from `../util/getWeb3`
複製程式碼

然後在(store 內部)的 action 物件中呼叫 getWeb3commit 其結果。我們會新增一些 console.log 在我們的邏輯中,這樣做是希望讓 dispatch-action-commit-mutation-statechange 流程更加清楚,有助於我們理解整個執行的步驟。

registerWeb3 ({commit}) {
      console.log(`registerWeb3 Action being executed`)
      getWeb3.then(result => {
        console.log(`committing result to registerWeb3Instance mutation`)
        commit(`registerWeb3Instance`, result)
      }).catch(e => {
        console.log(`error in action registerWeb3`, e)
      })
    }
複製程式碼

現在我們要建立我們的 mutation,它會將資料儲存為容器中的狀態。通過訪問第二個引數,我們可以訪問我們 commit 到 mutation 中的資料。在 mutations 物件中增加下面的方法:

registerWeb3Instance (state, payload) {
 console.log(`registerWeb3instance Mutation being executed`, payload)
 let result = payload
 let web3Copy = state.web3
 web3Copy.coinbase = result.coinbase
 web3Copy.networkId = result.networkId
 web3Copy.balance = parseInt(result.balance, 10)
 web3Copy.isInjected = result.injectedWeb3
 web3Copy.web3Instance = result.web3
 state.web3 = web3Copy
 }
複製程式碼

很棒!現在剩下要做的是在我們的元件中觸發(dispatch)一個 action,取得資料並在我們的應用中進行呈現。為了觸發(dispatch)action,我們將會用到 Vue 的生命週期鉤子。在我們的例子中,我們要在它建立之前觸發(dispatch)action。在 components/casino-dapp.vue 中的 name 屬性下增加以下方法:

export default {
  name: `casino-dapp`,
  beforeCreate () {
    console.log(`registerWeb3 Action dispatched from casino-dapp.vue`)
    this.$store.dispatch(`registerWeb3`)
  },
  components: {
    `hello-metamask`: HelloMetamask
  }
}
複製程式碼

很好!現在我們要渲染 hello-metamask 元件的資料,我們賬戶的所有資料都將在此元件中進行呈現。從容器(store)中獲得資料,我們需要在計算屬性中增加一個 getter 方法。然後,我們就可以在模板中使用大括號來引用資料了。

<template>
 <div class=`metamask-info`>
   <p>Metamask: {{ web3.isInjected }}</p>
   <p>Network: {{ web3.networkId }}</p>
   <p>Account: {{ web3.coinbase }}</p>
   <p>Balance: {{ web3.balance }}</p>
 </div>
</template>

<script>
export default {
 name: `hello-metamask`,
 computed: {
   web3 () {
     return this.$store.state.web3
     }
   }
}
</script>

<style scoped></style>
複製程式碼

太棒啦!現在一切都應該完成了。在你的終端(terminal)中通過 npm start 啟動這個專案,並訪問 localhost:8080。現在,我們可以看到 Metamask 的資料。當我們開啟控制檯,應該可以看到 console.log 輸出的 —— 在 Vuex 那段中的描述狀態管理模式資訊。

[譯] 使用 Web3 和 Vue.js 來建立你的第一個以太坊 dAPP(第二部分)

說真的,如果你走到了這一步並且一切正常,那麼你真的很棒!這是本系列教程目前為止,難度最大的一部分。在下一部分中,我們將學到如何輪詢 Metamask(如:賬戶切換)的變化,並將在第一部分描述智慧合約與我們的應用相連線。

以防萬一你出現錯誤,在這個 Github 倉庫 的 hello-metamask 分支上有此部分完整的程式碼

不要錯過本系列的最後一部分

如果你喜歡本教程的話,請讓我們知道,謝謝你堅持讀到最後。

ETH — 0x6d31cb338b5590adafec46462a1b095ebdc37d50


想完成自己的想法嗎?我們提供以太坊(Ethereum)概念驗證和開發眾募。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章