从 jQuery 过渡到 Vue

Avatar of Raymond Camden
Raymond Camden

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

作为一名使用 jQuery 多年的用户,最近也成为了 Vue 的使用者,我认为讨论从 jQuery 过渡到 Vue 的迁移过程是一个有趣的话题。

不过,在我开始之前,我想确保一件事非常清楚。我绝不会,也绝不会告诉任何人停止使用 jQuery。这在最近几年非常流行,而且,几年前我自己也写了一些类似的内容(“我(不)使用 jQuery 的方式”)。如果您使用 jQuery 完成工作,并且最终用户成功地使用了您的网站,那么您就更强大。继续使用对您有效的工具。

本指南更适合那些可能拥有多年 jQuery 经验,并想了解 Vue 如何完成这些工作的人。考虑到这一点,我将专注于我认为的 jQuery 的“核心”用例。我不会涵盖所有可能的特性,而是采用一种“我经常使用 jQuery 做 [X]”的方法,这种方法可能更能与考虑学习 Vue 的人产生共鸣。(顺便说一下,还要注意,我编写示例的方式仅仅是执行任务的一种方式。jQuery 和 Vue 都提供了多种方法来实现相同目标,这是一件好事!)

考虑到这一点,让我们考虑一些我们可能会转向 jQuery 的高级事物

  • 在 DOM 中查找内容(以便稍后对其进行操作)
  • 更改 DOM 中的内容(例如段落的文本或按钮的类)
  • 读取和设置表单值
  • 表单验证(实际上只是上面几项的组合)
  • Ajax 调用和处理结果
  • 事件处理(例如,单击按钮时执行某些操作)
  • 测量或更改元素的样式

当然,jQuery 还有更多功能,但这些用途(至少在我看来)涵盖了最常见的用例。还要注意,上面的列表中有很多交叉授粉。那么,我们应该从对每个用例进行一对一的简单比较开始吗?不,不要着急。让我们先从介绍 Vue 应用程序中的主要差异开始。

定义 Vue “工作”的位置

当我们将 jQuery 放到页面上时,我们实际上是在向 JavaScript 代码添加一把瑞士军刀,用于处理常见的 Web 开发任务。我们可以按照我们认为合适的任何顺序执行我们将要涵盖的任何用例。例如,客户今天可能要求进行表单验证,然后在一个月或更长时间后,要求在网站的页眉中进行基于 Ajax 的搜索表单。

Vue 在这方面有一个显著的区别。在开始一个 Vue 项目时,我们首先要定义一个 DOM 中的“区域”,我们希望它专注于该区域。因此,让我们考虑一个简单的原型网页

<body>

  <header>
    Fancy header stuff here
  </header>

  <div id="sidebar">
    Some sidebar doohicky here
  </div>

  <main>
    <p>
      Main site content here, super important stuff...
    </p>
    <div id="loginForm">
      A login form of course
    </div>
  </main>

</body>

在典型的 jQuery 应用程序中,我们可能编写代码来处理页眉、侧边栏和登录表单等。没什么大不了的

$(document).ready(function() {

  $('header') // something...

  $('#sidebar') // something...

  $('#loginForm') // something... 

});

在 Vue 应用程序中,我们首先要指定我们正在处理的内容。假设客户首先要求向 loginForm 元素添加验证。我们的 Vue 代码会指定

new Vue({
  el: '#loginForm',
  // Code here...
});

这意味着,如果客户后来决定让我们向侧边栏添加内容,我们通常会最终添加第二个 Vue 应用程序

new Vue({
  el:'#loginForm',
  // Code here...
});

new Vue({
  el:'#sideBar',
  // Code here...
});

这不好吗?当然不是。我们马上就能获得封装的好处。如果我们不小心使用了具有通用名称的变量(我们都做过),我们就不必担心它与代码的其他部分发生冲突。之后,当客户添加另一个请求时,我们的独特、逻辑的 Vue 代码集像这样分离出来,让我们放心地认为这些代码不会相互干扰。

