smartsrh

JQuery 重度用户学习 Vue.js 的指南

smartsrh · 2017-05-28翻译 · 1947阅读 原文链接

这几年学习 JavaScript 我是痛并快乐着。

我最开始学 JavaScript 是设计和编程圈子里最喜欢折腾的 jQuery。 作为一个「会编程的设计师」, jQuery 对我来说是一个神奇的存在。 我可以轻松实现淡入淡出的效果。 在第三方库的支持下, 只调用一个函数我就可以实现视差滚动。 这小小 100kb 文件中几乎包含了所有我能想到的效果。

Angular 出来以后,我不得不用它把我的作品重新实现一遍。React 出来以后,我又不得不基于它重新实现一遍。Vue.js 出来以后,我又不得不基于它重新再来。

玩笑归玩笑,我非常享受用这些框架来学习 JS 的过程。学习过程中我读了无数篇文章和教程,但只有上杉周作的《为 JQuery 重度用户介绍 React.js》这篇文章让我印象深刻。

只需一些基本的 JS 和 JQuery 的知识,通过重构推特网发推特的页面,上衫想我们展示了一个完整的 React 世界。

这篇文章,对我这类从实践中学习的人而言是很有帮助的。每当一个新的框架出来后,我都会用它重新把东西实现一遍。因此,我也仿照这篇文章,带你重温我最近学习 Vue 的过程。

我强烈建议在读我接下来写的内容之前,先读一下上衫写的那篇文章。因为他在文章中介绍了实现这些效果的 JQuery 代码,尽管其中的一些代码比较简单。所以我这里就不多说了,直接进入 Vue 的主题。

我们要做什么

推特很多人都用,下面这个界面大家都很熟悉。

发推特的弹窗界面

这个界面是一个很好的例子,像我们展示了如何用 Vue (或者上衫说的 React)来提升 JS 编程的舒适度。这个界面由以下几个部分构成:

  • 用来输入推文的 <textarea>

  • 发推文的 <button>,如果推文超过指定长度按钮就 disabled

  • 一个剩余可输入字数的计数器,小到某个阈值就变色提示用户快到规定字数了

  • 一个相机图表,点击可以上传图片

  • 一个列表显示已经上传的图片

  • 每个照片上有一个按钮,点击就可以删除照片

欢迎向我咨询

如果你感觉文章中什么地方很困惑,我没有解释清楚,可以立马在推特上找我 @mattrothenberg。一定要记住,这篇文章是我的感受,可能和你的不完全一样。

那么我们开始吧。

写代码的过程

我用 CodePen 来完成这个例子。CodePen 就不过多介绍了。

第一步: 搭好框架

在写 JS 之前,需要先写一个静态的发推特页面。用 Tachyons 可以快速搭建好看的前端页面,Tachyons 让我们少写很多 CS,专注于 JS 和静态页面的搭建。

我不会详细说 Tachyons 怎么用,假设你已经会用了。

我已经提前把 Vue 通过 CDN 加载好了,Vue 的一大优点就是精简,可以融入其它代码库。

万事俱备,就开动吧。

Step 2: 第一个功能——发推特的按钮开始应该是禁用的

To Disable or Not To Disable, that is the question

功能描述: 当用户向 textarea 输入至少一个字符才能移除禁用。

首先,用以下创建一个 Vue 实例。可见 Vue 以其简单易用的特性受广大开发者的喜爱。

new Vue({
  el: '#twitterVue',
  data: {
    tweet: ''
  },
  computed: {
    tweetIsEmpty: function() {
      return this.tweet.length === 0;
    }
  }
})

我来解释一下刚刚的代码是干嘛的:

  • el 是 Vue 实例所绑定的一个 DOM 元素,和 jQuery 的属性选择器一个意思。

  • data 是连接 Vue 实例和 DOM 元素的属性。可以在 HTML 中用{{tweet}}来获取其中的数据,也可以在 Vue 实例中用 tweet 访问这个数据(注意 tweetIsEmpty函数就使用了这个属性)。

  • computed 顾名思义,是用 Vue 实例中已有的属性计算出来的属性。推荐在 computed 中定义逻辑方法来得到这类需要计算的值(状态),可以避免在 HTML 中引入繁琐的逻辑。

