DAPP開發初探
阿新 • • 發佈:2019-01-27
前言
最近DAPP的開發貌似很火,學習了區塊鏈的一些知識之後,相信有很多人和我一樣,也想了解開發一個DAPP是一個怎樣的流程。
下面將通過一個簡單的栗子來初識一下DAPP的開發流程,屆時,我們也將開發出第一個DAPP應用–《永存的留言》。
專案介紹
《永存的留言》是一個基於以太坊的線上留言平臺。它的功能十分簡單–使用者可以在平臺上進行留言,平臺每10s隨機的展示留言內容。
但是它的特點在於,利用區塊鏈的特性,保證了資料的真實性、完整性和安全性。
- 使用Solidity開發後端方法
- 使用Truffle框架
- 基於
unbox react
腳手架專案 - 部署在以太坊測試網路上
Ropoetn Test Network
- 使用MetaMask錢包外掛釋出交易
開發步驟
下載react專案模板
確保本地已經準備好Truffle所需的環境,準備以下命令,下載react專案模板。
$ mkdir a && cd a
truffle unbox react
當看到 Unbox successful. Sweet!
提示時,表明下載成功。
編寫智慧合約
這是我們唯一的實現合約,包含的傳送留言和讀取留言的方法。
pragma solidity ^0.4.19;
contract SimpleStorage {
// 留言結構體
struct Message {
string word; // 留言
address from; // 留言者地址
string timestamp ; // 留言unix時間戳
}
Message[] private wordArr;
/**
* 寫入留言的方法
*/
function setWord(string s, string t) public {
wordArr.push(Message({
word: s,
from: msg.sender,
timestamp: t
}));
}
/**
* 獲取隨機留言的方法
*/
function getRandomWord(uint random) public view returns (uint, string, address, string) {
if(wordArr.length==0){
return (0, "", msg.sender, "");
}else{
Message storage result = wordArr[random];
return (wordArr.length, result.word, result.from, result.timestamp);
}
}
}
編譯、部署合約
修改釋出的指令碼。
var SimpleStorage = artifacts.require("./SimpleStorage.sol");
//var Words = artifacts.require("Words");
module.exports = function(deployer) {
deployer.deploy(SimpleStorage);
//deployer.deploy(Words);
};
執行truffle compile
進行合約的編譯。
獲取合約地址。
- 這裡我們開啟MetaMask錢包外掛,左上角選擇
Ropoetn Test Network
網路.
- 利用Remix編譯,釋出合約到以太坊測試環境。
- 通過MetaMask的交易hash查詢,獲取已經部署到以太坊測試網路中的合約地址。
編寫前端頁面
這個部分主要是編寫前端的展示效果和與合約互動的邏輯,這一部分最難編寫,也最耗時間。
- 主要邏輯程式碼
const contractAddress = "0x39e5196750dcddb1aaf6dda7d6e8dbb633482905" // 合約地址(以太坊測試網路)
var simpleStorageInstance // 合約例項
class App extends Component {
// 初始化構造
constructor(props) {
super(props)
this.state = {
word: null,
from: null,
timestamp: null,
random: 0,
count: 0,
input: '',
web3: null,
emptyTip: "還沒有留言,快來建立全世界第一條留言吧~",
firstTimeLoad: true,
loading: false,
loadingTip: "留言正在寫入,請耐心等待~",
waitingTip: "留言正在寫入,請耐心等待~",
successTip: "留言成功",
animate: "",
in: css(styles.in),
out: css(styles.out)
}
}
// 獲取Web3例項
componentWillMount() {
// Get network provider and web3 instance.
getWeb3
.then(results => {
this.setState({
web3: results.web3
})
// Instantiate contract once web3 provided.
this.instantiateContract()
})
.catch(() => {
console.log('Error finding web3.')
})
}
// 獲取合約例項
instantiateContract() {
/*
* SMART CONTRACT EXAMPLE
*
* Normally these functions would be called in the context of a
* state management library, but for convenience I've placed them here.
*/
const contract = require('truffle-contract')
const simpleStorage = contract(SimpleStorageContract)
simpleStorage.setProvider(this.state.web3.currentProvider)
// Get accounts.
this.state.web3.eth.getAccounts((error, accounts) => {
simpleStorage.at(contractAddress).then(instance => {
simpleStorageInstance = instance
/*simpleStorage.deployed().then((instance) => {
simpleStorageInstance = instance // 部署本地Ganache*/
console.log("合約例項獲取成功")
})
.then(result => {
return simpleStorageInstance.getRandomWord(this.state.random)
})
.then(result => {
console.log("讀取成功", result)
if(result[1]!=this.setState.word){
this.setState({
animate: this.state.out
})
setTimeout(() => {
this.setState({
count: result[0].c[0],
word: result[1],
from: result[2],
timestamp: result[3],
animate: this.state.in,
firstTimeLoad: false
})
}, 2000)
}else{
this.setState({
firstTimeLoad: false
})
}
this.randerWord()
})
})
}
// 迴圈從區塊上隨機讀取留言
randerWord() {
setInterval(() => {
let random_num = Math.random() * (this.state.count? this.state.count: 0)
this.setState({
random: parseInt(random_num)
})
console.log("setInterval讀取", this.state.random)
simpleStorageInstance.getRandomWord(this.state.random)
.then(result => {
console.log("setInterval讀取成功", result)
if(result[1]!=this.setState.word){
this.setState({
animate: this.state.out
})
setTimeout(() => {
this.setState({
count: result[0].c[0],
word: result[1],
from: result[2],
timestamp: result[3],
animate: this.state.in
})
}, 2000)
}
})
}, 10000)
}
// 寫入區塊鏈
setWord(){
if(!this.state.input) return
this.setState({
loading: true
})
let timestamp = new Date().getTime()
simpleStorageInstance.setWord(this.state.input, String(timestamp), {from: this.state.web3.eth.accounts[0]})
.then(result => {
this.setState({
loadingTip: this.state.successTip
})
setTimeout(() => {
this.setState({
loading: false,
input: '',
loadingTip: this.state.waitingTip
})
}, 1500)
})
.catch(e => {
// 拒絕支付
this.setState({
loading: false
})
})
}
// 時間戳轉義
formatTime(timestamp) {
let date = new Date(Number(timestamp))
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
let hour = date.getHours()
let minute = date.getMinutes()
let second = date.getSeconds()
let fDate = [year, month, day, ].map(this.formatNumber)
return fDate[0] + '年' + fDate[1] + '月' + fDate[2] + '日' + ' ' + [hour, minute, second].map(this.formatNumber).join(':')
}
/** 小於10的數字前面加0 */
formatNumber(n) {
n = n.toString()
return n[1] ? n : '0' + n
}
}
執行專案
使用npm start
啟動專案,瀏覽器的3000埠執行。
效果如下(一定要連線到Ropoetn Test Network
網路才可以)。
總結
這樣我們就開發出了我們的第一個DAPP,體會了開發的基本流程。
- 明確專案需求
- 確定開發環境
- 編寫、測試、部署合約
- 設計前端頁面,使前後端互動
- 釋出測試