所以,是的,这是一件好事。但这绝对让我在第一次开始使用 Vue 时停顿了一下。现在,让我们继续介绍我们的用例。

在 DOM 中查找内容

您会发现另一个有趣或可怕的方面是“如何在 DOM 中查找内容”。这有点模糊,但让我们考虑一个具体的例子。我们有一个按钮,单击它时,我们会执行某些操作。以下是如何执行此操作的简化示例

<button id="myButton">Click Me!</button>
<!-- More stuff... -->
<script>
$(document).ready(function() {

  $('#myButton').click(function() {
    alert(1);
  });

});
</script>

现在让我们将其与 Vue 如何执行此操作进行比较

<div id="app">
  <button v-on:click="doSomething">Click Me!</button>
</div>

<script>
const app = new Vue({
  el:'#app',
  methods: {
    doSomething: function() {
      alert(1);
    }
  }
});
</script>

Vue 应用程序比较冗长,但请注意标记之间如何直接连接操作(“click”)和将要调用的函数。Vue 的代码与 DOM(除了定义其工作位置的 el 部分之外)没有关联。这很容易成为我最喜欢 Vue 的东西之一——**感觉更容易弄清楚发生了什么。**此外,我不需要过于担心 ID 值和选择器。如果我更改按钮的类或 ID,就不需要回到代码中担心更新选择器。

让我们考虑另一个例子:在 DOM 中查找和更改文本。假设单击该按钮会更改 DOM 中另一个部分的文本。

<button id="myButton">Click Me!</button>
<span id="result"></span>

<!-- More stuff... -->

<script>
$(document).ready(function() {

  $('#myButton').click(function() {
    $('#result').text('You clicked me, thanks!');
  });

});
</script>

我添加了一个新的 span,现在,单击按钮时,我们使用另一个选择器来查找它,并使用 jQuery 实用程序方法更改其内部的文本。现在让我们考虑 Vue 版本

<div id="app">
  <button v-on:click="doSomething">Click Me!</button>
  <!-- On click, change text in this span -->
  <span>{{resultText}}</span>
</div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    resultText: ''
  },
  methods: {
    doSomething: function() {
      this.resultText = 'You clicked me, thanks!';
    }
  }
});
</script>

在这个例子中,我们使用 Vue 的模板语言(突出显示的行)来指定我们要在 span 内渲染一个变量,在本例中为 resultText。现在,单击按钮时,我们更改该值,span 的内部文本将自动更改。

顺便说一下,Vue 支持 v-on 属性的简写,因此示例中的按钮可以写成 @click="doSomething"

读取和写入表单变量

处理表单可能是我们使用 JavaScript 最常见且最有用的事情之一。甚至在 JavaScript 之前,我早期的“Web 开发”大部分时间都在编写 Perl 脚本以处理表单提交。作为接受用户输入的主要方式,表单一直是 Web 的关键,而且这种情况很可能在相当长一段时间内都会持续下去。让我们考虑一个使用 jQuery 读取一些表单字段并设置另一个字段的简单示例

<form>
  <input type="number" id="first"> + 
  <input type="number" id="second"> =
  <input type="number" id="sum"> 
  <button id="sumButton">Sum</button>
</form>

<script>
$(document).ready(function() {
  let $first = $('#first');
  let $second = $('#second');
  let $sum = $('#sum');
  let $button = $('#sumButton');
  
  $button.on('click', function(e) {
    e.preventDefault();
    let total = parseInt($first.val(),10) + parseInt($second.val(),10);
    $sum.val(total);
  });
});
</script>

这段代码演示了 jQuery 如何通过 val() 方法 读取和写入。我们最终从 DOM 中获取了四项(三个表单字段和一个按钮),并使用简单的数学运算来生成结果。现在让我们考虑 Vue 版本

<form id="myForm">
  <input type="number" v-model.number="first"> + 
  <input type="number" v-model.number="second"> =
  <input type="number" v-model="sum"> 
  <button @click.prevent="doSum">Sum</button>
</form>