现在请注意我们的 HTML 相比之前也有一些小小的三个变化:

1. 给最外层的 div 加上了 twitterVue 的 id,这样就完成了我们的 Vue 实例创建。

<div id="twitterVue">...</div>

2. v-model 指令在用户输入和 Vue 实例的 data 之间创建了双向绑定data 属性里tweet的值会随着用户在textarea输入的改变而自动改变。

<textarea v-model="tweet"></textarea>

3. 添加了 :disabled属性到按钮中。disabled前的冒号将引号内的语句当成 JavaScript 表达式运行。如果我们忽略冒号,内容将被视为一个字符串。 我还添加了几行 CSS,使禁用按钮具有独特的视觉样式。

<button :disabled="tweetIsEmpty">Tweet</button>
button[disabled] {
cursor: not-allowed;
opacity: .5;
}

4. 在实例上添加了一个计算属性tweetIsEmpty。该属性实际上是基于tweet属性的长度返回一个布尔值的函数。 Vue 使得在 HTML 和实例本身中访问数据模型变得简单。由于双向数据绑定的魔力,当 tweet 的值更新时,tweetIsEmpty也会重新运行,求值为true时,按钮被禁用。

tweetIsEmpty: function() {
return this.tweet.length === 0;
}

不得不说,当我第一次使用Vue时,感觉非常魔幻。当然真正了解这些函数和指令是如何操纵实例和 HTML 后 Vue 对我帮助更大。简而言之可以通过前面提到的大括号语法轻松访问 HTML中的数据模型,构建一个快速、可视化的反馈循环,这就够了。

<p>The value of <strong>tweet </strong>is: **{{tweet}}** </p>
<p>The value of <strong>tweetIsEmpty</strong>is: **{{ tweetIsEmpty}}**</p>

可以随意重复这些步骤,如果有什么地方感到困惑(我的写作或代码可能有问题,或是 Vue 本身的问题),可以发送推文或发表评论。

实现第二个功能 - 显示剩余的字符数

功能描述:当用户输入时,显示推文中剩余可键入的字符数(最多 140 个)。如果用户输入的字符超过 140 个字符,就禁用蓝色的按钮。

现在我们已经学会了双向数据绑定和计算属性,它们是 Vue 的核心概念。因此可以利用这些概念来构建我们的下一个功能:显示用户剩下多少个字符(140个),当没有剩余时就禁用该按钮。

下面是实现此功能所需的 JavaScript 和 HTML 的一些改动。

首先更改 JS :

1. 首先定义一个常量 MAX_TWEET_LENGTH 表示允许输入的最大字符数

const MAX_TWEET_LENGTH = 140;

2. 然后添加另一个计算属性charactersRemaining,动态返回 140 和用户输入的tweet的长度之差

charactersRemaining: function() {
  return MAX_TWEET_LENGTH - this.tweet.length;
}

3. 最后将旧的 tweetIsEmpty 属性重命名为 tweetIsOutOfRange 并更新函数逻辑。注意如何使用计算属性charactersRemaining来计算该值。 为这里的代码复用喝彩!

tweetIsOutOfRange: function() {
  return this.charactersRemaining == MAX_TWEET_LENGTH 
      || this.charactersRemaining < 0;
 }

HTML方面,得益于Vue的双向数据绑定功能,只需要做一些小修改就行了。

<div class="flex items-center">
  <span class="mr3 black-70">**{{ charactersRemaining }}**</span>
  <button **:disabled="tweetIsOutOfRange"** class="button-reset bg-blue bn white f6 fw5 pv2 ph3 br2 dim">Tweet</button>
