# Web3 开发极简教程001：一文快速学会DAPP前端开发

By [nobodyjack.eth](https://paragraph.com/@nobodyjack-eth) · 2022-08-13

---

**文：nobodyjack （Twitter: @ethcrap）**

前言
--

最近开始了解web3 DAPP开发，了解到一个完整DAPP开发主要包含如下两部分：

*   智能合约：通常指代运行在 EVM 兼容网络中的 Solidity 或其他合约语言代码，负责与用户交易我们发行的资产并储存 DApp 的链上状态。
    
*   DAPP前端：负责应用程序界面及与智能合约交互，它们大部分是 Web APP，可以用原生前端语言或流行框架例如 React/Vue 来进行编写。
    

我没什么编程基础，所以放弃直接从学习智能合约入门，转而先从DAPP前端开始学习，这样入门学习会简单很多，反馈也更快，可以避免自己中途放弃（挑软柿子捏更容易成功，持续反馈是坚持学习的关键）。

我一直非常推崇费曼学习法，简化概念、以教代学，因此后面我会持续更新本系列教程，希望能帮助自己和他人学习。

**我也是小白，如有错误，欢迎指教！**

基础&准备
-----

### 1）理解Signer、JSON RPC、Provider与Metamask

为了让大家对DAPP开发有个全貌了解，先祭一张DAPP架构神图

对于DAPP前端开发，图中有几点至关重要的概念需要理解：

*   Provider：节点提供者，这是一个 DApp 架构中特殊的角色，它负责与区块链进行通信，它提供对区块链及其状态的只读访问。
    
*   Signer：签名者，一个至关重要的角色，它负责签署消息和交易，链上所有交易都需要签名才能执行。
    
*   Ethereum JSON-RPC：以太坊通信协议接口，所有的节点客户端都需要实现该接口，该接口提供一组统一的方法供DAPP调用，实现DAPP与链进行交互（读取区块链数据或发送交易信息到链上）。
    
    Metamask作为一个浏览器扩展插件，既是Provider，也是一个Signer。
    

### 2）环境与工具

*   Chrome浏览器（不建议用其他浏览器）
    
*   安装MetaMask钱包插件，并创建或导入账号，切换到测试网Ropsten节点
    
*   测试网领水：[https://faucet.paradigm.xyz](https://faucet.paradigm.xyz)   [https://faucet.metamask.io/](https://faucet.metamask.io/)
    
*   etherscan：以太坊区块浏览器
    

### 3）技术栈

*   html、css
    
*   javascript
    
*   ethers.js：以太坊区块链及其生态系统进行交互的JavaScript库
    

需求分析
----

一个简单的DAPP 前端Demo，主要涉及如下几个通用功能：

*   登录
    
    *   连接钱包（MetaMask）
        
    *   签署消息
        
*   转账
    
*   调用智能合约只读方法
    
*   调用智能合约写方法
    

页面
--

本教程为了简便，直接用HTML+CSS，并在HTML中引入ethers.js

    <script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>
    

**做了一个毫无设计的页面，大家可以不用参照，可以自己设计😂**

![](https://storage.googleapis.com/papyrus_images/7e42ecfe617f1ac5e99fe6eb68a386a678e2c2ad59aa85aa84a8d6e1868748ee.png)

登录：连接钱包、签署消息
------------

登录是最常见的功能之一，不少应用都会要求用户登录才能使用应用或部分功能。与传统应用不同的是，对于一些 DAPP，你只需要一个钱包就可以访问它，它完全没有帐户、用户名、密码、个人数据。

也就是说，对于DAPP来说，登录意味着“连接钱包、访问钱包中的账户”

另外，为了保证安全，DAPP通常也会要求用户在钱包签署一条消息 ，以验证账户控制权限，而不是他人使用用户的钱包/资金进行欺诈行为。

简化流程： 登录 —>  连接钱包 —> 签署消息 —> 使用DAPP

### 1）连接钱包,获取当前账户

    //HTML
    <button class="btn btn-primary btn-lg btn-block mb-3" id="connectButton" >  
    Connect Wallet</button>
    <p class="info-text alert alert-success">Account : 
    <span id="showAccount"></span>
    </p>
    

MetaMask将一个全局Provider注入浏览器每个页面的window.ethereum，ethers库提供一个方法可将该Provider包装成一个新的可供 ethers.js 使用的Web3Provider。

“连接”到 MetaMask 实际上意味着“访问用户的以太坊帐户”：

    //javacriptconst 
    connectButton = document.querySelector('#connectButton');const showAccount = document.querySelector('#showAccount');
    var currentAccount ='';
    connectButton.addEventListener('click', () => {    getAccount();});
    async function getAccount() {   
     // const accounts = await ethereum.request({ method: 'eth_requestAccounts' });    
    if (window.ethereum) {
            const provider = new ethers.providers.Web3Provider(window.ethereum);
            const accounts = await provider.send("eth_requestAccounts", []);        currentAccount = accounts[0];        showAccount.innerHTML = currentAccount;
        } else {
            alert('Please install MetaMask ');
        }
    }
    

*   **签署消息**
    
    要签署消息，必须先获得一个signer实例，然后通过signMessage方法签署消息。
    

    //HTML
    <button class="btn btn-primary btn-lg btn-block mb-3" id="signButton">Sign Message</button>
    <p class="info-text alert alert-warning">Result :   <span id="signResult"></span>
    </p>
    

    //javacriptconst 
    signButton = document.querySelector('#signButton')
    const signResult = document.querySelector('#signResult')
    signButton.addEventListener('click', () => {    signMessage();});
    async function signMessage() {    
      if (currentAccount !== '') {        
        console.log(typeof account);        
        const provider = new    ethers.providers.Web3Provider(window.ethereum);        
        const signer = provider.getSigner();        
        const result = await signer.signMessage("Hello World");        
        signResult.innerHTML = result;    
       } else {        
          alert('Please connect to MetaMask. ');    
          }
       }
    

转账：发送ETH
--------

signer提供了一个sendTransaction方法用于发送交易：

    //HTML
    <div class="card full-width">    
      <div class="card-body">        
        <h4 class="card-title"> Send ETH </h4>
            <div class="form-group">
                <label>To</label>
                <input class="form-control" type="text" id="toInput" value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" />
            </div>
            <div class="form-group">
                <label>Amount</label>
                <input class="form-control" type="text" id="amountInput" value="0" />
            </div>        
    <div class="form-group">
                <label>Data</label>
                <input class="form-control" type="text" id="dataInput" />        </div>
            <button class="btn btn-primary btn-lg btn-block mb-3" id="submitForm">Submit</button>
        </div>
    </div>
    

    //javacript
    const toInput = document.querySelector('#toInput');
    const amountInput = document.querySelector('#amountInput');
    const dataInput = document.querySelector('#dataInput');
    const submitForm = document.querySelector('#submitForm');
    submitForm.addEventListener('click', () => {
        sendETH();
    });
    async function sendETH() {
        if (window.ethereum) {
            const amount = amountInput.value.trim();
            const to = toInput.value.trim();
            let data = dataInput.value.trim();
            if (to !== '' && amount !== '') {
                const provider = new ethers.providers.Web3Provider(window.ethereum);
                const signer = provider.getSigner();
                const tx = {};
                tx.to = to;
                tx.value = ethers.utils.parseEther(amount);            if (data != '') {
                    tx.data = ethers.utils.isHexString(data) ? data : '0x' + data;
                }
                await signer.sendTransaction(tx).catch(err =>{                alert(err.message);
                });
            } else {
                alert('to or amount can not blank');
            }
        } else {
            alert('Please connect to MetaMask. ');
        }}
    

调用合约的只读方法
---------

**调用只读方法（由Solidity 表示**`pure`或`view`在 Solidity 中表示），可以由节点返回结果，因此它是免费的，但**不能更改区块链状态。**

调用合约的方法，需要合约的ABI和地址，我们直接在etherscan.io找个Ropsten测试网的开源合约，可以查到合约的源代码、ABI及合约，这里以NFT Doodles 的合约为例：

[https://ropsten.etherscan.io/address/0xa86ea6e910ed86ad53807f533e1550b569b47ad4#code](https://ropsten.etherscan.io/address/0xa86ea6e910ed86ad53807f533e1550b569b47ad4#code)

调用合约的balanceof方法，查询地址持有该NFT的mint数量：

    //HTML
    <div class="card full-width">
        <div class="card-body">
            <h4 class="card-title">
                Mint
            </h4>
            <div class="form-group">
                <label>Num</label>
                <input class="form-control" type="text" id="numInput"                value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" />
            </div>
            <button class="btn btn-primary btn-lg btn-block mb-3" id="mintButton">
                Mint
            </button>
            <p class="info-text alert alert-warning">
                Hash Result: <span id="mintResult"></span>
            </p>
        </div>
    </div>
    

    //javacript
    queryButton.addEventListener('click', () => {
        const owner = ownerInput.value.trim();
        queryAvailableNum(owner);});
    async function queryAvailableNum(owner) {
        const provider = new   ethers.providers.Web3Provider(window.ethereum);
        const url = 'abi.json';
        const abi = await getABI(url);
        console.log(abi);
        const contract_adress = '0xA86eA6e910ED86aD53807F533e1550B569B47AD4';
        const contract = new ethers.Contract(contract_adress, abi, provider);
        const num = await contract.balanceOf(owner) 
        console.log(num);
        nftNum.innerHTML = num;}
    async function getABI(url) {
        const res = await fetch(url);
        return await res.json();}
    

调用合约的写方法
--------

调用写方法签署交易，并需要支付给矿工费用，该交易将由整个网络上的每个节点以及矿工验证，矿工将在针对当前状态执行区块链后计算区块链的新状态。

    //HTML
    <div class="card full-width">
        <div class="card-body">
            <h4 class="card-title">
                Query Doodle Num
            </h4>
            <div class="form-group">
                <label>owner</label>
                <input class="form-control" type="text" id="ownerInput"                value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" />
            </div>
            <button class="btn btn-primary btn-lg btn-block mb-3" id="queryButton">
                Query
            </button>
            <p class="info-text alert alert-warning">
                Doodle Num: <span id="nftNum"></span>
            </p>
        </div>
    </div>
    

    //javacript
    <div class="card full-width">
        <div class="card-body"> 
           <h4 class="card-title">
                Mint
            </h4>
            <div class="form-group">
                <label>Num</label>
                <input class="form-control" type="text" id="numInput"                value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" />
            </div>
            <button class="btn btn-primary btn-lg btn-block mb-3" id="mintButton">
                Mint
            </button>
            <p class="info-text alert alert-warning">
                Hash Result: <span id="mintResult"></span>
            </p>
        </div>
    </div>
    

完整实现
----

把以上的功能组合起来就可以实现一个简单完整的DAPP前端Demo，这里我只是抛砖引玉，剩下的工作就交给你们了。

### 如果你觉得对你有用，请关注我

**Mirror:** 

[https://mirror.xyz/0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E](https://mirror.xyz/0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E)

**Twitter：**

[https://twitter.com/ethcrap](https://twitter.com/ethcrap)

**Medium：**

[https://medium.com/@nobodyjack2050](https://medium.com/@nobodyjack2050)

---

*Originally published on [nobodyjack.eth](https://paragraph.com/@nobodyjack-eth/web3-001-dapp)*