<script>
new Vue({
  el: '#myForm',
  data: {
    first: 0,
    second: 0,
    sum: 0
  },
  methods: {
    doSum: function() {
      this.sum = this.first + this.second;
    }
  }
})
</script>

这引入了一些有趣的 Vue 快捷方式。首先,v-model 是 Vue 如何在 DOM 中的值和 JavaScript 中的值之间创建双向数据绑定。data 块中的变量将自动与表单字段同步。更改数据,表单将更新。更改表单,数据将更新。.number 是一个指示 Vue 将表单字段的继承字符串值视为数字的标记。如果我们省略它,并像我们正在做的那样进行加法运算,我们将看到字符串加法,而不是算术运算。我使用 JavaScript 已经快一个世纪了,但仍然会犯这个错误。

另一个巧妙的“技巧”是 @click.prevent。首先,@click 为按钮定义一个点击处理程序,然后 .prevent 部分阻止浏览器默认的表单提交行为(等效于 event.preventDefault())。

最后一段是添加了绑定到该按钮的 doSum 方法。请注意,它只是使用数据变量(Vue 在 this 范围内提供了这些变量)。

虽然这主要是我个人的感觉,但我真的喜欢在 Vue 中编写时脚本中没有查询选择器,以及 HTML 如何更清楚地说明它正在做什么。

最后,我们甚至可以完全去掉按钮

<form id="myForm">
  <input type="number" v-model.number="first"> + 
  <input type="number" v-model.number="second"> =
  <input type="number" v-model="sum"> 
</form>

<script>
new Vue({
  el: '#myForm',
  data: {
    first: 0,
    second: 0
  },
  computed: {
    sum: function() {
      return this.first + this.second;
    }
  }
})
</script>

Vue 的一个很酷的功能是计算属性。它们是虚拟值,可以识别其派生值何时更新。在上面的代码中,只要两个表单字段中的任何一个发生更改,总和就会更新。这在表单字段之外也有效。我们可以像这样渲染总和

The total is {{sum}}.

使用 Ajax

jQuery 使使用 Ajax 变得多么容易,这值得称赞。事实上,我可以说我“原汁原味”地使用过 Ajax 恐怕总共只有一次。(如果您好奇,可以查看 XMLHttpRequest 的规范,您可能会庆幸自己避开了它。)jQuery 的简单 $.get(...) 方法在许多情况下都能正常工作,而当需要更复杂的操作时,$.ajax() 也让事情变得简单。jQuery 做得好的另一件事是它处理 JSONP 请求的方式。虽然现在由于 CORS 变得几乎没有必要,但 JSONP 是一种处理向不同域上的 API 发出请求的方法。

那么,Vue 做了什么让 Ajax 更轻松呢?什么也没有!

好吧,这听起来很可怕,但实际上并非如此。有很多方法可以处理 HTTP 请求,而 Vue.js 采取了一种更不可知论的态度,让我们这些开发人员决定如何处理它。所以是的,这确实意味着要做更多工作,但我们有一些很棒的选择。

首先要考虑的是 Axios,它是一个基于 Promise 的库,在 Vue 社区中非常流行。以下是在实践中使用的简单示例(摘自他们的 README 文件)

axios.get('/user?ID=12345')
  .then(function (response) {
    // handle success
    console.log(response);
  })
  .catch(function (error) {
    // handle error
    console.log(error);
  })
  .then(function () {
    // always executed
  });

当然,Axios 支持 POST 请求,并且允许我们在众多选项中指定标题。

虽然 Axios 在 Vue 开发者中非常流行,但我并没有真正喜欢它。(至少现在还没有。)相反,我更喜欢 Fetch。Fetch 不是一个外部库,而是一种执行 HTTP 请求的 Web 标准方法。Fetch 有 非常好的浏览器支持率,大约 90%,但这意味着它并不完全安全,但我们可以随时使用需要填充的填充。

虽然这完全超出了我们在这里讨论的范围,但 Kingsley Silas 写了一篇很棒的 关于在 React 中使用 Axios 和 Fetch 的指南