</div>

下面这个动图就是这些代码达到的效果

步骤 4:实现第三个功能,「剩余字符」的条件样式

功能说明:撰写推特仅剩 20 个字符时,「剩余字符」的颜色应变为深红色,剩下十个或更少时变为浅红色。

使用 jQuery 操作元素的样式或 class 可能很麻烦,Vue 提供了一种更简洁的方法。Vue 的方法感觉更具声明性,只需描述想要改变某种样式的方式(比如基于某个给定的属性),把其它繁重的任务交给 Vue 完成。

本例中,字符剩余有两种状态,每个都需要一个相应的 class。

1. 当剩余字符数是十到二十个时应该添加 dark-red 的 class

2. 当剩余字符数小于十个时应该添加 light-red 的 class

现在你在潜意识里应该大喊「计算属性!」所以,让我们听从这个想法,并把这些属性用computed方法关联起来。

underTwentyMark: function() {
  return this.charactersRemaining <= 20 
    && this.charactersRemaining > 10;
  },
underTenMark: function() {
  return this.charactersRemaining <= 10;
}

根据我们的逻辑,让我们来看看 Vue 处理条件样式的方法之一:v-bind:class指令。此伪指令的参数是一个键为 CSS class 的对象,键对应的值是计算属性。

{ 'dark-red': underTwentyMark, 'light-red': underTenMark }

在包含「剩余字符」指标的span标签中添加以上指令,我们就完成了所需要的功能。

<span v-bind:class="{ 'dark-red': underTwentyMark, 'light-red': underTenMark }">
  {{ charactersRemaining }}
</span>

在这些钩子函数和双向数据绑定的作用下,Vue 将根据指定的计算属性来添加和删除这些类。

步骤5:实施第四个功能:上传照片

功能说明:允许用户通过文件选择器对话框将一张照片附加到他们的推文。上传照片后,将其显示在textarea下方,并允许用户通过单击图像删除附件。

请注意:本节内容比较多。尽管看起来本步骤增加了相当多的功能,但我们不必编写太多的代码。现在让我们将交互逻辑分解为小步骤来分析。

1. 用户点击「添加照片」按钮

2. 用户看到一个文件选择器对话框,可以选择一个要上传的照片

3. 选择照片后,将在文本区域下方显示一个框,其中包含所选的照片

4. 用户点击圆形 X 按钮删除照片

5. 用户回到步骤 1

到目前为止,我们还没有完成任何事件处理(监听按钮的点击和输入的更改等)。Vue 可以通过v-on指令(简称@)来监听事件。通过传递方法作为此指令的值,我们可以有效地监听 DOM 事件,并在事件触发时运行指定的 JavaScript。

在编写我们需要的功能之前,我们先来一个简单的练习

事件处理非常简单,将@click指令添加到给定按钮就可以了,并将相应的方法添加到 Vue 实例的method里。

<button  @click"="logNameToConsole">Log User's Name</button>

methods: {
  logNameToConsole: function() {
    if( this.name !== 'Donald Trump' ) {
      console.log(this.name); 
    } else {
      console.warn('Sorry, I do not understand');
    }
  },
}

现在来编写我们需要的功能 ... 我们的 HTML 和 JavaScript 需要做如下变化:

我们添加了一个带有@click指令的按钮。当用户单击此按钮时,将会调用triggerFileUpload方法。

