使用Vue & deepstream构建实时CRUD应用

WenStitt 8年前
   <p>JavaScript是当前最流行的语言,因为你可以用它为任何平台(包括网站,服务器,移动设备和桌面)构建应用程序。</p>    <p>Vue是一个渐进式的JavaScript UI工具,很容易上手,您可以在一天之内就学会它。另一方面,它拥有足够强大的功能以满足您的前端需求。</p>    <p>实时技术正在逐渐展现出新的形态;实时服务器现在用作处理实时相关任务的抽象。 deepstream 是一个 开源 ,免费和极速的实时服务器,您可以在您的机器上安装。</p>    <p>本文演示了使用deepstream和Vue作为我们的前端工具来构建实时应用程序。下面的GIF就是我们将要实现的内容: <img src="https://simg.open-open.com/show/ab3ea5063271ae317873e1af463a5789.gif"></p>    <p>在开始代码之前,让我们说服自己为什么非得如此。</p>    <h2>为什么要使用Vue</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9aa9d516a20d47b65fea9e7fb6504786.png"></p>    <p>依我拙见,Vue是一个迄今为止最简单的而且值得一试的UI框架。首先它很容易上手,其次它帮我们实现了数据绑定,服务端渲染和状态管理等在考虑UI库时所遇到的复杂的概念。</p>    <p>Vue考虑到现有UI库的复杂性,并简化了这些复杂性,使我们的生活不像软件工程师那么沮丧。它也支持我们最喜欢的后端工具, <a href="/misc/goto?guid=4958976112488620154" rel="nofollow,noindex">Laravel</a> --使用的集成更简单。这当然不意味着你不能与任何其他后端平台集成。</p>    <h2>为什么是Deepsteam</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/efedc370133e585eaf68a2e2e6e88b68.png"></p>    <p>deepstream是一个独立的服务器--它比大多数的实时解决方案都要 快 --它允许您使用持久状态作为数据同步,pub/sub模式作为事件或者请求/响应模式作为RPC。</p>    <p>您可以放心,通过丰富的选项,deepstream可以集成在你正在开发的任何性质的应用程序中。包括但不限于聊天,实时新闻更新,库存信息,CRUD/CMS应用,数据可视化,数据监控等。</p>    <h2>安装Vue和deepstream</h2>    <p>安装Vue和deepstream相当简单,您只需要几条CLI命令就可以完成安装。</p>    <p>deepstream是跨平台的,可以安装在Linux,Windows和OSX上。与此同时,你也可以通过Docker和npm来安装它。对于这篇文章,我们将下载操作系统相对应的deepstream。解压下载的文件,然后在解压后的文件夹根目录执行以下命令来启动服务器:</p>    <pre>  <code class="language-javascript">./deepstream</code></pre>    <p>vue-cli 是一个可以让你快速创建Vue项目的CLI工具。我们需要安装这个工具以便创建我们的demo应用。</p>    <pre>  <code class="language-javascript">npm i -g vue-cli</code></pre>    <p>通过全局安装vue-cli工具,我们可以在任何地方使用这个命令。Vue脚手架有不同的模版,我们只需要通过以下命令使用最简单的模版即可:</p>    <pre>  <code class="language-javascript">vue init webpack my-project</code></pre>    <h2>用Vue实现CRUD</h2>    <p>在我们尝试构建一个实时应用之前,让我们先创建一个平台。一个图书的CRUD(增删查改)应用 看起来是个好主意。</p>    <h3>Creating</h3>    <p>src 文件夹中的 App.vue 文件是我们的主要和唯一的组件,这对于我们正在尝试构建的应用而言是足够的。打开 app.vue 文件创建一个简单的form:</p>    <pre>  <code class="language-javascript"><!-- ./src/App.vue -->  <template>    <div id="app">      <h1> {{ title }} </h1>      <h3>New Book</h3>      <form v-on:submit.prevent="onSubmit">        <div>          <input name="title" type="text" placeholder="title" v-model="book.title" />        </div>        <div>          <input name="year" type="text" placeholder="year" v-model="book.year" />        </div>        <div>          <input name="author" type="text" placeholder="author" v-model="book.author" />        </div>        <div>          <label for="read">Read?</label>          <input type="checkbox" v-model="book.read" id="read" name="read" />        </div>        <button v-if="updating">Update</button>        <button v-else>Add</button>      </form>    </div>  </template>    <script>    export default {    name: 'app',    data () {      return {        title: 'My Books Manager',        updating: false,        book: {          title: '',          year: '',          author: '',          read: false        }      }    }  }  </script></code></pre>    <p>app.vue 就是我们熟知的Vue 单文件组件 。这是Vue应用的一个很好的策略,它允许每个组件将其模版,样式和业务逻辑保存在一个文件中。</p>    <p>我们用Vue数据绑定实现了一个基本的表单。每一个表单项都通过 v-model 与Vue data 方法返回的book对象的属性进行绑定。Vue data 方法还返回了用作APP头部的标题和切换 add 和 update 按钮的更新标识。通过 v-if...v-else 指令可以实现切换。</p>    <p>当有按钮被点击时, onSubmit 方法便会触发,因为在上面我们通过 v-on:submit 指令给form绑定了这个方法。因此接下来我们需要创建这样一个方法:</p>    <pre>  <code class="language-javascript">export default {    name: 'app',    data () {      return {        // ...        updateIndex: 0,        books: [],        book: {          title: '',          year: '',          author: '',          read: false        }      }    },    methods: {      onSubmit () {        if (this.updating) {          this.onUpdate();          return;        }        this.book.push(this.book);        this.book = {          title: '',          year: '',          author: '',          read: false        }      }    }  }</code></pre>    <p>onSubmit 会检查是否是更新操作。如果是更新操作,它将调用 onUpdate 方法来处理更新。反之,它将把新的book对象push到books数组里面。</p>    <h3>Reading</h3>    <p>通过使用 v-for 指令迭代books数组,我们可以通过表格将其展示出来。</p>    <pre>  <code class="language-javascript"><template>    <div id="app">      <h1> {{ title }} </h1>      <h3>New Book</h3>      <!-- For markup truncated -->      <h3>All Books</h3>      <table>        <tr>          <th>Title</th>          <th>Year</th>          <th>Author</th>          <th>Read</th>          <td>Update</td>          <td>Delete</td>        </tr>        <tr v-for="(b, index) in books">          <td>{{ b.title }}</td>          <td>{{ b.year }}</td>          <td>{{ b.author }}</td>          <td v-if="b.read">✓</td>          <td v-else> </td>          <td v-on:click.prevent="onEdit(index)"><a>✎</a></td>          <td v-on:click.prevent="onDelete(index)"><a>✗</a></td>        </tr>      </table>    </div>  </template></code></pre>    <p style="text-align:center">表格数据行中额外的两个链接来处理编辑(不是更新)和删除记录,它们分别调用 onEdit 和 onDelete 方法。您可以在表单中输入一些信息,然后在表格中查看结果: <img src="https://simg.open-open.com/show/6738d171b7d2960006581da4c958f78c.png"></p>    <h3>Updating</h3>    <p>更新操作需要两个步骤 - 从表中选择我们需要更新的记录,这将使它展现在表单中,并更改数组以更新值。</p>    <p>onEdit 方法负责第一个阶段:</p>    <pre>  <code class="language-javascript">data () {    return {      updating: false;      updateIndex: 0,      books: [],      book: {        title: '',        year: '',        author: '',        read: false      }    }  },  methods: {    // ...    onEdit (index) {      this.updating = true;      this.updateIndex = index;      this.book = this.books[index];    }  }</code></pre>    <p>onEdit 方法首先将更新标识设置为 true ,然后将 updateIndex 设置为正在编辑的索引,并用在更新的索引中找到的记录替换书模型。</p>    <p>updateIndex 用于跟踪调用 onUpdate 时正在更新的内容.</p>    <pre>  <code class="language-javascript">onUpdate () {    this.updating = false;    this.book[this.updateIndex] = this.book;    this.book = {      title: '',      year: '',      author: '',      read: false    }  },</code></pre>    <p>onUpdate 重置了更新标识,并更新了数组中位置为 updateIndex 的book对象,然后将book对象清空了。</p>    <h3>Deleting</h3>    <p>这是最简单的一部分;我们使用数组splice方法从数组中删除一个项目:</p>    <pre>  <code class="language-javascript">onDelete (index) {    // Remove one item starting at    // the specified index    this.book.splice(index, 1)  }</code></pre>    <h2>使用deepstream实现实时</h2>    <p>我们已经创建了一个可以运行的应用,但这并不是我们的最终目的,当表格中的数据发生变化时我们需要让所有连接的客户端都可以接收到变化,这正是deepstream的用武之地。目前我们这么做,其他客户端并不会接受到更新。 <img src="https://simg.open-open.com/show/3ccbc49e9c15bae75220d62451a4f40e.gif"></p>    <h3>deepstream客户端 记录 & 列表</h3>    <p>在这篇文章的开头,我们安装并启动了deepstream服务器。实际上,这个服务器运行在本机的 6020 端口上,且处于空闲状态等待客户端连接交换数据。</p>    <p>deepstream客户端可以是任何形式的,从Web,桌面,移动,甚至物联网。我们今天所关心的只是Web,所以我们需要使用deepstream的JS SDK来连接服务器。您可以通过执行下面的命令来安装SDK:</p>    <pre>  <code class="language-javascript">npm i --save deepstream.io-client-js</code></pre>    <p>deepstream中的记录和任何其他表示数据的形式的记录一样,它是单个实体并存储给定的信息项。唯一的区别在于deepstream中的记录是实时的,这意味着记录中存储的数据可以被客户端订阅,并且以最小的有效载荷通知客户端。</p>    <p>另一方面,列表可以很好的像集合那样组织数据。列表也是实时的,所以他们的实时更新也可以被客户端订阅。我们将在我们的应用程序中使用deepstream的这些功能,使应用程序实时。</p>    <h3>身份验证</h3>    <p>身份验证就是在任何情况下您都可以确认用户的身份是否和她表明的身份一致。通常的HTTP验证在处理这个问题时和deepstream有那么一点不同。然而,好消息是,你可以很简单的把它们 结合在一起使用 。</p>    <p>对于每一个deepstream的连接客户端,身份验证是必须的,但这并不意味着必须提供凭据。登录可以是匿名的,并适用于我们当前的情况。</p>    <pre>  <code class="language-javascript">import * as ds from 'deepstream.io-client-js';    export default {    name: 'app',    data () {      return {        ds: ds('localhost:6020'),        books$$: null        // . . .      }    },    created () {      this.ds.login({}, () => {        console.log('logged in');      });    },    methods: {/* . . .*/}  }</code></pre>    <p>首先我们导入了client模块,然后创建了一个成员变量 ds 来保存对deepstream的引用,同时传递服务器的URL。</p>    <p>created 方法在组件处于 ready 状态时被Vue调用。这使得我们可以在这个方法中处理deepstream身份验证,通过调用接收凭证对象和回调的 deepstream.login 方法来执行认证。</p>    <p>books$$ 属性将引用我们尚未创建的deepstream中的数据列表。</p>    <p>我们将改写CRUD过程,并用deepstream对其进行更新。但是在这之前,我们需要使所有连接的客户端能够监听到值的改变,以便它们可以相应的更新。</p>    <h3>订阅List和Records</h3>    <pre>  <code class="language-javascript">created () {      this.ds.login({}, () => {        console.log('logged in');      });        this.books$$ = this.ds.record.getList('books');      /*      * Entry added      */      this.books$$.on('entry-added', (recordName, index) => {         this.ds.record.getRecord(recordName).whenReady(record => {            // The scond paramaeter,          // a boolean, is a flag to specify whether           // the callback should be invoked immediatly          // with the current value           record.subscribe(data => {                if(!data.id) {                  if(data.title) {                    data.id = record.name;                    this.books.push(data);                  }                } else {                  this.books = this.books.map(b => {                        if(data.id == b.id) {                              b = data;                          }                          console.log(b)                            return b;                      });                }           }, true)          });      });      /*       * Entry removed       */        this.books$$.on('entry-removed', (recordName, index) => {         this.ds.record.getRecord(recordName).whenReady(record => {           record.subscribe(data => {               this.books.splice(this.books.indexOf(data, 1));           }, true)          });      });    },</code></pre>    <ul>     <li>Lists和records API依赖 record 对象,我们可以通过给getList方法传入一个名称来创建或检索列表。</li>     <li>entry-added 事件在记录添加到列表中时触发。</li>     <li>当记录已经添加, whenReady 方法确保该记录在我们通过 subscribe 方法订阅之前处于 ready 状态。</li>     <li>subscribe 方法接受一个回调来检查数据,如果数据存在,则更新它。反之则用传入的数据更新记录,同时将数据的id设置为记录名称(id),books数组随着数据的进入而更新。</li>     <li>entry-removed 正好和 entry-added 事件刚好相反。它在我们删除记录的时候触发,以便从books数组中删除记录的数据。</li>    </ul>    <h3>Creating</h3>    <p>onSubmit 方法需要重写。我们不会直接将data push到 books 数组中因为这部分的工作已经被我们的订阅处理了。我们仅仅需要调用方法创建一个带数据的record对象并将它添加到列表中:</p>    <pre>  <code class="language-javascript">onSubmit() {     const recordName = this.book.id || 'book/' + this.ds.getUid();       this.ds.record.has(recordName, (err, has) => {       if(has){         this.onUpdate();         return;       } else {          const bookRecord = this.ds.record.getRecord(recordName);           bookRecord.set(this.book);             this.books$$.addEntry(recordName)             this.book = {             title: '',             year: '',             author: '',             read: false           }       }     })   },</code></pre>    <p>如果数据存在,则使用 UUID 或附加到数据的Id来创建/获取记录。 has 用于检查该记录是否存在,如果存在,我们调用 onUpdate 方法;反之,我们将n new book 设置为记录,并使用 addEntry 更新 book$$ 列表.</p>    <h3>Updating</h3>    <pre>  <code class="language-javascript">onUpdate() {     const recordName = this.books[this.updateIndex].id;     const bookRecord = this.ds.record.getRecord(recordName);     bookRecord.set(this.book);     this.book = {       title: '',       year: '',       author: '',       read: false     }       this.updating = false;   }</code></pre>    <p>在 onSubmit 中,如果记录名字存在则调用了 onUpdate 方法进行更新。</p>    <p>onUpdate 通过 updateIndex 在books数组里检索记录名称。我们得到相应的记录并用 book 去更新记录。这里不需要对list进行任何操作,它会自动的进行相应的更新。</p>    <h3>Deleting</h3>    <p>onDeleting 方法仅仅调用了list的 removeEntry 来删除一个记录:</p>    <pre>  <code class="language-javascript">onDelete(index) {    this.books$$.removeEntry(this.books[index].id);   }</code></pre>    <p style="text-align:center"><img alt="(译)使用Vue & deepstream构建实时CRUD应用" src="https://simg.open-open.com/show/67b04d08e8b9fcf24c93ae73e9ebb366.gif"></p>    <h2>总结</h2>    <p>随着更先进的解决方案的出现,构建实时应用程序变得越来越好。 您还可以使用HTTP认证或JWT认证,以及当您需要持久化数据时将您的数据库连接到deepstream。</p>    <p>令人着迷的是我们可以使用Vue和deepstream构建更好的UI应用程序。Vue并不是唯一一个受到社区欢迎的框架。您可以使用相同的初始化和身份验证策略将deepstream与任何其他UI库集成在一起。</p>    <p> </p>    <p> </p>    <p> </p>    <p>来自:http://www.w3ctech.com/topic/1985</p>    <p> </p>