与 Axios 一样,Fetch 基于 Promise 并且有一个友好的 API

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });

Axios 和 Fetch 都涵盖了所有类型的 HTTP 请求,因此任何一种都能满足任何数量的需求。让我们看一个简单的比较。这是一个使用 Star Wars API 的简单 jQuery 演示。

<h1>Star Wars Films</h1>
<ul id="films">
</ul>

<script>
$(document).ready(function() {
  $.get('https://swapi.com/api/films', function(res) {
    let list = '';
    res.results.forEach(function(r) {
      list += `<li>${r.title}</li>`;
    });
    $('#films').html(list);
  });
});
</script>

在上面的示例中,我使用 $.get 访问 API 并返回电影列表。然后,我使用这些数据生成一个标题列表作为 li 标签元素,并将其全部插入到一个 ul 块中。

现在,让我们考虑使用 Vue 的示例。

<div id="app">
  <h1>Star Wars Films</h1>
  <ul>
    <li v-for="film in films">{{film.title}}</li>
  </ul>  
</div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    films: []
  }, 
  created() { 
    fetch('https://swapi.com/api/films')
    .then(res => res.json())
    .then(res => {
      this.films = res.results;  
    });
  }
})
</script>

也许最好的部分是使用 v-for 模板。请注意 Vue 如何不关心布局(至少 JavaScript 不关心)。数据是从 API 中获取的。它被分配了一个变量。布局负责显示它。我一直讨厌在我的 JavaScript 中使用 HTML,虽然 jQuery 有解决方案,但将其烘焙到 Vue 中使其成为自然选择。

一个完整(虽然有些琐碎)的示例

为了更贴近实际情况,让我们考虑一个更真实的例子。我们的客户要求我们构建一个精美的 Ajax 驱动的产品 API 前端搜索界面。功能列表包括

  • 支持按名称和产品类别进行过滤
  • 表单验证,要求我们必须提供搜索词或类别
  • 在访问 API 期间,向用户显示一条消息并禁用提交按钮
  • 完成后,处理报告没有找到产品或列出匹配项

让我们从 jQuery 版本开始。首先是 HTML

<form>
  <p>
    <label for="search">Search</label>
    <input type="search" id="search">
  </p>
  <p>
    <label for="category">Category</label>
    <select id="category">
      <option></option>
      <option>Food</option>
      <option>Games</option>
    </select>
  </p> 
  <button id="searchBtn">Search</button>
</form>

<div id="status"></div>
<div id="results"></div>

有一个包含两个筛选器和两个 div 的表单。一个用作搜索或报告错误时的临时状态,另一个用作渲染结果。

const productAPI = 'https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/productSearch';

$(document).ready(() => {
  let $search = $('#search');
  let $category = $('#category');
  let $searchBtn = $('#searchBtn');
  let $status = $('#status');
  let $results = $('#results');
  
  $searchBtn.on('click', e => {
    e.preventDefault();
    
    // First clear previous stuff
    $status.html('');
    $results.html('');

    // OK, now validate form
    let term = $search.val();
    let category = $category.val();
    if(term === '' && category === '') {
      $status.html('You must enter a term or select a category.');
      return false;
    }

    $searchBtn.attr('disabled','disabled');
    $status.html('Searching - please stand by...');
    
    $.post(productAPI, { name:term, category:category }, body => {
      $searchBtn.removeAttr('disabled');
      $status.html('');

      if(body.results.length === 0) {
        $results.html('<p>Sorry, no results!</p>');
        return;
      }
      
      let result = '<ul>';
      body.results.forEach(r => {
        result += `<li>${r.name}</li>`
      });
      result += '</ul>';
      $results.html(result);
    });
    
  });
});

代码首先创建一组变量,用于我们想要操作的每个 DOM 项,包括表单字段、按钮和 div。核心逻辑在按钮的单击处理程序中。我们进行验证,如果一切正常,则对 API 发出 POST 请求。返回后,我们会渲染结果,或者如果找不到匹配项,则显示消息。