<button [@click](http://twitter.com/click "Twitter profile for @click")="triggerFileUpload">...</button>

所以,在我们的 JavaScript 中,为我们的Vue实例添加一个method方法和新的data属性如下:

data: {
 photo: null
},
computed: {},
methods: {
  triggerFileUpload: function() {
    this.$refs.photoUpload.click(); // 这是什么鬼?
  },
}

自定义 HTML5 文件输入样式非常困难。 一个解决方法是将一个<input>元素放在 DOM 中并用 CSS 隐藏它。为了让浏览器打开本地文件选择器,又必须单击这个隐藏的<input>元素。不管怎样,如何点击<input>元素和客户端怎样处理用户上传的内容是没有关系的。

在我们的 HTML 中,我们添加了一个这样的<input>元素并用一个特殊的 hide class 隐藏它。 我们还添加了一些其他属性:

<input ref="photoUpload" @change="handlePhotoUpload" type="file" class="hide">
  • ref属性用于注册给定 DOM 元素的引用,在 JavaScript 代码中可以使用$refs.photoUpload来访问我们的 DOM 元素。这意味着我们可以通过编程方式触发此元素上的click事件,从而规避了上述难题。

  • 点击<input>元素是一个难题; 处理用户上传的文件是另一个难题。幸运的是,Vue 允许我们通过@change指令将处理程序绑定到<input>元素的更新事件上。在用户从文件选择器中选择文件后,我们绑定在该指令的方法将被调用。 该方法handlePhotoUpload是相当简单的

handlePhotoUpload: function(e) {
  var self = this;
  var reader = new FileReader();

  reader.onload = function(e) {
    // Set that base 64 string to our data model's 'photo' key
    self.photo = (e.target.result);
  }
  // Read upload file as base 64  string
  reader.readAsDataURL(e.target.files[0]);
 }

深吸一口气,因为我们几乎快完成这个功能了!

用户上传照片后,我们需要在textarea下方显示一个包含所选照片的框。正如元素的条件样式用 Vue 能轻松实现一样,条件渲染也是小菜一碟。注意我们在textarea下面添加了以下 HTML:

<div v-if="photoHasBeenUploaded">
  <figure>
    <button @click="removePhoto">
      ...
    </button>
    <img v-bind:src="photo">
  </figure>
</div>

Vue 提供了一些模板助手(v-ifv-showv-else等)来有条件地显示和隐藏内容。当传递给该指令的 JavaScript 表达式求值为 true 时,元素将被渲染,反之隐藏。

在我们的例子中,我们添加了一个v-if语句来用计算属性photoHasBeenUploaded来控制是否渲染图片。

photoHasBeenUploaded: function() {
  return this.photo !== null;
}

当该函数返回值为真时(当dataphoto键不等于null时)整个div被渲染。

而在div里面,我们渲染了两个元素:

1. 通过将dataphoto键的内容传递给 Vue 的v-bind:src指令来加载图像

2. 一个删除按钮,其中利用了@click来处理点击事件,绑定了一个函数,通过将dataphoto键的内容设置为null来「删除」照片

removePhoto: function() {
  this.photo = null;
}

我们就快完成任务了。

步骤6:更正,用户可以上传多张「照片」

我们现在可以有效地处理一个用户上传一张照片到推特,但如果 Ta 想上传更多照片怎么办?

到目前为止,你应该考虑一下:「之前我们已经绑定了我们的事件处理函数,我想这里唯一重大的变化就是能够在textarea下有条件的显示出多个图像...」你想的很正确!我们来看看我们需要遵循的步骤:

1. 我们需要通过将dataphoto键更改为photos来更新我们的数据模型,新的键是一个 base64 字符串的数组(不是一个 base64 字符串)

data: {
  photos: []
},

2. 我们需要更新我们的计算属性photoHasBeenUploaded来检查我们的photos数组的长度

photoHasBeenUploaded: function() {
  return this.photos.length > 0;
}

3. 我们需要更新我们输入监听@change绑定的处理函数,循环上传的文件并将其推送到我们的photos数组里。

handlePhotoUpload: function(e) {
  var self = this;
  var files = e.target.files;

  for(let i = 0; i < files.length; i++) {
    let reader = new FileReader();

    reader.onloadend = function(evt) {
 self.photos.push(evt.target.result);
    }

 reader.readAsDataURL(files[i]);
  }
},

但是,在HTML方面,我们必须用新的方法,因为用 jQuery 来迭代数据和渲染内容可能很麻烦。

var array = [1, 2, 3, 4, 5];
var newHTML = [];
for (var i = 0; i < array.length; i++) {
 console.log('UGHHHHHH');
    newHTML.push('<span>' + array[i] + '</span>');
}
$(".element").html(newHTML.join(""));

幸运的是,Vue 通过v-for指令提供了对该过程的抽象。这个指令的参数是(thing,index)的一个集合形式,这种集合一般是是源数组,thing指代数组中的元素,而index是该元素的索引。

一个典型的例子可能如这个 Codepen

之前我们为用户上传的照片提供了一个单一的figure元素,我们现在将有Nfigure元素对应于photos数组的长度。

幸运的是,由于设计的整体结构仍然相同,因此我们的 HTML 不必变化太大。

<figure v-for="(photo, index) in photos">
  <button @click="removePhoto(index)">
    ...
  </button>
  <img v-bind:src="photo" class="h3 w3">
</figure>

我们需要做的一个改变是removePhoto这个方法,之前,这个方法将将data的单个photo键的内容设置为null。 现在,由于我们有N张照片,我们必须将元素的索引传递给removePhoto方法,并将该元素从数组中删除。

removePhoto: function(index) {
  this.photos.splice(index, 1);
}

步骤7:动画 + 额外修改

在推特的用户界面中,编写推特的组件是以模态弹窗的形式打开。我们的最后一个任务就是实现这个弹窗,我想要应用我们迄今为止学到的所有 Vue 技术,再加一个新的 Vue 技术 —— 过渡。

实际上,过渡是 Vue 的一个重要的内容,我们现在实现的动效仅仅是其中的冰山一角,我们用第三方动画库 Velocity.js 与 Vue 集成。

简而言之, Vue 提供了一个过渡组件,可以为包含的元素添加进入/离开的动画,前提是该元素通过例如v-ifv-show指令有条件地显示。

<transition 
  name="modal-transition"
  v-on:enter="modalEnter"
  v-on:leave="modalLeave">
    <div v-if="modalShowing"> 
      <!-- Our modal contents goes here ! -->
    </div>
</transition>

在我们的示例中,我们附加了两个对应于过渡生命周期中的两个事件方法:v-on:enterv-on:leave。将这些方法添加到 Vue 实例中,让 Velocity.js 来淡入淡出我们的弹窗。

methods: {
  modalEnter: function(el, done) {
    Velocity(el, 'fadeIn', { duration: 300, complete: done, display: 'flex' })
  },
  modalLeave: function(el, done) {
    Velocity(el, 'fadeOut', { duration: 300, complete: done })
  }
}

如上所述,当包含的元素被有条件地设置为显示时,过渡动效将触发。因此,在我们的过渡组件的内部div上,我们添加了一个v-if声明,其值为一个布尔的modalShowing,让我们及时更新我们的实例的数据模型。

data: {
  modalShowing: false
}

现在,当我们要显示弹窗时,我们所要做的就是将布尔值设置为true

<button @click="showModal">Compose Tweet</button>

并写一个函数来改变布尔值。

hideModal: function() {
  this.modalShowing = false;
},
showModal: function() {
  this.modalShowing = true;
},

这里用了一些 CSS 奇技淫巧,我们还将一个点击事件处理函数绑定到页面背景,因此用户可以隐藏弹窗。厉害吧!

<div 
    @click="hideModal" class="backdrop">
</div>

总结

我希望以上过程不是太痛苦(并且你一路上也学到了一些东西)。 我们只接触了一下 Vue 的冰山一角,但是这些概念对于解锁 Vue 大师级成就至关重要。

我承认,将 Vue 与 jQuery 进行比较是不公平的,因为它们是不同时代的产品,有各不相同的用例。 然而,对于那些通过 jQuery 学习 DOM 操作和事件处理的你来说,我希望这些概念能够让你耳目一新,你可以将其应用于你之后的工作中。

相关文章