从全栈开发者迈向Web3弄潮儿

业无高卑志当坚,男儿有求安得闲?这篇文章主要讲述从全栈开发者迈向Web3弄潮儿相关的知识,希望能为你提供帮助。

?作者丨John Vester
?编译丨陈峻
?策划丨孙淑娟
?本文从 Web1~3 的基本特征讲起,通过一个选举示例,向您展示了全栈开发者将如何迈向 Web3 的技术实践过程。?
?近年来大热的美剧《创业公司(StartUp)》虚构了一种被称为 GenCoin 的数字货币,可用于各种创新式的金融交易场景中。而在我看来,它可以被理解为一种具有 Web3 核心属性的区块链分布式设计产品。如果您对 Web3 还不甚了解的话,让我们先回顾一下 Web 的三个主要时代:?

  • Web1 - 静态网页(1991 年–2004 年),请参见 --https://en.wikipedia.org/wiki/Web_2.0#Web_1.0
  • Web2 - Web 作为一个平台(自 2004 年起),请参见 --https://en.wikipedia.org/wiki/Web_2.0#Web_2.0
  • Web3- 去中心化的设计,包含了区块链技术(自 2009 年起,且近年来发展势头迅猛、前景广阔),请参见 --https://en.wikipedia.org/wiki/Web3