你可以使用下面的 CodePen 尝试此演示的完整版本。

查看 Pen
jQuery 完整
,作者:Raymond Camden (@cfjedimaster)
CodePen 上。

现在让我们考虑 Vue 版本。同样,让我们从布局开始

<div id="app">
  <form>
    <p>
      <label for="search">Search</label>
      <input type="search" v-model="search">
    </p>
    <p>
      <label for="category">Category</label>
      <select v-model="category">
        <option></option>
        <option>Food</option>
        <option>Games</option>
      </select>
    </p>
    <button @click.prevent="searchProducts" :disabled="searchBtnDisabled">Search</button>
  </form>

    <div v-html="status"></div>
    <ul v-if="results">
      <li v-for="result in results">{{result.name}}</li>
    </ul>
</div>

从上到下,更改包括

  • 将布局包装在一个 div 中,以便 Vue 可以知道在哪里工作。
  • 使用 v-model 来处理表单字段,以便轻松地操作数据。
  • 使用 @click.prevent 来处理主要搜索操作。
  • 使用 :disabled 将按钮是否禁用的状态绑定到 Vue 应用程序中的一个值(我们将在稍后看到)。
  • 状态值与之前的示例略有不同。虽然 jQuery 有一个特定方法来设置 DOM 项中的文本,以及另一个方法用于设置 HTML,但 Vue 需要使用 v-html 来将 HTML 分配给要渲染的值。如果我们尝试对 {{status}} 使用 HTML,则标签将被转义。
  • 最后,使用 v-if 有条件地渲染结果列表,并使用 v-for 来处理迭代。

现在让我们看看代码。

const productAPI = 'https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/productSearch';

const app = new Vue({
  el: '#app',
  data: {
    search: '',
    category: '',
    status: '',
    results: null,
    searchBtnDisabled: false
  },
  methods: {
    searchProducts:function() {
      this.results = null;
      this.status = '';
      
      if(this.search === '' && this.category === '') {
        this.status = 'You must enter a term or select a category.';
        return;
      }

      this.searchBtnDisabled = true;
      this.status = 'Searching - please stand by...';
      
      fetch(productAPI, {
        method: 'POST',
        headers: {
          'Content-Type':'application/json'
        },
        body: JSON.stringify({name:this.search,category:this.category})
      }).then(res => res.json())
      .then(res => {
        this.status = '';
        this.searchBtnDisabled = false;
        this.results = res.results;
        if(this.results.length === 0) this.status = '<p>Sorry, no results!</p>';
      });
      
    }
  }
});

第一个值得一提的块是 data 字段集。有些映射到表单字段,而另一些映射到结果、状态消息等。searchProducts 方法处理与 jQuery 版本几乎相同的任务,但总的来说,与直接绑定到 DOM 的代码相比,代码更少。例如,即使我们知道结果列在一个无序列表中,代码本身并不关心这一点。它只分配值,而标记负责渲染它。总的来说,JavaScript 代码更多地关注逻辑,而 jQuery 代码则感觉更像是关注点分离。

与之前一样,我为你准备了一个 CodePen,你可以自己尝试一下。

查看 Pen
Vue 完整
,作者:Raymond Camden (@cfjedimaster)
CodePen 上。

jQuery 终结!Vue 万岁!

好吧,这有点夸张了。正如我在开头所说,我认为如果你喜欢使用 jQuery 并且它对你有用,你不应该改变任何东西。

不过我可以说,对于习惯使用 jQuery 的人来说,Vue 感觉是一个很棒的“下一步”。Vue 支持复杂应用程序,并且有一个很棒的命令行工具用于搭建和构建项目。但是对于更简单的任务,Vue 作为一个很棒的“现代 jQuery”替代品,已成为我的首选开发工具!

要了解使用 Vue 代替 jQuery 的另一种观点,请查看 Sarah Drasner 的 “用 Vue.js 代替 jQuery:无需构建步骤”,因为它包含了一些其他非常有用的示例。