-------------------------鸡西新闻,仅此一站就能够解决您多数生活服务,我们专注鸡西新闻发布,24小时不间断随时更新,浏览页面流畅,快速专业报道鸡西社会、时政新闻,我们还具备国际视野,报道国内外的重大经济军事要闻,专业时评也随时为您跟进更新,此外,您可以一站实现旅游路线和美食查询、家居、教育、二手买卖的生活服务查询,本站由鸡西市委宣传部主管,是鸡西市具有权威性的网站。
本文目标很明白:手把手教你运用 DApp 开辟框架 Embark 构建一个去中间化百度贴吧(文末附 GitHub 地点),主要包括以下 3 部份:
明白 DApp 需求,布置智能合约;
运用 EmbarkJS 测试智能合约;
运用 React 构建 DApp 的前端。
在上一篇文章中,营长手把手带你们运用 Solidity 言语布置合约,并运用 EmbarkJS 完成智能合约测试,本文基于此将继续深切,运用 JavaScript 用户界面框架 React 构建去中间化百度贴吧的前端。
衬着第一个组件
在构建与智能合约实例交互的组件之前,我们须要先在屏幕上现实衬着一个简朴的文本,以确保 React 框架已获得了准确的设置。
为此,我们须要将 React 框架增添为项目标依靠项。事实上,我们的代码依靠两个递次包:react 和 react-dom。之所以须要 react-dom 是由于它可以在 DOM (Document Object Model,文档对象模子)环境中衬着运用 React 框架定义的组件,听起来使人摸不着头脑,简朴来说这就是浏览器所做的事情。
接下来我们须要将这两个依靠项增添到项目标 package.json 中:
"dependencies": { "react": "^16.4.2", "react-dom": "^16.4.2"}
完成后,我们须要现实装置这些依靠项,我们只须要在终端中实行以下敕令:
npm install
一切一般的话,如今我们便可以运用 React 框架了。由于 Embark 框架并不须要指定任何前端框架,因此我们不会过量关注 React 框架特有的属性,仅仅完成构建运用递次的事情就已足矣。
在 React 框架中竖立组件异常简朴。我们须要做的就是竖立一个继续了 React 的 Component (组件)范例的类,然后增添一个衬着函数 render() 来展现组件的视图。
我们须要为项目中的一切组件竖立一个文件夹:
mkdir app/js/components
接下来,我们须要为根组件竖立一个文件,我们简朴地把根组件定名为 App 并运用雷同的文件名:
touch app/js/components/App.js
如前所述,我们须要在屏幕上衬着一些笔墨来确保 React 框架没有失足,也就是说,我们须要编写以下代码:
import React, { Component } from 'react';export class App extends Component { render() { return <h1>DReddit</h1> }}
这些代码的可读性照样很强的,险些可以做到自诠释(self explanatory)。在代码中我们导入了 React 及其 Component(组件)范例,并竖立了一个继续 Component 组件的 App 类。React 框架将运用衬着函数 render() 来展现出组件的视图,而且会返回用 JavaScript 语法拓展 JSX 编写的模板。JSX 在语法上看起来很像 HTML,只是它带有一些用来嵌入像掌握组织如许功用的分外语法,稍后我们会再运用它!
如今我们已定义好了这个组件,接下来就须要通知 React 框架来现实衬着这个组件。为此,我们须要转到 app / js / index.js 文件,并在个中增添以下代码:
import React from 'react';import { render } from 'react-dom';import { App } from './components/App';render(<App />, document.getElementById('root'));
由于 React 在当前这个剧本局限中还不可用,所以我们起首须要再次导入 React,同时我们还须要从 react-dom 中导入衬着函数 render(),衬着函数会协助我们将根组件衬着到 HTML 文档的某个元素中。在这类情况下,我们想要衬着的根组件元素是那些显现为根组件 root 的元素。
接下来我们来疾速设置它,我们须要在 app / index.html 文件中增添一个显现为根组件 root 的新元素:
<body> <div id="root"></div> <script src="js/app.js"></script></body>
请注重,代码中在挑选了根组件 root 后,我们还更新了 script 标签。如许做是为了保证,我们在衬着函数 render()中指定的元素在剧本实行时是现实可用的。
功德圆满!接下来我们启动 Embark 框架,此时屏幕上应当会涌现方才定义的组件:
embark run
构建竖立帖子组件 CreatePost
上面的例子能够让你对怎样构建组件有了基础的相识,如今是时刻构建真正有效的组件了。起首我们会构建一个用户竖立帖子时运用的组件。与上面定义的 App 组件相似,我们须要构建一个新的竖立帖子组件 createPost,它带有一个衬着函数 render()来展现输入数据的简朴表单(form)。我们还须要向表单中增添事宜处置惩罚递次,以便用户在提交表单时,我们可以接见到用户提交的数据并将其发送到智能合约中。
竖立一个表单异常简朴:
import React, { Component } from 'react';export class CreatePost extends Component { render() { return ( <form> <div> <label>Topic</label> <input type="text" name="topic" /> </div> <div> <textarea name="content"></textarea> </div> <button>Post</button> </form> ) }}
为在屏幕上展现这个组件,我们须要将其定义为 App 组件的一部份。详细而言,就是让 App 组件衬着竖立帖子组件 CreatePost ,我们可以简朴地将它增添到 App 组件的衬着函数中:
import { CreatePost } from './CreatePost';export class App extends Component { render() { return ( <React.Fragment> <h1>DReddit</h1> <CreatePost /> </React.Fragment&> ) }}
React 框架不许可在单个组件视图中运用多个根组件,因此我们须要用到 React.Fragment。明显,除了我们适才定义的静态表单以外,根组件中没有其他的衬着使命。
接下来我们继续完美表单的功用。起首,我们须要确保输入到表单中的数据在组件中可用。React 组件中的状况对象 state 可以协助处理这个题目。我们所要做的就是给它一些初始值来初始化它,并在须要时运用设置状况函数 setState()来更新它。
我们可以在竖立帖子组件 CreatePost 中经由过程运用组织函数来引入状况对象 state ,响应的我们还可以直接初始化它:
export class CreatePost extends Component { constructor(props) { super(props); this.state = { topic: '', content: '', loading: false }; } ...}
接下来,我们将该状况绑定到表单字段:
<form> <div> <label>Topic</label> <input type="text" name="topic" value={this.state.topic} /> </div> <div> <textarea name="content" value={this.state.content}></textarea> </div> <button>Post</button></form>
你能够会问代码中的 loading 是干吗的,别着急,我们立时会说到它。末了但一样主要的是,我们须要增添一些事宜处置惩罚递次,以便在用户输入数据时视图中的变动能通报回组件并更新组件的状况。为了确保一切一般,我们还须要为表单提交增添一个事宜处置惩罚递次,让它输出状况对象 state 中的数据,换句话说,我们须要更新处置惩罚递次 handleChange()和竖立帖子处置惩罚递次 createPost(),代码以下:
export class CreatePost extends Component { ... handleChange(field, event) { this.setState({ [field]: event.target.value }); } createPost(event) { event.preventDefault(); console.log(this.state); } ...}
请注重代码中更新处置惩罚递次 handleChange()的完成体式格局,我们在个中运用了设置状况函数 setState()来更新通报给该函数的值。如今我们须要做的就是将这些处置惩罚递次附加到表单中:
<form onSubmit={e => createPost(e)}> <div> <label>Topic</label> <input type="text" name="topic" value={this.state.topic} onChange={e => handleChange('topic', e)} /> </div> <div> <textarea name="content" value={this.state.content} onChange={e => handleChange('content', e})></textarea> </div> <button type="submit">Post</button></form>
由于我们正在运用表单的 onSubmit()处置惩罚递次,因此很主要的一点就是将 type =“submit” 增添到按钮对象 button 中,或将按钮对象变动为 <input type =“submit”>,不然,表单将不会发出提交事宜。
做完了这些,在提交表单时我们就能在掌握台中看到组件的状况了!接下来最大的应战就是运用 EmbarkJS 和它的 API 完成组件与智能合约实例的交互。
1、将数据上传到 IPFS
追念一下我们适才的定义, DReddit 中竖立帖子函数 createPost()吸收一些字节作为帖子的形貌,我们也议论了,这些字节现实上并非帖子本身的数据,而是可以指向帖子数据的 IPFS 哈希值。换句话说,我们必需以某种体式格局将数据上传到 IPFS 中,并取得如许的哈希值。
荣幸的是,壮大的 EmbarkJS 为我们供应了大批的 API 来完成这个功用!就比如说, EmbarkJS 的存储文档函数 EmbarkJS.Storage.saveText()会把一段字符串上传到 IPFS 中并返回其哈希值,然后我们可以经由过程智能合约中的竖立帖子函数 createPost()来用这个哈希值竖立一个帖子。须要注重的是,这些 API 是异步的,与在测试中运用到的异步操纵雷同,这里我们将运用 async / await 要领以同步体式格局编写异步代码。
async createPost(event) { event.preventDefault(); this.setState({ loading: true }); const ipfsHash = await EmbarkJS.Storage.saveText(JSON.stringify({ topic: this.state.topic, content: this.state.content })); this.setState({ topic: '', content: '', loading: false });}
代码中我们运用了将 JavaScript 对象转换为字符串的函数 JSON.stringify(),我们运用它来获得所竖立帖子的主题和内容。这也是我们第一次运用 loading。我们起首将 loading 设置为true,接着我们实行操纵为守候更新的用户衬着出有效的信息,末了再将 loading 改回 false。
<form onSubmit={e => createPost(e)}> ... {this.state.loading && <p>Posting...</p> }</form>
很明显,到这里我们还没有完成这个功用。上面所做的只是将帖子的数据上传到 IPFS 中并吸收它的哈希值,接下来我们须要完成经由过程智能合约中的竖立帖子函数 createPost()来用这个哈希值竖立一个帖子。
2、发送生意业务以竖立帖子
要将生意业务发送到智能合约中,我们可以再次运用 EmbarkJS 的 API。同时我们还须要一个以太坊账户来发送生意业务。这并不难,我们可以运用 Embark 供应的以太坊节点来天生帐户。
完成了这些后我们便可以预算生意业务的 gas 需求并经由过程生意业务发送数据。取得以太坊账户的要领以下所示,请注重此处我们也可以运用 async / await 异步处置惩罚要领:
async createPost(event) { ... const accounts = await web3.eth.getAccounts(); ...}
接下来,我们将从 EmbarkJS 中导入 DReddit 智能合约实例,并预算生意业务的gas 需求。末了我们将运用取得的账户和预算的生意业务燃料来现实提议生意业务:
import DReddit from './artifacts/contracts/DReddit';...async createPost(event) { ... const accounts = await web3.eth.getAccounts(); const createPost = DReddit.methods.createPost(web3.utils.toHex(ipfsHash)); const estimate = await createPost.estimateGas(); await createPost.send({from: accounts[0], gas: estimate}); ...}
到这里我们的竖立帖子函数 createPost()就悉数完成了!虽然我们还没有竖立一切已竖立帖子的列表,但我们已可以经由过程运用递次来竖立帖子了,我们可以运用 Embark 框架搜检生意业务是不是胜利。在输入敕令 embark run 启动运转后,终端中应当会显现如许的输出:
Blockchain> DReddit.createPost("0x516d5452427a47415153504552614645534173335133765a6b59436633634143776368626263387575623434374e") | 0xbbeb9fa1eb4e3434c08b31409c137c2129de65eb335855620574c537b3004f29 | gas:136089 | blk:18455 | status:0x1
构建帖子组件 Post
DReddit 运用递次的下一个应战在于从智能合约实例和 IPFS 中猎取一切竖立的帖子,以便我们在屏幕上展现。我们先从最简朴的最先,起首竖立一个只能展现一个帖子的新组件,以后,我们将依据所猎取的数据动态地展现帖子列表。
一样的,我们只关注准确地完成中心功用,因此我们的运用递次看起来不会迥殊悦目。从需求上来说,帖子组件 Post 须要离别展现帖子的主题、内容、一切者、竖立日期,以及好评差评的投票按钮。
这是一个组件的基础模板:
import React, { Component } from 'react';export class Post extends Component { render() { return ( <React.Fragment> <hr /> <h3>Some Topic</h3> <p>This is the content of a post</p> <p><small><i>created at 2019-02-18 by 0x00000000000000</i></small></p> <button>Upvote</button> <button>Downvote</button> </React.Fragment> ) }}
有许多种要领都可以用来完成数据的动态展现。一般,我们可以将一个或多个属性通报给帖子组件 Post,这个组件示意全部帖子对象,它的衬着函数 render()可以完成数据的动态展现。然则在这里,我们将挑选一个轻微差别的完成要领。我们将经由过程帖子组件 Post 吸收存储在智能合约中的 IPFS 哈希值并让它自己剖析数据。
为了保证智能合约和组件中的各功用定名一致,我们将组件中想要存储的数据也叫做形貌。然后我们可以运用数据猎取函数 EmbarkJS.Storage.get()来猎取 IPFS 哈希值对应的数据,也就是现实的帖子数据。为了在帖子组件 Post 的视图中展现数据,我们将对适才猎取的数据举行剖析并响应地运用设置状况函数 setState()。
为了确保在组件准备就绪以后这些操纵都能一般运转,我们把这些操纵都放在 componentDidMount()生命周期钩子函数(life cycle hook)中实行:
import React, { Component } from 'react';import EmbarkJS from '.artifacts/embarkjs';export class Post extends Component { constructor(props) { super(props); this.state = { topic: '', content: '' }; } async componentDidMount() { const ipfsHash = web3.utils.toAscii(this.props.description); const data = await EmbarkJS.Storage.get(ipfsHash); const { topic, content } = JSON.parse(data); this.setState({ topic, content }); } ...}
这里须要强调的一点是:在页面加载时挪用数据猎取函数 EmbarkJS.Storage.get()或其他任何 EmbarkJS 函数能够会失利,由于此时存储体系能够还还没有完整初始化。不过这关于数据上传函数 EmbarkJS.Storage.uploadText()来说并非题目,由于我们只能在 Embark 框架初始化完成后挪用了它。
不过,从理论上来说,竖立一个帖子时能够会存在合作前提(race condition,是指装备或体系涌现不适当的实行时序,因此获得不准确的结果)。为了确保 EmbarkJS 在任何时候点都能准备就绪,我们将运用到推断是不是准备就绪的钩子函数 onReady()。一旦 EmbarkJS 准备就绪,EmbarkJS.onReady()就会实行一次挪用,在这里被调函数的最好挑选就是运用递次的衬着函数,所以我们在 Embark 框架的 onReady() 函数中挪用衬着函数 render() 来衬着 App 组件。
EmbarkJS.onReady(() => { render(<App />, document.getElementById('root'));});
这也意味着我们的运用递次只会在 EmbarkJS 准备就绪时实行衬着,展现数据。从理论上来说,如许做守候的时候能够会变长,但就我们这个 DReddit 运用递次而言,形成影响的能够性不大。
我们还须要增添帖子一切者和帖子竖立日期。根据预期,一切者和竖立日期都将作为帖子的属性被纪录下来。我们只须要以用户可以明白的体式格局对数据举行花样化,展现一切者并不会有什么题目,但要以人类可读的情势展现日期就须要装置并导入日期花样库 dateformat,装置的操纵以下所示:
npm install --save dateformat
装置完成后,我们须要更新帖子组件 Post 的衬着函数 render(),将获得的帖子竖立日期 creationDate 转换成人类可读的情势。
...import dateformat from 'dateformat';export class Post extends Component { ... render() { const formattedDate = dateformat( new Date(this.props.creationDate * 1000), 'yyyy-mm-dd HH:MM:ss' ); return ( <React.Fragment> <hr /> <h3>{this.state.topic}</h3> <p>{this.state.content}</p> <p><small><i>created at {formattedDate} by {this.props.owner}</i></small></p> <button>Upvote</button> <button>Downvote</button> </React.Fragment> ) }}
请注重,在衬着函数 render() 中竖立的变量可以恣意地增添数据,所以我们不须要让它们在 props (React 用来在组件之间通报值的一种对象)或状况对象 state 上可用。事实上, React 框架默许 props 对象都是只读的(read only,即不可修正)。
我们可以试着将一些数据增添到 App 组件视图中来测试一下新的帖子组件 Post。接下来,我们将经由过程从智能合约中提取帖子来完成这个功用。
须要注重的是,这个代码片断中的哈希值是我所存储数据的哈希值,因此它在你的当地 IPFS 节点中是不可用的,你须要将它替换成你数据的哈希值。详细而言,你只须要纪录数据上传至 IPFS 时返回的哈希值并将其转换为十六进制。
export class App extends Component { render() { return ( <React.Fragment> <h1>DReddit</h1> <CreatePost /> <Post description="0x516d655338444b53464546725369656a747751426d683377626b56707566335770636e4c715978726b516e4b5250" creationDate="1550073772" owner="0x00000000000" /> </React.Fragment> ) }}
构建帖子列表组件 List
蔡维德:Libra如果脱钩美元 美国第二天就让它下市
在构建展现帖子列表的组件之前,我们必需想办法来优化智能合约。现在我们还没有一个很好的要领从智能合约中猎取数组数据,也就是说要完成帖子的列表展现功用我们须要逐一猎取帖子的数据。为此,我们须要猎取帖子的总个数并经由过程迭代来索引一切的帖子,从而完成对每一个帖子的猎取。
我们须要在 DReddit 智能合约中引入一个推断帖子个数的函数 numPosts():
function numPosts() public view returns (uint) { return posts.length;}
当我们增添帖子时,帖子个数 posts.length 会响应的增添,因此我们可以把它用做读取帖子时的索引。固然了,假如情愿的话你也可以写一个测试考证一下它的准确性!
有了这个,我们便可以最先构建帖子列表组件 List 了。List 组件维护着一个要在屏幕上展现的帖子列表,我们可以从最简朴的功用最先再一步步深切,详细代码以下:
import React, { Component } from 'react';export class List extends Component { constructor(props) { super(props); this.state = { posts: [] }; } render() { return (<React.Fragment> {this.state.posts.map(post => { return ( <Post key={post.id} description={post.description} creationDate={post.creationDate} owner={post.owner} />) })} </React.Fragment> ) }}
这里最风趣的部份就是衬着函数 render(),代码中我们遍历了一切的 state.posts (现在为空),然后在每次迭代中衬着一个帖子组件 Post。另一个须要注重的点是,每一个帖子组件 Post 都邑收到一个键值 key, React 框架在轮回竖立视图时须要用到这个键值。你能够会发现到现在为止我们还没用过帖子的序号 post.id,不要忧郁,我们立时就会用到它。
如今我们已可以将帖子列表组件 List 放在 App 组件中了。但如今它还不会展现任何内容,由于我们还没有宣布任何帖子,我们接下来就要做这个事情。
import { List } from './List';export class App extends Component { render() { return ( <React.Fragment> <h1>DReddit</h1> <CreatePost /> <List /> </React.Fragment> ) }}
a)猎取帖子数据
如前所述,我们将运用智能合约的推断帖子个数函数 numPosts()来猎取帖子的总数。然后我们将帖子总数作为索引来迭代零丁接见每一个帖子。这个逻辑应当在帖子列表组件 List 准备就绪后实行,因此我们须要在 List 组件的定义以后到场 componentDidMount()函数:
export class List extends Component { ... async componentDidMount() { const totalPosts = await DReddit.methods.numPosts().call(); let list = []; for (let i = 0; i < totalPosts; i++) { const post = DReddit.methods.posts(i).call(); list.push(post); } list = await Promise.all(list); } ...}
请注重,在上面的代码中,我们并没有效 await 语句来守候每次对帖子的挪用。这是故意为之,由于我们不能够守候每一个许诺的完成,所以我们会网络一切须要的许诺,然后运用 Promise.all()函数一次性处理一切这些许诺。
末了但一样主要的是,前面也提到了我们须要为每一个帖子增添一个 id 属性。我们可以简朴地遍历一切帖子并将帖子的索引赋值给 id。这些操纵完成后,我们可以运用设置状况函数 setState()来更新组件的状况并展现列表:
async componentDidMount() { ... list = list.map((post, index) => { post.id = index; return post; }); this.setState({ posts: list });}
到如今为止,我们的 DReddit 运用递次已可以展现一切已竖立帖子的列表。但遗憾的是,在增添新帖子时,它并不会自动从新加载帖子。因此,我们必需在每次增添帖子后革新浏览器,如许做非常影响用户体验,我们如今须要处理这个题目。
b)从新加载帖子
我们有多种差别的要领来完成帖子列表的从新加载,最简朴的一种就是让竖立帖子组件 createPost 通知帖子列表组件 List 从新加载帖子。然则,我们构建的这个 React 运用递次并没有设置通讯层,所以最直接的要领就是变动竖立帖子组件 CreatePost 和帖子列表组件 List 的父组件(在这里就是 App 组件)中加载帖子的逻辑,让这个父组件把逻辑通报到须要它的处所。这也意味着我们将把猎取帖子列表的功用放在 App 组件中,帖子列表组件 List 仅仅吸收通报过来的纯数据。
这个完成要领听起来很绕,但不必忧郁,在代码中完成它并不难!我们起首须要在 App 组件中定义一个读取帖子函数 loadPosts(),然后基础上我们须要把帖子列表组件 List 中 componentDidMount()函数的一切功用都移动到 App 组件中:
export class App extends Component { ... async loadPosts() { const totalPosts = await DReddit.methods.numPosts().call(); let list = []; if (totalPosts > 0) { for (let i = 0; i < totalPosts; i++) { const post = DReddit.methods.posts(i).call(); list.push(post); } } list = await Promise.all(list); list = list.map((post, index) => { post.id = index; return post; }); list; this.setState({ posts: list }); }}
为了完成这项事情,我们还须要引入一个帖子的状况,如许便可以确保 App 组件在挂载时会挪用读取帖子函数 loadPosts():
export class App extends Component { constructor(props) { super(props); this.state = { posts: [] }; } async componentDidMount() { await this.loadPosts(); } ...}
末了但一样主要的是,我们须要将帖子通报给帖子列表组件 List 并将加载帖子函数 loadPosts()通报给竖立帖子组件 CreatePost 作为回调处置惩罚递次:
render() { return ( <React.Fragment> <h1>DReddit</h1> <CreatePost afterPostHandler={this.loadPosts.bind(this)}/> <List posts={this.state.posts}/> </React.Fragment> )}
完成后,我们可以离别从 this.props 中猎取帖子和帖子竖立后处置惩罚函数 afterPostHandler()。这个功用将在帖子列表组件 List 的衬着函数 render()中完成(注重我们不再须要状况对象 this.state 了):
render() { return (<React.Fragment> {this.props.posts.map(post => { ... })} </React.Fragment> )}
然后在竖立帖子组件 CreatePost 中,我们须要在竖立帖子后挪用帖子竖立后处置惩罚函数afterPostHandler():
async createPost(event) { ... await createPost.send({from: accounts[0], gas: estimate}); await this.props.afterPostHandler(); this.setState({ topic: '', content: '', loading: false });}
有了这些功用。在新竖立帖子时,帖子列表会自动从新加载,你大可去试一试。
增添投票功用
我们将要完成的末了一个功用就是对帖子举行好评照样差评的投票。这须要我们回到方才竖立的帖子组件 Post 中举行变动,起首我们必需明白此处变动要完成的功用:
展现每一个帖子的好评数和差评数;
为用户离别增添处置惩罚好评投票和差评投票的处置惩罚递次;
肯定用户是不是可以对帖子举行投票。
a)衬着帖子的票数
第一个功用是个中最噜苏的一个,所以我们先来举行它的攻关。虽然 DReddit 智能合约返回的数据中已附加了好评数和差评数,但它的花样并不准确,由于智能合约返回的数据是字符串情势。接下来我们须要扩大 App 组件的加载帖子函数 loadPosts()来完成对帖子好评数和差评数的剖析,代码以下:
async loadPosts() { ... list = list.map((post, index) => { post.id = index; post.upvotes = parseInt(post.upvotes, 10); post.downvotes = parseInt(post.downvotes, 10); return post; }); ...}
完成后,我们可以经由过程帖子列表组件 List 中的 props 对象将每一个帖子的好评数和差评数通报给每一个帖子组件 Post :
export class List extends Component { ... render() { return (<React.Fragment> {this.props.posts.map(post => { return (<Post key={post.id} description={post.description} creationDate={post.creationDate} upvotes={post.upvotes} downvotes={post.downvotes} owner={post.owner} />) })} </React.Fragment> ) }}
展现好评数和差评数的功用现实上只须要在帖子组件 Post 的衬着函数 render()中插进去数据。代码中我们将数据增添到按钮旁边,你可以随便将它们放在其他位置:
export class Post extends Component { ... render() { ... return ( <React.Fragment> ... {this.props.upvotes} <button>Upvote</button> {this.props.downvotes} <button>Downvote</button> </React.Fragment> ) }}
b)完成好评差评投票
与竖立新帖子相似,对帖子举行好评差评投票也须要发送生意业务到 DReddit 智能合约。因此,我们将实行与竖立帖子组件 CreatePost 中险些雷同的操纵,唯一的区分就是在这里我们挪用的是智能合约的投票函数 vote()。你应当还记得,投票函数 vote()吸收两个参数,帖子序号 post id 和投票范例 Ballot,详细而言就是没有投票 NONE,好评 UPVOTE 或差评 DOWNVOTE,它的存储花样为 8 位无标记整型 uint8。
上文提到过,在这个运用中差别部份(智能合约、前端组件)的变量都有着雷同的示意,如许会大大减小失足的能够,关于前端组件中的投票组件,我们仍运用 0、1、2 这三个数字来示意没有投票 NONE,好评 UPVOTE 或差评 DOWNVOTE,但题目在于 JavaScript 中不支持罗列数据组织,因此在前端组件中我们须要运用哈希对象作为替换:
const BALLOT = { NONE: 0, UPVOTE: 1, DOWNVOTE: 2}
现实上,我们的帖子组件 Post 中并没有到场帖子序号 post id,不过将帖子序号 post id 增添到帖子列表组件 List 中并非什么难事,如今你应当知道该怎样做了!
我们须要离别在好评投票按钮和差评投票按钮上增添点击处置惩罚递次,然后再将我们在投票范例 BALLOT 中定义的好评投票和差评投票通报给它们(请注重,投票范例中的没有投票 None 只是为了保证递次逻辑的完整性,但现实上在代码中我们并没有运用它):
<button onClick={e => this.vote(BALLOT.UPVOTE)}>Upvote</button><button onClick={e => this.vote(BALLOT.DOWNVOTE)}>Downvote</button>
接下来,我们须要将该投票范例以及所投的帖子序号 post id 发送到智能合约当中。
async vote(ballot) { const accounts = await web3.eth.getAccounts(); const vote = DReddit.methods.vote(this.props.id, ballot); const estimate = await vote.estimateGas(); await vote.send({from: accounts[0], gas: estimate});}
我们还愿望在胜利发送投票后更新视图。我们须要经由过程帖子的 props 对象猎取帖子的好评差评投票并响应地衬着它们。然则,假如在吸收到投票后马上更新这些值就好了。为此,我们须要变动代码,让它只读取一次来自 props 对象的好评差评投票并将它们存储在组件的状况中。
export class Post extends Component { constructor(props) { super(props); this.state = { topic: '', content: '', upvotes: this.props.upvotes, downvotes: this.props.downvotes }; } ...}
同时我们还须要变动组件的衬着函数 render(),让它从组件状况中读取数据而不是从 props 对象中:
render() { ... return ( <React.Fragment> ... {this.state.upvotes} <button ...>Upvote</button> {this.state.downvotes} <button ...>Downvote</button> </React.Fragment> )}
如许,我们便可以在投票提议后马上运用设置状况函数 setState()来更新状况:
async vote(ballot) { ... this.setState({ upvotes: this.state.upvotes + (ballot == BALLOT.UPVOTE ? 1 : 0), downvotes: this.state.downvotes + (ballot == BALLOT.DOWNVOTE ? 1 : 0) });}
功德圆满,我们如今可以对帖子举行好评差评投票,且对每一个帖子只能投票一次,没错,当我们对一个帖子屡次投票时,递次会报错。这是由于,我们在智能合约中到场了一项限定前提,确保用户没法对已投票或还未竖立的帖子举行好评差评投票。
胜利近在眼前,末了我们只须要将这个投票限定逻辑到场前端递次中。
c)运用函数 canVote() 禁用投票按钮
这个投票限定逻辑完成起来异常简朴。假如用户不能对帖子投票,我们只须要禁用投票按钮。我们可以经由过程挪用智能合约中可否投票函数 canVote()来肯定用户可否举行投票。同时,我们还须要考虑到,假如用户已对一个帖子举行了投票,只是这笔包括投票的生意业务还未被到场到区块链中,也就是说此时投票还没有完成,这时候我们不应当许可用户对该帖子再次投票。
在代码中,这个功用对应于投票是不是正在提交(submitting)的状况。一般来说,假如一个用户之前没有对某个帖子投票,而且他此时没有在提交对该帖子的投票,那末他便可以对该帖子投票:
export class Post extends Component { constructor(props) { super(props); this.state = { topic: '', content: '', upvotes: this.props.upvotes, downvotes: this.props.downvotes, canVote: true, submitting: false }; } ...}
接下来,我们须要更新帖子组件 Post 的衬着函数 render(),以便在用户不能对帖子投票时禁用投票按钮:
render() { ... const disabled = this.state.submitting || !this.state.canVote; return ( <React.Fragment> ... {this.state.upvotes} <button disabled={disabled} ...>Upvote</button> {this.state.downvotes} <button disabled={disabled} ...>Downvote</button> </React.Fragment> )}
末了但一样主要的是,我们必需确保组件的状况也做出响应的更新。当一个帖子初始化时,我们将挪用智能合约中函数 canVote():
export class Post extends Component { ... async componentDidMount() { ... const canVote = await DReddit.methods.canVote(this.props.id).call(); this.setState({ topic, content, canVote }); } ...}
在举行投票时,我们在发送投票地点的生意业务之前要先将正在提交状况 submitting 设置为是(true),并在生意业务完成后再将其改成否(false),由于此时已完成了对帖子的投票,因此可否投票状况 canVote 也应当被设置为否(false):
async vote(ballot) { ... this.setState({ submitting: true }); await vote.send({from: accounts[0], gas: estimate + 1000}); this.setState({ ... canVote: false, submitting: false });}
Bingo!运转一下代码看看结果吧!
一些发起
上述所完成的功用只是百度贴吧供应功用的冰山一角,因此,我们还可以在许多处所做出革新和优化,以下是我的一些发起:
根据反向的时候递次对帖子举行排序,以便最新提交的帖子一直位于页面顶部;
经由过程智能合约事宜完成帖子列表的从新加载;
引入路由,以便差别用户在竖立和检察帖子时有差别的视图;
运用 CSS(层叠样式表)来美化运用递次的视图;
经由过程运用 IPFS 和智能合约组合开辟一款去中间化运用并非难事,更多功用等你去发掘哟。
GitHub 地点:
https://github.com/embark-framework/dreddit-tutorial
原文地点:
1、Setting up the project and implementing a Smart Contract
https://embark.status.im/news/2019/02/04/building-a-decentralized-reddit-with-embark-part-1/
2、Testing the Smart Contract through EmbarkJS
https://embark.status.im/news/2019/02/11/building-a-decentralized-reddit-with-embark-part-2/
3、Building a simple front-end using React
https://embark.status.im/news/2019/02/18/building-a-decentralized-reddit-with-embark-part-3/
蔡维德:Libra如果脱钩美元 美国第二天就让它下市