在 Web2 时代,Web 服务主要集中和被控制在诸如:谷歌、苹果和亚马逊等少数技术提供商的手里。而作为 Web2 的替代方案(https://consensys.net/blog/blockchain-explained/what-is-web3-here-are-some-ways-to-explain-it-to-a-friend/),Web3 创建了一个无需准入的数据存储方式。其中不存在任何个人或公司控制或拥有着数据,而且数据的真实性也得到了充分保证。这些数据会被存储在区块链网络中的公共分类账本(public ledger)上。因此,不再是由一个实体拥有数据,而是由多个节点(即:运行着区块链的计算机)存储着数据,并就数据是否有效达成了共识。
从比特币(https://bitcoin.org/en/)到以太坊等协议的应用,Web3 以此类数据存储协议为基础,开启了各种全新的用例。例如:
  • 由用户而非公司控制着个人身份
  • 未经许可的金融系统(如:比特币)可开展贷款、货币、投资等的数字化货币业务
  • 由 NFT(https://en.wikipedia.org/wiki/Non-fungible_token)去证明诸如音乐(https://royal.io/)、艺术(https://www.artblocks.io/)等数字项目的数字所有权
  • 通过去中心化的自治组织(decentralized autonomous organizations,DAO)临时组建具有相同目的的团体,例如:Constitution DAO(https://www.constitutiondao.com/)和 social DAO(https://www.fwb.help/)
  • 通过边玩边赚(Play-to-earn,p2e)的游戏,用户可以在玩游戏的同时,用来谋生(例如 Axie Infinity,https://axieinfinity.com/)
当然,上述应用的关键在于,数字货币的所有权(如:DAO 会员资格、或音乐版权等)都被掌控在用户的手中。在世界上任何地方,只要有互联网连接,任何人都可以自由地交易、销售和构建这些物品,而完全脱离了某个公司或政府的规则控制。对于这样的 Web3 理想主义,我在此不做评判,只是单纯从开发者的角度和您探讨,一个全栈开发者将如何具备 Web3 的技术能力。
从全栈说起?
源于 2015 年的“全栈开发者”一词是指:一个软件工程师可以为任何级别的软件技术栈做出贡献。例如,面对某个与服务层相关的功能性缺陷,刚刚完成了客户端相关任务的同一开发者,可以无缝“接单”,去高效地抓 bug。您可以通过链接 --https://dzone.com/articles/do-not-publishfull-stack-development-truly-possibl,了解更多有关全栈开发的概念。
Web3 基础?
为了深入研究 Web3,我依次创建了一个智能合约,以及一个 Dapp 与之进行交互。其中,
  • 智能合约(https://www.coinbase.com/learn/crypto-basics/what-is-a-smart-contract)是部署在区块链上的一段代码(我下面会以以太坊(https://ethereum.org/en/what-is-ethereum/)为例)。该合约一旦被部署到区块链上,就不可改变、也无需许可(permissionless),但是任何人都可以检索到它。
  • Dapp(decentralized application,去中心化应用)是我们通过 UI(通常来自网页或应用)与智能合约交互的方式。Dapp 会在后端利用智能合约的开放性,采用诸如 IPFS(InterPlanetary File Storage,星际文件存储)的方式,实现文件的分散存储,且不会出现停机。毕竟 DDoS 攻击无法攻击负责存储的每个节点。当然,在考虑部署之前,我们需要针对其安全性,开展全面测试,并处置好代码中的潜在缺陷与漏洞。
Web3 技术栈?
目前,针对 Web3 的成熟技术栈组合,通常包括以下组件:
  • NPM - 备受 Web2 开发人员欢迎的节点包管理器,请参见 --https://nodejs.org/en/
  • Truffle 框架 - 专注于 Web3 的开发工具,请参见 --https://www.trufflesuite.com/
  • Ganache – 可以在本地主机上启动私有区块链,请参见 --https://www.trufflesuite.com/ganache
  • MetaMask - 以太坊的区块链用户界面与网关,属于开源且去中心化的区块链类型,请参见 --https://metamask.io/
  • Solidity – 先进的智能合约编程语言,请参见 --https://solidity.readthedocs.io/en/v0.7.1/
  • html/CSS/javascript - 客户端的层面,请参见 --https://www.w3.org/standards/webdesign/htmlcss
  • Web3.js – 通过以太坊网络交互的以太坊 API 库,请参见 --https://web3js.readthedocs.io/en/v1.3.0/
  • Infura - 授予以太坊网络访问权限的以太坊 API 服务,请参见 --https://infura.io/
以太坊 Dapp 的需求?
假设有一个居委会即将举办定期选举,附近的居民将对一系列的决议进行投票。那么,我们就可以将该选举构建成为一个以太坊 Dapp。由于数据被存储在公开的区块链上,而不是单个公司的私有服务器上,因此任何人都可以通过与智能合约的交互,以无需许可的方式,检索投票结果。据此,投票结果就不存在被篡改或伪造的情况,进而避免了争议的发生。
创建智能合约?
首先,我们需要利用前文提到的:Infura、NPM、Truffle 框架、Ganache、以及 Solidity 等 Web3 技术栈组件,来创建一个能与应用协同的智能合约。其创建的流程如下图所示:

我们可以根据该流程,去招募以太坊的开发者,具体内容请参见链接 --https://consensys.net/developers/onboarding-step-2/。
使用 React 创建 Dapp?
有了智能合约,Web3 工程师便可以使用 NPM、MetaMask、HTML/CSS/javaScript/React、以及 Web3.js 等 Web3 技术栈组件,构建居委会选举的应用。在本例中,我们将采用 React(https://reactjs.org/) 框架和如下流程:

首个以太坊 Dapp?
【从全栈开发者迈向Web3弄潮儿】我会通过 Infura 的注册页面(https://infura.io/register)创建一个免费帐户,并创建一个名为 jvc-homeowners-ballot 的项目:

下图中有关该项目的细节,我会在下文中详细讨论:

Truffle 入门在本地主机上,我创建了一个名为 jvc-homeowners-ballot 的文件夹,并使用 CLI 命令 --truffle init,来初始化 Truffle。初始化完成后的目录结构为:
├── contracts
│└── Migrations.sol
├── migrations
│└── 1_initial_migration.js
├── test
└── truffle-config.js

接着,我用如下命令为基于 Truffle 的钱包 provider,添加了对应的依赖项:
npm install --save @truffle/hdwallet-provider

为了创建本地开发网络,我通过命令 ganache 启动 Ganache CLI。
根据 CLI 的如下响应信息,我们可以看到 Ganache 已在本地主机的 8545 端口上运行:
ganache v7.0.1 (@ganache/cli: 0.1.2, @ganache/core: 0.1.2)
Starting RPC server
Available Accounts
==================
(0) 0x2B475e4fd7F600fF1eBC7B9457a5b58469b9EDDb (1000 ETH)
(1) 0x5D4BB40f6fAc40371eF1C9B90E78F82F6df33977 (1000 ETH)
(2) 0xFaab2689Dbf8b7354DaA7A4239bF7dE2D97e3A22 (1000 ETH)
(3) 0x8940fcaa55D5580Ac82b790F08500741326836e0 (1000 ETH)
(4) 0x4c7a1b7EB717F98Fb0c430eB763c3BB9212F49ad (1000 ETH)
(5) 0x22dFCd5df8d4B19a42cB14E87219fea7bcA7C92D (1000 ETH)
(6) 0x56882f79ecBc2D68947C6936D4571f547890D07c (1000 ETH)
(7) 0xD257AFd8958c6616bf1e61f99B2c65dfd9fEE95A (1000 ETH)
(8) 0x4Bb2EE0866578465E3a2d3eCCC41Ea2313372B20 (1000 ETH)
(9) 0xdf267AeFeAfE4b7053ca10c3d661a8CB24E98236 (1000 ETH)
Private Keys
==================
(0) 0x5d58d27b0f294e3222bbd99a3a1f07a441ea4873de6c3a2b7c40b73186eb616d
(1) 0xb9e52d6cfb2c074fa6a6578b946e3d00ea2a332bb356d0b3198ccf909a97fdc8
(2) 0xc52292ce17633fe2724771e81b3b4015374d2a2ea478891dab74f2028184edeb
(3) 0xbc7b0b4581592e48ffb4f6420228fd6b3f954ac8cfef778c2a81188415274275
(4) 0xc63310ccdd9b8c2da6d80c886bef4077359bb97e435fb4fe83fcbec529a536fc
(5) 0x90bc16b1520b66a02835530020e43048198195239ac9880b940d7b2a48b0b32c
(6) 0x4fb227297dafb879e148d44cf4872611819412cdd1620ad028ec7c189a53e973
(7) 0xf0d4dbe2f9970991ccc94a137cfa7cf284c09d0838db0ce25e76c9ab9f4316d9
(8) 0x495fbc6a16ade5647d82c6ad12821667f95d8b3c376dc290ef86c0d926f50fea
(9) 0x434f5618a3343c5e3b0b4dbeaf3f41c62777d91c3314b83f74e194be6c09416b
HD Wallet
==================
Mnemonic:immense salmon nominee toy jungle main lion universe seminar output oppose hungry
Base HD Path:m/44/60/0/0/account_index
Default Gas Price
==================
2000000000
BlockGas Limit
==================
30000000
Call Gas Limit
==================
50000000
Chain Id
==================
1337
RPC Listening on 127.0.0.1:8545

项目文件夹中的 truffle-config.js 文件,会被激活并更新如下代码行:
JSON
development:
host: "127.0.0.1",// Localhost (default: none)
port: 8545,// Standard Ethereum port (default: none)
network_id: "*",// Any network (default: none)
,

现在,我们可以在新的终端中通过命令 --truffle console,来启动 Truffle 控制台,能显示如下提示:
truffle(development)>

我们可以在控制台中,通过命令 --const HDWalletProvider = require(@truffle/hdwallet-provider); 来创建钱包。当然,它可能会导致未定义的响应。
接下来,我需要通过 Mnemonic Code Converter(https://iancoleman.io/bip39/)网站,生成一个 12 字的助记词(12-word mnemonic phrase,类似私钥),并将其通过如下命令,更新到 Truffle 控制台处:
const mnemonic = 12 words here;
const wallet = new HDWalletProvider(mnemonic, "http://localhost:8545");

上述两条命令虽然也会导致未定义的响应,但是钱包的控制台最终会显示如下运行结果:
truffle(development)> wallet
HDWalletProvider
walletHdpath: "m/44/60/0/0/",
wallets:
...
,
addresses: [
0xa54b012b406c01dd99a6b18ef8b55a15681449af,
0x6d507a70924ea3393ae1667fa88801650b9964ad,
0x1237e0a8522a17e29044cde69b7b10b112544b0b,
0x80b4adb18698cd47257be881684fff1e14836b4b,
0x09867536371e43317081bed18203df4ca5f0490d,
0x89f1eeb95b7a659d4748621c8bdbabc33ac47bbb,
0x54ceb6f0d722dcb33152c953d5758a08045f254d,
0x25d2a8716792b98bf9cce5781b712f00cf33227e,
0x37b6364fb97028830bfeb0cb8d2b14e95e2efa05,
0xe9f56031cb6208ddefcd3cdd5a1a41f7f3400af5
],
...

添加以太坊资金进行测试?
现在我们需要为 Dapp 获取一些测试资金,并使用 Ropsten Ethereum Faucet(https://faucet.ropsten.be/)将资金添加到现有的、由 ConsenSys(https://consensys.net/)创建的 MetaMask(https://metamask.io/index.html)钱包中。当然,为了降低意外情况所导致的真实资金损失的风险,您可以在 MetaMask 中创建多个帐户,其中至少有一个帐户可专用于开发和测试。请记住:永远不要与任何人分享您的助记词,也不要在任何地方上传您的私钥!
如下图所示,为了添加测试资金,我需要输入自己的帐户地址:

如下图所示,通过 Ropsten Etherscan 站点,我们可以验证交易是否能够成功完成:

最终准备步骤?
请使用如下命令将 dotenv 依赖项添加到该项目中:
npm install --save dotenv

接着,请在项目的根目录下创建一个.env 的新文件,并在其中包含如下两行:
INFURA_API_KEY=INSERT YOUR API KEY HERE (no quotations)
MNEMONIC="12 words here"

其中,INFURA_API_KEY 是在创建 jvc-homeowners-ballot 项目时给定的项目 ID。注意:请确保.env 文件被包含在.gitignore 文件中,以避免其他有权访问该存储库的人,擅自使用此机密信息。
最后一项准备步骤是更新 truffle-config.js 文件。我们首先需要在文件的顶部添加如下三行:
JavaScript
require("dotenv").config();
const HDWalletProvider = require("@truffle/hdwallet-provider");

接着,我们利用 dotenv 将如下网络信息,添加至上述依赖项:
JavaScript
ropsten:
provider: () =>
new HDWalletProvider(
process.env.MNEMONIC,
`https://ropsten.infura.io/v3/$process.env.INFURA_API_KEY`
),
network_id: 3, // Ropstens id
gas: 5500000, // Ropsten has a lower block limit than mainnet
confirmations: 2, // # of confs to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out(minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
,

设置智能合约?
准备好了 Infura、Truffle、以及测试资金后,让我们开始设置智能合约。针对前面的居委会选举示例,我们将使用位于本项目 contracts 文件夹中的 JvcHomeownerBallot.sol 合约:
JavaScript
// SPDX-License-Identifier: UNLICENSED(it is common practice to include an open source license or declare it unlicensed)
pragma solidity ^0.8.7; // tells the compiler which version to use


contract Homeowners


// store the addresses of voters on the blockchain in these 2 arrays
address[] votedYes;
address[] votedNo;


function voteYes() public
votedYes.push(msg.sender);



function voteNo() public
votedNo.push(msg.sender);



function getYesVotes() public view returns (uint)
return votedYes.length;



function getNoVotes() public view returns (uint)
return votedNo.length;


正如上面的代码所示,该合同将非常简单,参选居民只需选择是或否即可。其对应的 contracts 文件夹结构如下图所示:
.
├── JvcHomeownersBallot.sol
└── Migrations.sol

有了合约,我们就需要建立部署合约的方法。下面让我们转移到 migrations 文件夹,将如下内容添加到该文件夹下的 2_deploy_contracts.js 文件中:
JavaScript
const JvcHomeownersBallot = artifacts.require("JvcHomeownersBallot.sol");


module.exports = function(deployer)
deployer.deploy(JvcHomeownersBallot);
;

然后,我们可以使用如下命令执行合约的迁移:
truffle migrate --network ropsten

迁移的响应结果为:
Compiling your contracts...
===========================
> Compiling ./contracts/JvcHomeownersBallot.sol
> Artifacts written to /Users/john.vester/projects/jvc/consensys/jvc-homeowners-ballot/build/contracts
> Compiled successfully using:
- solc: 0.8.11+commit.d7f03943.Emscripten.clang
Network up to date.
truffle(development)> truffle migrate --network ropsten
Compiling your contracts...
===========================
> Compiling ./contracts/JvcHomeownersBallot.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to /Users/john.vester/projects/jvc/consensys/jvc-homeowners-ballot/build/contracts
> Compiled successfully using:
- solc: 0.8.11+commit.d7f03943.Emscripten.clang


Starting migrations...
======================
> Network name:ropsten
> Network id:3
> Block gas limit: 8000000 (0x7a1200)


1_initial_migration.js
======================
Deploying Migrations
----------------------
> transaction hash:0x5f227f26a31a3667a689be2d7fa6121a21153eb219873f6fc9aecede221b3b82
> Blocks: 5Seconds: 168
> contract address:0x9e6008B354ba4b9f91ce7b8D95DBC6130324024f
> block number:11879583
> block timestamp:1643257600
> account:0xa54b012B406C01dd99A6B18eF8b55A15681449Af
> balance:1.573649230299520359
> gas used:250142 (0x3d11e)
> gas price:2.506517682 gwei
> value sent:0 ETH
> total cost:0.000626985346010844 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 11879584)
> confirmation number: 2 (block: 11879585)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost:0.000626985346010844 ETH


2_deploy_contracts.js
=====================
Deploying JvcHomeownersBallot
-------------------------------
> transaction hash:0x1bf86b0eddf625366f65a996e633db589cfcef1a4d6a4d6c92a5c1f4e63c767f
> Blocks: 0Seconds: 16
> contract address:0xdeCef6474c95E5ef3EFD313f617Ccb126236910e
> block number:11879590
> block timestamp:1643257803
> account:0xa54b012B406C01dd99A6B18eF8b55A15681449Af
> balance:1.573133154908720216
> gas used:159895 (0x27097)
> gas price:2.507502486 gwei
> value sent:0 ETH
> total cost:0.00040093710999897 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 11879591)
> confirmation number: 2 (block: 11879592)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost:0.00040093710999897 ETH


Summary
=======
> Total deployments:2
> Final cost:0.001027922456009814 ETH


- Blocks: 0Seconds: 0
- Saving migration to chain.
- Blocks: 0Seconds: 0
- Saving migration to chain.

至此,我们已将 JvcHomeownersBallot 智能合约部署到了 Ropsten 网络中。我们可以进一步使用如下 URL,来验证智能合约,并在“Deploying JvcHomeownersBallot”日志中提供合约的地址:
https://ropsten.etherscan.io/

或是:
https://ropsten.etherscan.io/address/0xdeCef6474c95E5ef3EFD313f617Ccb126236910e


使用 React 创建 Dapp?
在上述提到的 jvc-homeowners-ballot 文件夹的同级目录,我将创建一个名为 jvc-homeowners-ballot-client 的目录,通过调用 React CLI 和如下命令,来创建同名的 React 应用:
npx create-react-app jvc-homeowners-ballot-client

接着,我通过如下命令,将 Web3 的依赖项安装到 React 应用中:
cd jvc-homeowners-ballot-client
npm installWeb3

核心的 React 应用一旦就绪,我们就需要建立合约应用的二进制接口(application binary interface,ABI),以便 Dapp 与以太坊生态系统上的各种合约进行通信。
根据 JvcHomeownerBallot.sol 智能合约文件的内容,我们在 build/contracts 文件夹下打开 JvcHomeownersBallet.json 文件,并使用 abi.js 文件的 jvcHomeOwnersBallot 常量的“abi”属性值。具体内容如下:
JavaScript
export const jvcHomeownersBallot = [

"inputs": [],
"name": "voteYes",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
,

"inputs": [],
"name": "voteNo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
,

"inputs": [],
"name": "getYesVotes",
"outputs": [

"internalType": "uint256",
"name": "",
"type": "uint256"

],
"stateMutability": "view",
"type": "function",
"constant": true
,

"inputs": [],
"name": "getNoVotes",
"outputs": [

"internalType": "uint256",
"name": "",
"type": "uint256"

],
"stateMutability": "view",
"type": "function",
"constant": true

];

该文件应当被放置在 React 应用目录 src 的新建子文件夹 abi 内。
下面,我们根据如下配置,从头开始更新 Apps.js:
JavaScript
import React,useStatefrom "react";
importjvcHomeownersBallotfrom "./abi/abi";
importWeb3from "web3";
import "./App.css";


constWeb3= newWeb3(Web3.givenProvider);
const contractAddress = "0xdeCef6474c95E5ef3EFD313f617Ccb126236910e";
const storageContract = newWeb3.eth.Contract(jvcHomeownersBallot, contractAddress);

我们可以通过多种方式找到上面提到的 contactAddress。除了我在此使用的 truffle 的 migrate CLI 命令之外,您还可以使用 Etherscan 站点(https://ropsten.etherscan.io/)。
标准的 React 开发?
在开始标准的 React 开发之前,让我们先来看看完整的 App.js 文件 (如下所示):
JavaScript
import React,useStatefrom "react";
importjvcHomeownersBallotfrom "./abi/abi";
importWeb3from "web3";
import Nav from "./components/Nav.js";
import "./App.css";
importmakeStylesfrom "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import CircularProgress, Grid, Typography from "@material-ui/core";


const useStyles = makeStyles((theme) => (
root:
"& > *":
margin: theme.spacing(1),
,
,
));


constWeb3= newWeb3(Web3.givenProvider);
const contractAddress = "0xdeCef6474c95E5ef3EFD313f617Ccb126236910e";
const storageContract = newWeb3.eth.Contract(jvcHomeownersBallot, contractAddress);


function App()
const classes = useStyles();
const [voteSubmitted, setVoteSubmitted] = useState("");
const [yesVotes, setYesVotes] = useState(0);
const [noVotes, setNoVotes] = useState(0);
const [waiting, setWaiting] = useState(false);


const getVotes = async () =>
const postYes = await storageContract.methods.getYesVotes().call();
setYesVotes(postYes);


const postNo = await storageContract.methods.getNoVotes().call();
setNoVotes(postNo);
;


const voteYes = async () =>
setWaiting(true);


const accounts = await window.ethereum.enable();
const account = accounts[0];
const gas = (await storageContract.methods.voteYes().estimateGas()) * 1.5;
const post = await storageContract.methods.voteYes().send(
from: account,
gas,
);


setVoteSubmitted(post.from);
setWaiting(false);
;


const voteNo = async () =>
setWaiting(true);


const accounts = await window.ethereum.enable();
const account = accounts[0];
const gas = (await storageContract.methods.voteNo().estimateGas() * 1.5);
const post = await storageContract.methods.voteNo().send(
from: account,
gas,
);


setVoteSubmitted(post.from);
setWaiting(false);
;


return (
< div className=classes.root>
< Nav />
< div className="main">
< div className="card">
< Typography variant="h3" gutterBottom>
JVC Homeowners Ballot
< /Typography>


< Typography gutterBottom>
How do you wish to vote?
< /Typography>


< span className="buttonSpan">
< Button
id="yesButton"
className="button"
variant="contained"
color="primary"
type="button"
onClick=voteYes> Vote Yes< /Button>
< div className="divider"/>
< Button
id="noButton"
className="button"
color="secondary"
variant="contained"
type="button"
onClick=voteNo> Vote No< /Button>
< div className="divider"/>
< /span>


waiting & & (
< div>
< CircularProgress />
< Typography gutterBottom>
Submitting Vote ... please wait
< /Typography>
< /div>
)


!waiting & & voteSubmitted & & (
< Typography gutterBottom>
Vote Submitted: voteSubmitted
< /Typography>
)


< span className="buttonSpan">
< Button
id="getVotesButton"
className="button"
color="default"
variant="contained"
type="button"
onClick=getVotes> Get Votes< /Button>
< /span>


(yesVotes > 0 || noVotes > 0) & & (
< div>
< Typography variant="h5" gutterBottom>
Current Results
< /Typography>


< Grid container spacing=1>
< Grid item xs=6>
< div className="resultsAnswer resultsHeader"> Vote< /div>
< /Grid>
< Grid item xs=6>
< div className="resultsValue resultsHeader"> # of Votes< /div>
< /Grid>
< Grid item xs=6>
< div className="resultsAnswer"> Yes< /div>
< /Grid>
< Grid item xs=6>
< div className="resultsValue"> yesVotes< /div>
< /Grid>
< Grid item xs=6>
< div className="resultsAnswer"> No< /div>
< /Grid>
< Grid item xs=6>
< div className="resultsValue"> noVotes< /div>
< /Grid>
< /Grid>
< /div>
)
< /div>
< /div>
< /div>
);



export default App;

运行 Dapp?
我们可以使用 Yarn CLI 的如下命令,来启动基于 React 的 Dapp:
yarn start

在完成编译和验证之后,您会看到如下应用界面:

它拥有三个选项:
  • VOTE YES - 提交赞成票
  • VOTE NO - 提交反对票
  • GET VOTES – 在 Dapp 的下部显示投赞成与反对票的总数
小结?
综上所述,一旦建立了智能合约,从客户端的角度来看,我们将能够沿用 Web2 的如下方面到 Web3 上:
  • 目前在 Web2 项目中常用的 JavaScript 客户端框架,可以被继续使用。
  • NPM 可以被用来包含依赖项,以促进 Web3 的开发。
  • 类似于 Web2 应用程序与传统数据存储的交互方式,Web3 的 Truffle 和 MetaMask 库也允许应用程序与数据进行交互。
  • 现有的业务规则和 UI/UX 设计,将继续满足产品所有者对于 Web3 特性和功能上的要求。
而 Web3 的独特之处主要体现在:
  • 在区块链上构建的 Dapps,都采用同一个事实源向各个信息消费者的请求,提供可靠的数据。
  • 我们不再需要知道“谁”参与了交易、或查询存储在区块链智能合约中的信息(哪怕是别的 Dapp 去访问存储的数据)。由于结果总是固定不变的,因此 Dapp 只需关注应用程序的业务规则。正如上述居委会选举的简单示例那样,无论选票被查询多少次,即便是有另一个 Dapp,其结果总是完全相同的。
  • 由于其分布式的特性,因此控制权被返回给了消费者,而非停留在少数人的手中。
可见,从全栈开发者迈向 Web3 的学习曲线并不陡峭,而且我们可以寻求各种工具、框架和库的帮助。如果您对上述项目所涉及到的源代码感兴趣的话,可以通过如下链接,访问到它在 GitLab 上两个存储库:
??
  • https://gitlab.com/johnjvester/jvc-homeowners-ballot
  • ??
  • ??https://gitlab.com/johnjvester/jvc-homeowners-ballot-client??
  • ????


    推荐阅读