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

點此在 LinkedIn 分享本文 »html

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

若是你錯過了第一部分,你能夠在下面找到,也請在 Twitter 上關注咱們。vue

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

進入正題:VueJS

VueJS 是一個用於構建用戶界面的 JavaScript 框架。初看起來,它相似傳統的 mustache(譯者注:原文爲 moustache)模板,但其實 Vue 在後面作了不少工做。android

<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/ios

VueJS 的另外一個重要特徵是組件。組件是小的、可複用的而且可嵌套的小段代碼。本質上,一個 Web 應用是由較小組件組成的組件樹構成的。當咱們着手編寫咱們前端應用時,咱們會越發清楚。git

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

狀態的整合: Vuex

咱們使用 Vuex 管理應用的狀態。相似於 Redux,Vuex 實現了一個對於咱們應用數據「單一數據源」的容器。Vuex 容許咱們使用一種可預見的方法操做和提供應用程序使用的數據。web

它工做的方式是很是直觀的。當組件須要數據進行渲染時,它會觸發(dispatch)一個 action 獲取所需的數據。Action 中獲取數據的 API 調用是異步的。一旦取得數據,action 會將數據提交(commit)給一個變化(mutation)。而後,Mutation 會使得咱們容器(store)的狀態發生改變(alert the state)。當組件使用的容器中的數據改變時,它會從新進行渲染。vue-router

Vuex 的狀態管理模式。vuex

在咱們繼續以前...

在第一部分中,咱們已經經過 vue-cli 生成了一個 Vue 應用,咱們也安裝了所需的依賴。若是你沒有這樣作的話,請查看上面第一部分的連接。

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

新生成的 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 那段中的描述狀態管理模式信息。

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

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

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

若是你喜歡本教程的話,請讓咱們知道,謝謝你堅持讀到最後。

ETH — 0x6d31cb338b5590adafec46462a1b095ebdc37d50


想完成本身的想法嗎?咱們提供以太坊(Ethereum)概念驗證和開發衆募。


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

相關文章
相關標籤/搜索