Fork me on GitHub

窥探Vue.js 2.0

窥探Vue.js2.0

令人兴奋的Vue.js 2.0正式版发布了。新的VUE框架有重大的改进,添加了一些新的特性,比如 Virtual-DOM,Server Side Rendering(服务端渲染),支持 JSX/Hyperscript 语法等,并去掉了一些已有的方法。

新的特性

Virtual DOM

虚拟DOM 是一个真正DOM的抽象、轻量级版本。
它主要作用是让你操控虚拟DOM,来代替直接操控真是DOM。之后虚拟DOM和真是DOM比较,把两者差异的部分再渲染到页面上。
这比直接操作DOM的速度要快的多,因为DOM操作是非常耗时的,加入虚拟DOM对比之后减少不必要DOM操作花的时间比进行DOM和虚拟DOM对比的时间要多,所以采用虚拟DOM是更有优势的。更多关于 什么是Virtual Dom?什么是DOM ?

Server Side Rendering

我们知道 JavaScript 除了可以在浏览器上运行,也可以在服务器端运行,比如 Node.js 平台。JavaScript在服务端生成HypetText或者HTML文件发送给客户端的浏览器。
服务器端渲染(SSR)有几个特点:

  1. 服务器端渲染可以让搜索引擎更容易读取页面的meta信息以及其他SEO相关信息,大大增加网站在搜索引擎中的可见度。
  2. 更快的来生成内容和相对比较慢的来进行数据交互。
  3. 首次加载页面的速度加快。客户端渲染的一个缺点是,当用户第一次进入站点,此时浏览器中没有缓存,需要下载代码后在本地渲染,时间较长。而服务器渲染则是,用户在下载的已经是渲染好的页面了,打开速度比本地渲染快。
  4. 服务器端多更多的事情,让客户端做尽量少的事,从而可以照顾(不抛弃)那些手机或电脑性能比较特别差又想上网的用户。

现在已有的后台服务端框架都可以实现如 MeteorExpressSails

JSX (Javascript XML syntax transform)

JSX是使用XML语法编写Javascript的一套解析工具,X代表XML。这不是要由引擎或浏览器中实现,它的目的是通过各种预处理程序(transpilers),将这些标记转换成标准的ECMAScript。实质上也就是说JSX是一个语法糖,每一个XML标签都会被JSX转换工具转换成纯Javascript代码。如:

// Using JSX to express UI components.
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);

Hyperscript

Hyperscript是JavaScript创建超文本(HTML)的语法。客户端、服务器端都支持。
如下代码及其输入

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

舍弃和修改的部分

接下来我将用一个实例演示vue这两个版本间的一些不同的地方。主要包括:

  • events
  • .sync 依赖的弃用
  • vm.$set() 方法的弃用
  • component 组件的模板
  • 一些其他的变化

.sync 修饰符

当父子组件传参的时候我们肯能会想到把.sync修饰符用于子组件的prop上,以实现数据的双向绑定。从现在起,props 就只有单项绑定了。如果当一个组件需要修改他作用域外的数据的时候,它只能emit一个事件(vm.$emit(event,[..args])事件)让监听器回调,而不是再依赖于之前的隐士绑定了。
去掉这个修饰符的目的是为了防止子组件在父组件的范围内所引发的副作用。这样,当你阅读一个组件的代码的时候,就更加容易理解它做了什么以及是如何影响其他部分的。

vue 版本的比较

为了尽可能阐述清楚这两版本的差异,我用一个实例来解释吧。比如
这有个用户可以购买宝石的页面,其中有个Vue实例和一个购物车组件。在购物车组件里,用户选择了要购买多少个然后这个购物车组件把这个变化反映给它的父组件。

用vue 1.0.*

用vue1.0.* 的话,要实现这个功能是显而易见的。我们创建一个变量,来存储所选择的数量,我们把它传递给购物车组件,等待选择数量来更改它的值,在父组件上在写一个computed属性来计算总价格。

<!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 2.0

如果切换到vue 2.0,运行上面的代码,我们会得到以下警告。

[Vue warn]: Component template should contain exactly one root element.
为了去掉这个警告我们在 template内部加上一个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>

刷新浏览器,发现这个警告消失。
当我们改变这个变量的时候,我们得到这个警告。

[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: )

这是因为在vue2中,直接修改prop是被视作反模式的。由于在新的渲染机制中,每当父组件重新渲染时,子组件都会被覆盖,所以应该把props看做是不可变对象 [1]
不能更改 quantity prop使其和父组件同步 , 而是让应该这个组件提交个事件给父组件,可以 watch quantity 变量,如果变量发生改变就emit事件,所以这里压根不需要 prop。
由于 vm.$dispatch 和 vm.$broadcast 被弃用了,我们用一个集中式事件集线器触发事件 来实现组件间的通讯。建议阅读 升级提示
因为在Vue实例中已经实现了事件发射器接口emit,实际开发中可以用空的Vue实例作为事件发射器。
让我们看回这个例子,创建一个emitter,重构一下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的实例来监听,并更新相应的数据。

new Vue({
    el: '.container',
    data : function () {
        return {
            quantity: 0
        };
    },
    created: function () {
        // store this to use with Vue.set
        var temp = this;
        bus.$on('quantity-changed', function (quantity) {
            // vm.$set deprecated
            Vue.set(temp, 'quantity', quantity)
        })
    },
    computed:{
        gold: function(){
            return this.quantity * 100
        }
    }
})

注意

  • vm.$set() 被弃用了,所以我们用全局方法 Vm.set()。
    因为不能使用prop去同步,所以我们这样引用购物车组件:

    <cart></cart>

  • ’lazy’ 和 ’number’ 修饰符更改了,所以我们这样修改下 购物车组件里的Input

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

好了,这就是所有的了,你可以查看最终代码,然后自己把玩一番吧 JSFiddle

其他资源

下面列出的资源清单强烈建议你去看下:

本文参考


  1. 在面向对象和函数式编程,不可变对象的状态在创建后不能修改 ↩︎

posted @ 2016-09-27 14:59  Terry√  阅读(1592)  评论(0编辑  收藏  举报