少年阿布DX

一览 Vue.js 2 – dotdev

少年阿布DX · 2017-02-20翻译 · 496阅读 原文链接

Vue.js 2 就要来了,我们都对此感到非常激动。Vue 的新版本除了带来了巨大的提升与许多新特性(虚拟 DOM、服务端渲染、JSX / Hyperscript 支持等),也做了一些舍弃与改变。

新特性

所有 Vue 2 带来的这些新特性听起来都很棒。但在实践它们之前,你应该熟悉这些特性到底是什么。

虚拟 DOM

虚拟 DOM 是真实 DOM 的抽象 / 轻量级版本。

它最主要的理念就是不直接操作 DOM,而是操作虚拟 DOM。在与真实 DOM 比较之后,真实 DOM 中需要被改变的部分会被重新渲染。

相对于直接与真实 DOM 打交道来说,这快多了。因为它避免了许多重量级的操作直接发生在真实 DOM 上。

如果你想了解更多关于虚拟 DOM 的内容,我认为你真的需要读下面这篇 Tony Freed 写的文章。

什么是虚拟 DOM?

服务端渲染

JavaScript 不仅能在浏览器运行,也能通过使用像 Node.js 的平台跑在服务器上。

通过在服务端运行 JavaScript,你能生成 HyperText 或 HTML 然后直接发送到客户端浏览器。浏览器在渲染模版时会导致短暂的空模版闪现(在看到真实的内容前,你可能先看到一对空的 ’{{}}’),服务端渲染通常用来避免这个问题。

服务端渲染通常会造成内容会很快出现,而到可以交互的时间会轻微增长。

SSR(服务端渲染)对 SEO、Facebook 的 meta tag 等也很有用。

此外,人们通常借助 JavaScript 框架如 MeteorExpressSails 等来使用 SSR 构建 API。

我最喜欢 SSR 的一点就是服务端做更多的工作,客户端做更少的工作。对那些使用老旧电脑或不那么强力的手机访问网页的用户来说,这无疑救他们于水火。

JSX(类 XML 的语法扩展)

JSX 是对 ECMAScript 的类 XML 语法扩展,没有任何新定义的语义。它并不打算被引擎或浏览器实现,它期望被各式预处理器(转译器)用来将这些词元转译成标准的 ECMAScript。

例如:

// 用 JSX 表示一个组件
let dropdown =
  <Dropdown>
    A dropdown list(一个下拉列表)
    <Menu>
      <MenuItem>Do Something</MenuItem>
      <MenuItem>Do Something Fun!</MenuItem>
      <MenuItem>Do Something Else</MenuItem>
    </Menu>
  </Dropdown>;

render(dropdown);

JSX 的官网是 http://facebook.github.io/jsx/

Hyperscript

Hyperscript 被用来在客户端或服务器用 JavaScript 创建 HyperText(译者注:超文本。HTML 全程就是超文本标记语言)。

看下面的例子和它的输出:

var h = require('hyperscript');
h('h1.fun', {style: {'font-family': 'Comic Sans MS'}}, 'Is Guybrush a French name?');

带有 “fun” 类的标题

你能在它们的可交互 Demo 页游玩一番。

舍弃与改变

我将搭建一个简单的基于 Vue 2 的 app,并借此来指出两个版本之间的不同(译者注:1.x 和 2)。

我会设计的主要一些点有:

  • 事件
  • 舍弃 .sync 修饰符
  • 舍弃 vm.$set() 修饰符
  • 组建模版包装

再见 .sync

有一个我们我都将会怀念的东西,那就是使用 “.sync" 实现的在父子组件之间同步数据的能力。从现在开始,props 只会单向向下传播。如果一个组件需要修改不在其作用域的数据,它必须触发一个事件,而不是依赖于隐式结合(implicit binding)。

舍弃 .sync 的目的是为了阻止子组件在父组件作用域中产生副作用。按这种新方式,当你在阅读组件代码时,你会更容易理解它在干什么,会如何影响你的应用的其他部分。

Vue 版本对比

使用 Vue 1.0.*

我将实现的场景是关注。有一个用户可以购买宝石的网页,我们有一个 Vue 实例与一个 Cart (译者注:购物车)组件。在 Cart 组件中,用户会选择他想购买的宝石数量,然后这个组件会向其父组件反映这个改变。

使用 Vue.js 1.0.*,实现会很简单。我们会创建一个变量来存储被选择的数量,然后将它传递至会更新它的值的组件,最后返回到父组件。在 Vue 中,我们可能需要可计算属性来计算总价。

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.23/vue.js"></script>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <meta charset="utf-8">
  <title>Gem Market</title>
</head>
<body>
  <div class="container">
    <h1>Gem Market</h1>
    <cart :quantity.sync="quantity"></cart>
    <hr>
    <div>
      <h3>You have to pay <strong>{{ gold }}</strong> pieces of gold.</h3>
    </div>
  </div>

  <template id="cart-template">
    <h2>How many gems would you like to buy?</h2>
    <p>1 emerald costs 100 pieces of gold.</p>
    <input v-model="quantity" class="form-control" number>
  </template>
</body>

<script type="text/javascript">
Vue.component('cart', {
  template: "#cart-template",
  props: { quantity : 0 }
});
new Vue({
  el: '.container',
  data : function () {
    return {
      quantity: 0
    };
  },
  computed:{
    gold: function(){
      return this.quantity * 100
    }
  }
})
</script>
</html>

Vue.js 1.0.*

使用 Vue 2

如果我们到 Vue 2,运行上面的代码时,我们会得到下面的警告。

[Vue warn]: Component template should contain exactly one root element.

(译者注:[Vue 警告]: 组件模板会要包含至少一个根元素)

为了消除这个警告,我们会把组件的模板封装到一个 'div' 元素中。

<template id="cart-template">
  <div>
    <h2>How many gems would you like to buy?</h2>
    <p>1 emerald costs 100 pieces of gold.</p>
    <input v-model="gems" class="form-control" number>
  </div>
</template>

根元素

再刷新浏览器时,我们会看到警告会消失。

当我们试图改变 quantity 时会得到警告是:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value. Prop being mutated: “gems” (found in component: < cart>)

(译者注:[Vue 警告]:避免直接改变一个 prop,因为它的值会在父组件重新渲染时被重写。然而,使用基于 prop 值的 data 或可计算属性时。Prop 被改变了:“gems”(组件:< cart>)

这是因为 Vue 2 中局部改变一个 prop 会被当成是 反模式。因为新的渲染机制,无论何时父组件重新渲染,自组件的本地改变都会被重写。现在你应该把 props 当成不可变(immutable)的

在面向对象与函数式编程中,不可变对象是指在创建后它的状态不能被改变的对象。

所以不要直接更新 quantity prop 而是与它的父组件同步,我们会让我们的组件提交一个事件到父组件。当然,它也需要监听 quantity 的改变,然后在它发生时用新的值触发事件。我们甚至不需要 prop。

由于 'vm.$dispatch' 和 `vm.$broadcast' 已经被舍弃,为了触发一个事件,我们将使用一个集中式事件中心让组件之间可以通讯,正如升级贴士中建议的那样。

由于 Vue 实例实现了 event emitter 接口,实际上我们可以使用一个空的 Vue 实例来作为我们的事件触发器。

回到例子,创建事件触发器并重构 Cart 组件。

let bus = new Vue()

Vue.component('cart', {
  template: "#cart-template",
  data () {
    return {quantity : 0 }
  },
  watch: {
    'quantity': function (quantity, oldQuantity) {
      console.log('quantity changed from %s to %s', oldQuantity, quantity)

      bus.$emit('quantity-changed', quantity)
    }
  }
});

如果你看控制台,你会注意到每次你改变 quantity 的值时,你都能看到它当前与之前的值。

浏览器控制台输出

还剩最后一件事,修改父组件 Vue 实例,监听并更新 quantity 数据。为此我们可以在 created hook(译者注:钩子)里建立事件监听器。


new Vue({
  el: '.container',
  data : function () {
    return {
      quantity: 0
    };
  },
  created: function () {
    // 使用 Vue.set 存储
    var temp = this;
    bus.$on('quantity-changed', function (quantity) {
      // vm.$set 已舍弃
      Vue.set(temp, 'quantity', quantity)
    })
  },
  computed:{
    gold: function(){
      return this.quantity * 100
    }
  }
})

注意 1:'vm.$set()' 已经被舍弃,所以我们得使用全局的 'Vue.set()' 方法。 由于我们不再需要一个属性来同步两个 quantities,所以我们可以像这样引用 Cart 组件:

<cart></cart>

注意 2: 'lazy' 与 'number' 参数现在是装饰器了,所以我们不得不更新 Cart 的 input 标签:

// Vue 1
<input v-model=”quantity” class=”form-control” number>
// Vue 2
<input v-model.number=”quantity” class=”form-control”>

这就行了!你可以在 JSFiddle 看到最终的代码并畅玩一番。

有用的资源

下面是我建议你看看的资源列表。

书的更新

因为每个人都在问我的书是否会更新,跟上 Vue 2 的改变,答案是是!一旦 Vue 2 稳定了,我们计划更新大部分书中的实例,展示如何使用 Vue 两个版本 来构建它们!。

如果你还没读过,快到 LeanPub 看一看!

发信给我

欢迎对本篇教程的反馈和问题或任何其他的东西。你也能在推特上关注我,来获悉更多关于 Vue.js 与 Laravel 的更新。

相关文章