Vue入门

来源:互联网 发布:孔子知天命 编辑:程序博客网 时间:2024/06/06 04:06

Vue实例

构造器

每个Vue.js应用都是通过构造函数vue创建一个Vue的根实例启动的:

var vm = new Vue({  //选项});

Vue.js没有完全遵循MVVM模式,Vue的设计无疑是收到了它的启发。因为在文档中经常会使用vm(viewModel的简称)这个变量名表示Vue实例

在实例化Vue时,需要传入一个选项对象,它可以包含数据、模板、挂载元素、方法、生命周期钩子等选项

可以扩展 vue构造器,从而用预定义选项创建可复用的组件构造器:

var myComponent = Vue.extend({  //扩展选项});//所有的MyComponent实例都将以预定义的扩展选项被创建var myComponent = new myComponent();

尽管可以命令的创建扩展实例,不过在大多数情况下将组件构造器注册为一个自定义元素,然后声明式地用在模板中

属性和方法

每个Vue实例都会代理其data对象里面所有的属性:

var data = {a : 1};var vm = new Vue({data : data});vm.a === data.a;   //truevm.a = 2;data.a //2data.a = 3;vm.a //3

只有这些被代理的属性是响应的,如果在实例创建之后添加新的属性到实例上,它不会触发视图更新

除了data属性,Vue实例暴露了一些有用的实例属性和方法,这些属性和方法都有前缀$,以便与代理的data属性区分,例如

var data = {a : 1};var vm = new Vue({  el : "#example",  data : data});vm.$data === data;   //truevm.$el === document.getElementById('example');  //true//$watch是一个实例方法vm.$watch('a', function() {  //这个回掉将在"vm.a"改变后调用});

注意:不要在实例属性或者回掉函数中(如vm.$watch(‘a’, newVal=>this.method()))使用箭头函数。因为箭头函数绑定父上下文,所以this不会像是预想的一样是Vue实例,而是this.myMethod未被定义

实例生命周期:

每个Vue实例在被创建之前都要经过一系列的初始化过程,例如需要配置数据观测、模板编译、挂载实例到DOM,然后在数据变化时更新DOM。在这个过程中,实例也会调用一些生命周期钩子,这就给我们提供了执行自定义逻辑的机会,例如,created这个钩子在实例被创建之后调用

var vm = new Vue({  data: {    a : 1  },  //this指向Vue实例  created: function() {    console.log('a is: ' + this.a);   //'a is: 1'  }});

也有其他的一些钩子,比如mountedupdatedestoryed,钩子的this指向的调用它的Vue的实例。钩子的this指向调用它的Vue的实例,Vue.js中是没有控制器的概念的,组件的自由逻辑可以分布在这些钩子中

生命周期图示

模板语法

Vue.js使用了基于HTML的模板语法,允许开发者声明式地将DOM绑定至底层Vue实例的数据,所有Vue的模板都是合法的HTML,所以能被遵循规范的浏览器和HTML解析器解析

在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。结合响应系统,在应用状态改变时,Vue能够智能的计算出重新渲染组件的最小代价并应用到DOM操作上

如果你熟悉虚拟DOM并且偏爱JavaScript的原始力量,也可以不用模板,直接写渲染函数,使用可选的JSX语法

插值

#文本

数据绑定最常见的形式就是使用“Mustache”语法(双大括号)的文本插值

<span>message: {{msg}}</span>

Mustache 标签将会被替代为对应数据上的msg的属性的值,无论何时,绑定的数据对象上msg属性发生了变化,插值处的内容都会更新

通过使用v-once指令,你也能一次性地插值,当数据改变时,插值处的内容不会更新,但请留心这会影响该结点上的所有的数据绑定

<span v-once>This will never change: {{msg}}</span>

#纯HTML

双大括号会将数据解释为纯文本,而非HTML。为了输出真正的HTML,需要使用v-html指令

<div v-html="rawHtml"></div>

被插入的内容都会被当成HTML——数据绑定会被忽略,注意,不能使用v-html来复合局部模板,因为Vue不是基于字符串的模板引擎。组件更适合担任UI重用与复合的基本单元

在站点上使用动态渲染的任意HTML可能会非常危险,因为会很容易造成XSS攻击,所以只对可信内容进行使用HMTL插值,绝不要对用户提供的内容插值

#属性

Mustache不能再HTML属性中使用,应使用v-bind指令

<div v-bind:id="dynamicId"></div>

这对布尔值的属性也有效——如果条件被求值为false的话该属性会被移除:

<button v-bind:disabled="someDynamicCondition"></button>

#使用JavaScript表达式

在之前的例子中,都只是只绑定简单的属性键值,但实际上,对于所有的数据绑定,Vue.js都提供了完全的JavaScript表达式支持

{{number + 1}}

{{ok ? ‘Yes’ : ‘No’}}

{{message.split(”).reverse().join(”)}}

这些表达式会在所属Vue实例的数据作用域下作为JavaScript解析,有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效

    <!--这是一条语句,不是表达式-->    {{var a = 1}}    <!--流控制也不会生效,但可以使用三元表达式-->    {{if(ok) {    return message    }}}

模板表达式都被放到沙盒中,只能访问全局变量的一个白名单,如Math和Data,不应该在模板表达式中试图访问用户定义的全局变量

指令

指令指的是带有v-前缀的特殊属性,指令属性的预期值是单一 JavaScript表达式(除了v-for,之后再讨论)。指令的职责就是当表达式的值改变时相应地将某些行为应用到DOM上

  <p v-if="seen">Now you see me</p>

这里,v-if指令会根据表达式seen的值的真假来移除/插入p元素

#参数

一些指令能接受一个“参数”,在指令后以冒号指明,例如v-bind指令被用来响应的更新HTML属性

<a v-bind:href="href"></a>

这里的href是参数,告知v-bind指令将该元素的href属性与表达式url的值绑定

另一个例子就是v-on指令,它用于监听DOM事件:

<a v-on:click="doSomething"></a>

在这里参数是监听的事件名

#修饰符

修饰符是以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定,例如.prevent修饰符告诉v-on指令对于触发的事件的调用event.preventDefault():

  <form v-on:submit.prevent="onSubmit"></form>

过滤器

Vue.js允许你自定义过滤器,可被用作一些常见的文本格式化,过滤器可用与两个地方:插值和v-bind表达式,过滤器应该被添加在JavaScript表达式的尾部,由“管道”符指示

{{message | capitalize}}<div v-bind:id="rawId | formatId"></div>

过滤器函数总接受表达式的值作为第一个参数

new Vue({  // ...  filters: {    capitalize: function (value) {      if (!value) return ''      value = value.toString()      return value.charAt(0).toUpperCase() + value.slice(1)    }  }})

过滤器可以关联:

{{message | filterA | filterB}}

过滤器是JavaScript函数,因此可以接受参数

{{message | filterA('arg1', 'arg2')}}

这里,字符串的arg1将传给过滤器作为第二个参数,arg2表达式的值将被求值然后传给过滤器作为第三个参数

缩写

v-前缀在模板中使用一个表示Vue特殊属性的明显标识,当使用Vue.js为现有的标记添加动态行为时,它会很有用,但对于一些经常使用的指令来说有点繁琐,同时,当搭建Vue.js管理的是所有模板的SPA时,v-前缀也变得没那么重要了。因为,Vue.js为两个最为常用的治疗另提供了特别的缩写

#v-bind的缩写

<!-- 完整语法 --><a v-bind:href="url"></a><!-- 缩写 --><a :href="url"></a>

#v-on

<!-- 完整语法 --><a v-on:click="doSomething"></a><!-- 缩写 --><a @click="doSomething"></a>

:@ 对于属性名来说都是合法字符,在所有支持 Vue.js 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记。

计算属性

模板内的表达式是非常便利的,但是它们实际上只用于简单的计算,在模板中放入太多的逻辑会让模板过重且难以维护

    <div id="example">        {{ message.split('').reverse().join('') }}    </div>

在这种情况下,模板不再简单和清晰,在意识到这是反向显示message之前,你不得不确认第二遍,当你想要在模板中多次反向显示message的时候,问题会变得更糟糕

这就是对于任何复杂逻辑,都应当使用计算属性的原因

#基础例子

    <div id="example">        <p>Original message: "{{message}}"</p>        <p>Computed reversed message: "{{reversedMessage}}"</p>    </div>    <script type="text/javascript">        new Vue({           el : "#example",            data: {                message: "Vue"            },            computed : {                reversedMessage : function() {                    return this.message.split('').reverse().join('');                }            }        });    </script>

结果:

Original message: “Vue”

Computed reversed message: “euV”

这里声明了一个计算属性reversedMessage,提供的函数将用作属性vm.reversedMessage的getter

也可以看出在vm.reversedMessage的值始终取决于vm.message的值,这种依赖是以声明的方式创建的:计算属性的getter是没有副作用的,这使得它易于测试和推理

#计算缓存 vs Methods

上面的效果也可以通过调用表达式中的method

    <p id="test">Reversed message: "{{ reversedMessage() }}"</p>    <script type="text/javascript">        new Vue({            el : "#test",            data : {                message : "an element"            },            methods: {                reversedMessage: function () {                    return this.message.split('').reverse().join('')                }            }        })    </script>

我们可以将同一个函数定义为一个method而不是一个计算属性,对于最终的结果,两种方式确实是相同的,然而,不同的计算属性是基于它们的依赖进行缓存的,计算属性只有在它的相关依赖发生改变时才会重新求值,这就意味着只要message还没有改变,多次访问reversedMessage会立即返回之前的计算结果,而不是再次执行函数,那么意味着下面的计算属性将不再跟新,因为Data.now()不是响应式依赖

computed : {  now : function() {    return Date.now();  }}

相比较的话,只要发生重新渲染,method调用总会执行该函数

缓存的作用:对于性能开销比较大的计算属性A,我们可能有其他的属性依赖于这个A属性,如果没有缓存,那么就需要多次执行这个A的getter方法,如果不希望有缓存的时候,可以用method代替

#Computed属性 vs Watched属性

Vue确实提供了一种更通用的方式来观察和响应Vue实例上的数据变动: watch属性,当你有一些数据需要随着其他数据变动而变动时,很容易滥用watch–特别是如果之前使用过AngularJS,然而,通常更好的想法是使用computed属性而不是命令式的watch回掉。

    <div id="demo">{{fullName}}</div>    <script type="text/javascript">        new Vue({            el : "#demo",            data: {                firstName : 'Foo',                lastName : 'Bar',                fullName : 'Foo Bar'            },            watch : {                firstName : function(val) {                    this.fullName = val + ' ' + this.lastName;                },                lastName : function(val) {                    this.fullName = this.firstName + ' ' + val;                }            }        });    </script>

上面的代码是命令式的和重复的,下面的是用computed属性实现的

#计算setter

计算属性默认是只有getter,不过可以在需要时也可以提供一个setter

// ...computed: {  fullName: {    // getter    get: function () {      return this.firstName + ' ' + this.lastName    },    // setter    set: function (newValue) {      var names = newValue.split(' ')      this.firstName = names[0]      this.lastName = names[names.length - 1]    }  }}// ...

当运行vm.fullName = 'John Doe'时,setter就会被调用,vm.firstName和vm.lastName就会相应的更新

观察watchers

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的watcher。通过watch选项,来响应数据的变化,当你想要数据发生响应时,执行异步操作或开销较大的操作,这个比较有用

    <div id="watch-example">        <p>Ask a yes/no question:            <input v-model="question">        </p>        <p>{{ answer }}</p>    </div>    <script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>    <script src="https://unpkg.com/lodash@4.13.1/lodash.min.js"></script>    <script>        var watchExampleVM = new Vue({            el: '#watch-example',            data: {                question: '',                answer: 'I cannot give you an answer until you ask a question!'            },            watch: {                // 如果 question 发生改变,这个函数就会运行                question: function (newQuestion) {                    this.answer = 'Waiting for you to stop typing...'                    this.getAnswer()                }            },            methods: {                // _.debounce 是一个通过 lodash 限制操作频率的函数。                // 在这个例子中,我们希望限制访问yesno.wtf/api的频率                // ajax请求直到用户输入完毕才会发出                // 学习更多关于 _.debounce function (and its cousin                // _.throttle), 参考: https://lodash.com/docs#debounce                getAnswer: _.debounce(                        function () {                            var vm = this                            if (this.question.indexOf('?') === -1) {                                vm.answer = 'Questions usually contain a question mark. ;-)'                                return                            }                            vm.answer = 'Thinking...'                            axios.get('https://yesno.wtf/api')                                    .then(function (response) {                                        vm.answer = _.capitalize(response.data.answer)                                    })                                    .catch(function (error) {                                        vm.answer = 'Error! Could not reach the API. ' + error                                    })                        },                        // 这是我们为用户停止输入等待的毫秒数                        800                )            }        })    </script>

Class与Style绑定

数据绑定一个常见需求是操作元素的class列表和它的内联样式,因为它们都是属性,我们可以用v-bind处理它们:只需要计算出表达式最终的字符串,不过,字符串拼接麻烦又出错,因此在v-bind用于classstyle时,Vue.js专门增强了它,表达式的结果类型除了字符串之外,还可以是对象或者数组

绑定HTML Class

# 对象语法

我们可以传给v-bind:class一个对象,以动态的切换class

<div v-bind:class="{ active: isActive }"></div>

上面的语法表示classactive的更新将取决于数据属性isActive是否为真。

我们也可以在对象中传入更多属性用来动态切换多个class,此外,v-bind: class指令可以与普通的class属性共存。如下模板:

    <div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }">    </div>    <script type="text/javascript">        new Vue({            el : ".static",            data: {                isActive : true,                hasError : false            }        })    </script>

得到的结果:

<div class="static active"></div>

当使用v-bind绑定的class属性会随着“isActive”和“hasError”的值的变化而改变的

上面的写法是在class里面写了一个对象,当然也可以通过返回对象的计算属性来完成

    <div class="static" v-bind:class="classObject">    </div>    <script type="text/javascript">        new Vue({            el : ".static",            data: {                isActive : true,                error : null            },            computed: {                classObject : function() {                    return {                        isActive : this.isActive && !this.error,                        'text-danger': this.error && this.error.type === 'false'                    }                }            }        })    </script>

我觉得这两种写法都有着自己的特点,具体采用哪个得根据实际情况

# 数组语法

和刚才的类似,就是把对象换成了数组的形式

    <div class="static" v-bind:class="[activeClass, errorClass]">    </div>    <script type="text/javascript">        new Vue({            el : ".static",            data: {                activeClass : 'active',                errorClass : 'text-danger'            }        })    </script>

结果如下:

<div class="static active text-danger"></div>

如果要根据情况切换列表中的class,可以用三元表达式

 <div class="static" v-bind:class="[isActive ? activeClass : '', errorClass]">

从上面的例子中也可以看出来,当有多个class需要切换时,上面的写法就有点繁琐,所以就采用在数组语法中使用对象语法:

<div v-bind:class="[{ active: isActive },  errorClass]"></div>

# 用在组件上

当定制的一个组件上用到class属性的时候,这些类就会被添加到根元素上,这个元素已经存在的类不会被覆盖

组件:

Vue.component('my-component', {  template : "<p class='foo'>Hi</p>"});

然后再使用它的时候添加一些class:

<my-component class="baz boo"></my-component>

结果:

<p class="foo bar baz boo">Hi</p>

同样的适用于绑定 HTML class :

<my-component v-bind:class="{ active: isActive }"></my-component>

isActive 为 true 的时候,HTML 将被渲染成为:

<p class="foo bar active">Hi</p>

绑定内联样式

# 对象语法

v-bind:style的对象语法十分直观,它是一个JavaScript对象,CSS属性名可以用驼峰式或短横分隔命名:

    <div id="div-style" v-bind:style="{color: activeColor, fontSize: fontSize + 'px'}"></div>    <script type="text/javascript">        new Vue({            el : "#div-style",            data : {                activeColor: "orange",                fontSize: 16            }        })    </script>

结果如下:

<div id="div-style" style="color: orange; font-size: 16px;"></div>

或者直接通过一个对象完成:

    <div id="div-style" v-bind:style="styleObject"></div>    <script type="text/javascript">        new Vue({            el : "#div-style",            data : {                styleObject: {                    color: "orange",                    fontSize: '16px'                }            }        })    </script>

# 数组语法

v-bind:style的数组语法可以将多个样式对象应用到一个元素上

<div id="div-Style-demo" v-bind:style="[baseStyles, overridingStyles]"></div>

条件渲染

v-if

在字符串模板中,Handlebars有自己的模板的写法,可以查看Handlebars,关于选择语句的模板

{{#if ok}}    <h1>Yes</h1>{{/if}}

在Vue.js里面,使用v-if指令完成

    <h1 v-if="ok">Yes</h1>    <script type="text/javascript">        new Vue({            el: "h1",            data : {                ok : true            }        });    </script>

#<template>v-if条件组

因为v-if是一个指令,需要将它添加到一个元素上,如果需要切换多个元素,可以把template元素当做包装元素,并在上面使用v-if,最终的渲染结果不会包含template元素

    <template v-if="ok">        <h1>title</h1>        <p>paragraph 1</p>        <p>paragraph 2</p>    </template>

# v-else

    <div id="div">        <div v-if="Math.random > 0.5">           " Math.random > 0.5 "        </div>        <div v-else>            " Math.random <= 0.5 "        </div>    </div>    <script type="text/javascript">        new Vue({            el : "#div"        })    </script>

这部分,在查看官方文档的时候,会出现意向不到的结果,这部分可以参考http://blog.csdn.net/Dear_Mr/article/details/71598099

关于v-else-if这里就不详细说了

<div v-if="type === 'A'">  A</div><div v-else-if="type === 'B'">  B</div><div v-else-if="type === 'C'">  C</div><div v-else>  Not A/B/C</div>

# 用key管理可复用的元素

Vue会尽可能高效的渲染元素,通常会复用已有元素而不是从头开始渲染,这么做,除了让Vue变得非常快之外,还有一些有用的好处,例如,允许用户在不同的登录方式之间切换

    <div id="login">        <template v-if="loginType === 'username'">            <label>Username</label>            <input placeholder="Enter your username">        </template>        <template v-else>            <label>Email</label>            <input placeholder="Enter your email address">        </template>    </div>    <script type="text/javascript">        new Vue({           el : "#login",            data: {                loginType : 'username'            }        });    </script>

上面的方式切换loginType将不会清除用户已经输入的内容,因为两个模板使用了相同的元素,input不会被替换掉——仅仅是替换了它的placeholder

如果要清除用户的输入内容呢?Vue提供了一种方式来声明”两个元素是独立的——不要复用它们”,只需要添加一个具有唯一值的key属性即可

    <div id="login">        <template v-if="loginType === 'username'">            <label>Username</label>            <input placeholder="Enter your username" key="username-input">        </template>        <template v-else>            <label>Email</label>            <input placeholder="Enter your email address" key="email-input">        </template>    </div>

但是上面的label元素仍然会被高效的复用,因为它们没有添加key属性

v-show

另一个根据条件展示元素的选项是v-show指令,大致用法如下:

    <h1 id="h1" v-show="ok">Hello</h1>    <script type="text/javascript">        new Vue({           el : 'h1',            data: {                ok : true            }        });    </script>

带有v-show的元素始终会被渲染并保留在DOM中,v-show是简单地切换元素的CSS属性display

v-show 不支持语法,也不支持v-else

v-if vs v-show

v-if是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁核重建

v-if也是惰性的,如果在初始渲染的时候条件为假,则什么都不做直到条件第一次变成真的时,才会开始真正的渲染条件块

相比较,v-show就简单了,不管初始条件是什么,元素 总是被渲染,并且只是简单地基于CSS进行切换。一般来说,v-if有较高的切换代价,而v-show有更高的渲染开销,因此,如果需要非常频繁的切换,则使用v-show较好,如果在运行时条件不太可能改变,则使用v-if较好

v-ifv-for一起使用

v-ifv-for一起使用时,v-forv-if有更高的优先权

列表渲染

v-for

我们使用v-for指令根据一组数组的选项列表进行渲染,v-for指令需要以item in items形式的特殊语法,iems是源数据数组并且item是数组元素迭代的别名

# 基本用法

    <ul id="example-1">        <li v-for="item in items">            {{ item.message }}        </li>    </ul>    <script type="text/javascript">        new Vue({           el : '#example-1',            data: {                items : [                    {message: 'foo'},                    {message: 'bar'}                ]            }        });    </script>

v-for块中,我们拥有对其父作用域属性的完全访问权限,v-for还支持一个可选的第二个参数作为当前项的索引

    <ul id="example-1">        <li v-for="(item, index) in items">            {{index}} - {{ item.message }}        </li>    </ul>    <script type="text/javascript">        new Vue({           el : '#example-1',            data: {                items : [                    {message: 'foo'},                    {message: 'bar'}                ]            }        });    </script>

也可以使用of替代in作为分割符,因为它是最接近JavaScript迭代器的语法

<div v-for="item of items"></div>

# Template v-for

如同v-if模板,你也可以用带有v-fortemplate标签来渲染多个元素块

    <ul id="example-2">       <template v-for="item in items">           <li>{{ item.msg }}</li>           <li class="drive"></li>       </template>    </ul>    <script type="text/javascript">        new Vue({            el : '#example-2',            data : {                items : [                    {msg : 'helen'},                    {msg : 'ceil'}                ]            }        })    </script>

# 对象迭代v-for

可以使用 v-for通过一个对象的属性来迭代

    <ul id="repeat-object">        <li v-for="value in object">            {{ value }}        </li>    </ul>    <script type="text/javascript">        new Vue({           el : '#repeat-object',            data : {                object: {                    firstName : 'John',                    lastName : 'Doe',                    age : 28                }            }        });    </script>

数组迭代中,第二个参数代表着下标,对象迭代中第二个参数代表着键名,第三个参数是索引

    <div id="tmp">        <div v-for="(value, key, index) in object">            {{ index }}. {{ key }} : {{ value }}        </div>    </div>    <script type="text/javascript">        new Vue({            el : '#tmp',            data : {                object: {                    firstName : 'John',                    lastName : 'Doe',                    age : 28                }            }        });    </script>

# 整数迭代v-for

v-for也可以取整数,在这种情况下,它将多次重复模板

    <div id="number">        <span v-for="n in 10"> {{n}} </span>    </div>    <script type="text/javascript">        new Vue({           el : '#number'        });    </script>

# 组件和v-for

在自定义组件时,可以像任何普通元素一样使用v-for

<my-component v-for="item in items"></my-component>

但是它不能自动传递数据到组件中,因为这使得组件会紧密耦合到v-for如何运行,在一些情况下,明确数据的来源可以使组件重用

关于todo list的例子:todo list

# v-for with v-if

当它们处于同一结点,v-for的优先级比v-if更高,这就意味着v-if将分别重复运行于每个v-if循环当中,当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用

    <ul id="ul-list">        <li v-for="todo in todos" v-if="!todo.isComplete">            {{ todo.msg }}        </li>    </ul>    <script type="text/javascript">        new Vue({            el : "#ul-list",            data : {                todos: [                    {isComplete: true, msg: 'valen'},                    {isComplete: false, msg: 'helen'}                ]            }        })    </script>

key

当Vue.js使用v-for正在更新渲染过的元素列表时,它默认用“就地复用”策略,如果数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已经被渲染的每个元素

这个默认的模式是有效的,但是只适合于不依赖子组件状态或者临时DOM状态(例如:表单输入值)的列表渲染输出

为了给Vue一个提示,以便它能跟踪每个结点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一key属性,理想key值是每项都有唯一id,需要使用v-bind来绑定动态值

<div v-for="item in items" :key="item.id"></div

数组更新检测

#变异方法

Vue包含一组观察数组的变异方法,所以它们也将触发视图更新,这些方法如下:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse

# 重塑数组

变异方法,顾名思义,会改变被这些方法调用的原始数组,相比之下,也有非变异方法,如filterconcat

slice。这些不会改变原始数组,但是会返回一个新的数组,当使用非变异方法时,可以用新数组替换旧数组

example1.items = example1.items.filter(function() {  return item.message.match(/Foo/);});

# 注意事项

由于JavaScript的限制,Vue不能检测以下变动的数组

  1. 当利用索引直接设置一个项时,例如 vm.items[indexOfItem] = newValue
  2. 当修改数组的长度时,例如vm.items.length = newLength

为了解决第一类问题,下面的两个例子都可以实现 vm.items[indexOfItem] = newValue相同的效果,同时也将触发状态更新

/*Vue.set*/Vue.set(example1.items, indexOfItem, newValue)

刚看到这个Set的时候,我就联想到我刚才我看了得ES6里面的Set,这个Set是一种数据结构-Set,它类似于数组,但是成员的值是唯一的,没有重复的值,然后我又去查看了Vue的源码,虽然吧,没咋看懂,但是我还是看到了当判断出example1.items是数组的时候,会通过splice进行替换操作,这部分的分析暂时留一下

example1.items.splice(indexOfItem, 1, newValue)

解决第二类问题

example1.items.splice(newLength)

显示过滤/排序结果

有时,我们需要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据,在这种情况下,可以创建返回过滤或排序数组的计算属性

    <ul id="ul-sort">        <li v-for="item in sortItems"> {{ item }}</li>    </ul>    <script type="text/javascript">        new Vue({            el : "#ul-sort",            data : {                items : [6, 3, 7, 2, 1, 8]            },            computed : {                sortItems : function() {                    return this.items.filter(function(number) {                        return number % 2 === 0;                    });                }            }        })    </script>

或者,也可以使用method方法

    <ul id="ul-sort">        <li v-for="item in sortItems(items)"> {{ item }}</li>    </ul>    <script type="text/javascript">        new Vue({            el : "#ul-sort",            data : {                items : [6, 3, 7, 2, 1, 8]            },            methods : {                sortItems : function() {                    return this.items.filter(function(number) {                        return number % 2 !== 0;                    });                }            }        })    </script>

事件处理器

监听事件

可以用v-on指令监听DOM事件来触发一些JavaScript代码

之前写的todo list已经就是应用过v-on指令了

<div id="test-on">    <button v-on:click="count += 1">增加1</button>    <p>这个按钮被点击了{{ count }}次</p></div><script type="text/javascript">    new Vue({       el : "#test-on",        data: {            count : 0        }    });</script>

方法事件处理器

v-on可以接收一个定义的方法来调用

    <div id="div-on">        <button v-on:click="greet">Greet</button>    </div>    <script type="text/javascript">        new Vue({           el : "#div-on",            data: {                name: 'Vue'            },            methods : {                greet : function(event) {                    alert("Hello " + this.name);                    alert(event.target.tagName);                }            }        });    </script>

内联处理器方法

除了直接绑定到一个方法,也可以用内联JavaScript语句

    <div id="div-on">        <button v-on:click="say('hi')">say hi</button>        <button v-on:click="say('hello')">say hello</button>    </div>    <script type="text/javascript">        new Vue({           el : "#div-on",            data: {                name: 'Vue'            },            methods : {                say : function(msg) {                    alert(msg);                }            }        });    </script>

有时也需要在内联语句处理器中访问原生DOM事件。可以用特殊变量$event把它传入方法:

    <div id="div-on">        <button v-on:click="warn('Form cannot be submitted yet.', $event)">submit</button>    </div>    <script type="text/javascript">        new Vue({           el : "#div-on",            data: {                name: 'Vue'            },            methods : {                warn : function(msg, event) {                    /*event 原生事件对象*/                    if(event) {                        event.preventDefault();                    }                    alert(event);  /*[object MouseEvent]*/                    alert(msg);                }            }        });    </script>

事件修饰符

在事件处理中调用event.preventDefault()event.stopPropagation()是非常见的需求,尽管我们可以在methods中轻松实现这点,但更好的是:methods只有纯粹的数据逻辑,而不是去处理DOM事件

为了解决这个问题,Vue.js为v-on提供了事件修饰符,通过由点(.)表示的指令后缀来调用修饰符

  • .stop()
  • .prevent()
  • .capture()
  • .self()
  • .once()
<!-- 阻止单击事件冒泡 --><a v-on:click.stop="doThis"></a><!-- 提交事件不再重载页面 --><form v-on:submit.prevent="onSubmit"></form><!-- 修饰符可以串联  --><a v-on:click.stop.prevent="doThat"></a><!-- 只有修饰符 --><form v-on:submit.prevent></form><!-- 添加事件侦听器时使用事件捕获模式 --><div v-on:click.capture="doThis">...</div><!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 --><div v-on:click.self="doThat">...</div><!-- 2.1.4新增 点击事件将只会触发一次 --><a v-on:click.once="doThis"></a>

按键修饰符

在监听键盘事件时,我们经常需要监听常见的键值,Vue允许为v-on在监听键盘事件时添加按键修饰符

<!-- 只有在 keyCode 是 13 时调用 vm.submit() --><input v-on:keyup.13="submit">

Vue为常见的按键提供了别名

<input v-on:keyup.enter="doSomething"><!--缩写--><input @keyup.enter="submit">

全部的按键别名

  • .enter
  • .tab
  • .delete(捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

2.1.0新增的:

  • .ctrl
  • .alt
  • .shift
  • .meta

可以通过全局config.keyCodes对象自定义按键修饰符别名

// 可以使用 v-on:keyup.f1Vue.config.keyCodes.f1 = 112

注意:在Mac系统键盘上,meta对应命令键 (⌘)。在Windows系统键盘meta对应windows徽标键(⊞)。在Sun操作系统键盘上,meta对应实心宝石键 (◆)。在其他特定键盘上,尤其在MIT和Lisp键盘及其后续,比如Knight键盘,space-cadet键盘,meta被标记为“META”。在Symbolics键盘上,meta被标记为“META”或者 “Meta”。例如:

<!-- Alt + C --><input @keyup.alt.67="clear"><!-- Ctrl + Click --><div @click.ctrl="doSomething">Do something</div>

为什么在HTML中监听事件

这种事件监听的方式虽然违背了关注点分离的传统理念,但是所有的Vue.js事件处理方法和表达式都严格绑定在当前视图的ViewModel上,它不会导致任何维护上的困难,实际上,使用v-on有以下好处:

  1. 可以很轻易的在HTML模板中在JavaScript所对应的方法
  2. 因为没有必要在JavaScript里手动绑定事件,所以viewModel代码可以是非常纯粹的逻辑,和DOM完全解耦,更易于测试
  3. 当一个ViewModel被销毁时,所有的事件处理器都会自动被删除,无需担心如何自己清理它们

表单控件绑定

基础用法:使用v-model指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方式来更新元素,但v-model本质上是语法糖,它只是负责监听用户的输入事件以更新数据,特别是处理一些极端的例子

它并不关心表单控件初始化所生成的值,因为它会选择Vue实例数据作为具体的值,对于要求 IME (如中文、 日语、 韩语等) 的语言,你会发现那v-model不会在 ime 构成中得到更新。如果你也想实现更新,请使用 input事件。

基础用法

# 文本

    <div id="div-model">        <input v-model="message">        <p>message: {{ message }}</p>    </div>    <script type="text/javascript">        new Vue({            el : "#div-model",            data: {                message : ''            }        })    </script>

# 多行文字

<span>Multiline message is:</span><p style="white-space: pre">{{ message }}</p><br><textarea v-model="message" placeholder="add multiple lines"></textarea>

在文本区域插值并不会生效,应用v-model来代替

# 复选框

单个勾选框,逻辑值:

    <div id="div-check">        <input type="checkbox" id="checkbox" v-model="checked">        <label for="checkbox">{{ checked }}</label>    </div>    <script type="text/javascript">        new Vue({           el : "#div-check",            data : {                checked : false            }        });    </script>

多个勾选框,绑定同一个数组:

    <div id="div-check">        <input type="checkbox" value="Node" id="checkbox1" v-model="checkNames">        <label for="checkbox1">Node</label>        <input type="checkbox" value="Vue" id="checkbox2" v-model="checkNames">        <label for="checkbox2">Vue</label>        <input type="checkbox" value="Bootstrap" id="checkbox3" v-model="checkNames">        <label for="checkbox3">Bootstrap</label>        <span>{{ checkNames }}</span>    </div>    <script type="text/javascript">        new Vue({           el : "#div-check",            data : {                checkNames : []            }        });    </script>

# 单选按钮

    <div id="div-radio">        <input type="radio" id="radio1" value="One" v-model="picked">        <label for="radio1">One</label>        <input type="radio" id="radio2" value="Two" v-model="picked">        <label for="radio2">Two</label>        <span>Picked: {{ picked }}</span>    </div>    <script type="text/javascript">        new Vue({           el : "#div-radio",            data: {                picked: []            }        });    </script>

# 选择列表

单选列表:

    <div id="div-select">        <select v-model="selected">            <option>A</option>            <option>B</option>            <option>C</option>            <option>D</option>        </select>        <span>selected : {{ selected }}</span>    </div>    <script type="text/javascript">        new Vue({            el : "#div-select",            data: {                selected: null            }        });    </script>

多选列表:

    <div id="div-select">        <select v-model="selected" multiple>            <option>A</option>            <option>B</option>            <option>C</option>            <option>D</option>        </select>        <span>selected : {{ selected }}</span>    </div>    <script type="text/javascript">        new Vue({            el : "#div-select",            data: {                selected: []            }        });    </script>

只是给select标签添加了multiple属性,把实例的selected的初始值定位一个数组

动态绑定,用v-for渲染

    <div id="div-select">        <select v-model="selected" multiple>            <option v-for="item in options" v-bind:value="item.value"> {{ item.text }} </option>        </select>        <span>selected : {{ selected }}</span>    </div>    <script type="text/javascript">        new Vue({            el : "#div-select",            data: {                options: [                    {text: 'one', value: 'A'},                    {text: 'two', value: 'B'},                    {text: 'three', value: 'C'}                ],                selected : []            }        });    </script>

绑定value

对于单选按钮,勾选框及选择列表选项,v-model绑定的value通常是静态字符串(对于勾选框来说是逻辑值)

<!-- 当选中时,`picked` 为字符串 "a" --><input type="radio" v-model="picked" value="a"><!-- `toggle` 为 true 或 false --><input type="checkbox" v-model="toggle"><!-- 当选中时,`selected` 为字符串 "abc" --><select v-model="selected">  <option value="abc">ABC</option></select>

但是我们想绑定value到Vue的实例的一个动态属性上,这时可以用v-bind实现,并且这个属性的值可以不是字符串

# 复选框

    <input id="input-check" type="checkbox" v-model="toggle" v-bind:true-value="a" v-bind:false-value="b">    <label for="input-check">check</label>    <script type="text/javascript">        var v = new Vue({            el: "#input-check",            data: {                toggle: "",                a : "a",                b : "b"            }        });    </script>

结果是:前面是选中的时候的输出,后者是没有选中时的状态

这里写图片描述

# 单选按钮

    <input id="input-radioo" type="radio" v-model="pick" v-bind:value="a">    <label for="input-radioo">pick</label>    <script type="text/javascript">        var p = new Vue({            el : "#input-radioo",            data: {                pick : "",                a : 'a'            }        })    </script>

# 选择列表设置

<select v-model="selected" id="selected">    <option v-bind:value="{ number:123 }">123</option></select><script type="text/javascript">    new Vue({        el : "#selected",        data : {            selected: "",        }    })</script>

修饰符

# .lazy

默认情况下,v-model在input事件上同步输入框中的值与数据,当添加了修饰符lazy之后,从而转变在change事件上同步

    <div id="div-lazy">        <input  v-model.lazy="msg">        <span>{{ msg }}</span>    </div>    <script type="text/javascript">        new Vue({            el : "#div-lazy",            data : {                 msg: ""            }        });    </script>

# .number

    <div id="div-number">        <input  v-model.lazy.number="age">        <span>{{ age }}</span>    </div>    <script type="text/javascript">        new Vue({            el : "#div-number",            data : {                age: ""            }        });    </script>

举个例子,当我在输入框中输入”18岁”的时候,由于我上面加上了lazy的修饰符,所以当触发了“change”事件之后,会将输入的内容转换为Number类型,也就是18

所以,该修饰符会自动的将用户的输入值转为Number类型(如果原值的转换结构为NaN则返回原值),在type=number时,HTML中输入的值也总是会返回字符串类型

# .trim

可以自动过滤用户输入的首尾空格

<input v-model.trim="msg">

组件

组件的定义:组件也是Vue.js最强大的功能之一,组件可以扩展HTML元素,封装可用的代码,在较高层次上,组件是自定义元素,Vue.js的编辑器为它添加了特殊功能,在有些情况下,组件也可以是原生HTML元素的 形式,以is的特性扩展,查看例子

使用组件

# 注册

之前的学习中,我们总是在创建Vue实例,比如下面这个样子

new Vue({  el : "#some-element",  //选项})

注册一个全局组件,可以使用Vue.component(tagName, options)

new Component("my-component", {  //选项});

组件在注册之后,就可以在父实例的模板中以自定义元素<my-component></my-component的形式使用,要确保在初始化根实例之前注册了组件

    <div id="div-exp1">        <my-component></my-component>    </div>    <script type="text/javascript">        Vue.component("my-component", {            template: "<div>A custom component!</div>"        });        new Vue({            el : "#div-exp1"        })    </script>

# 局部注册

不必在全局注册每个组件,通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用

    <div id="div-exp2">        <com></com>    </div>    <script type="text/javascript">        var child = {            template: "<div>定义的Component</div>"        };        new Vue({            el : "#div-exp2",            components: {                'com': child            }        })    </script>

这种封装方式也适用于其他可注册的Vue功能,如指令

# DOM模板解析说明

当使用DOM作为模板时(例如,将el元素挂载在一个已存在元素上),会受到HTML的一些限制,因为只有Vue只有在浏览器解析和标准化HTML后才能获得模板内容,尤其像这些元素<ul><ol><table>select限制了能被它包裹元素,‘option’只能出现在某些其他元素的内部

在自定义组件中使用这些受限制的元素会导致问题

<table>  <my-row>...</my-row></table>

自定义组件my-row会认为是无效的内容,因此在渲染的时候会导致错误,变通的办法是使用特殊的is属性

<table>  <tr is="my-row"></tr></table>

如果使用的是下面来源之一的字符串模板,这些限制将不适用

  • <script type="text/x-template">
  • JavaScript内联模板字符串
  • .vue组件

因此,有必要的话请使用字符串模板

# data必须是函数

通过Vue构造器传入的各种选项大多数都可以用在组件里,data是个例外,它必须是函数,实际上,如果你这么 做:

Vue.component('my-component', {  template: '<span>{{ message }}</span>',  data: {    message: 'hello'  }})

那么Vue会停止,并在控制台发出警告,告诉你组件必须是一个函数,假设用如下方式会绕开Vue的警告:

<div id="example-2">  <simple-counter></simple-counter>  <simple-counter></simple-counter>  <simple-counter></simple-counter></div>
var data = { counter: 0 }Vue.component('simple-counter', {  template: '<button v-on:click="counter += 1">{{ counter }}</button>',  // 技术上 data 的确是一个函数了,因此 Vue 不会警告,  // 但是我们返回给每个组件的实例的却引用了同一个data对象  data: function () {    return data  }})new Vue({  el: '#example-2'})

出现的结果就是三个标有0的按钮,当点击了其中一个按钮,三个按钮都会增加1,这是由于这三个组件共享了一个data,因此增加一个counter会影响所有组件,所以需要为每一个组件返回一个全新的data对象来解决这个问题

data: function () {  return {    counter: 0  }}

这样每个组件里面的counter都是不同的,当点击一个按钮的时候,该按钮就会增加1

# 构成组件

组件意味着协同工作,通常父子组件会是这样的关系,组件A在它的模板中使用了模板B,它们之间必然需要相互通信,父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。这样大的耦合度有点高,所以就需要解耦,这可以保证每个组件在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性

在Vue中父子组件的关系可以总结为props down,events up,父组件通过props向下传递数据给子组件,子组件通过events给父组件发送消息

Prop

#使用Prop传递数据

组件实例的作用域是孤立的,这意味着不能(也不应该)在子组件的模板内直接引用父组件的数据,这种数据流通的关系,我们需要用子组件的props选项

子组件要显式地使用props选项声明它期待获得的数据

    <div id="div-child">        <child message="hello"></child>    </div>    <script type="text/javascript">        Vue.component('child', {            /*就像data一样,prop可以用在模板内*/            props : ['message'],            template: '<span>{{ message }}</span>'        });        new Vue({            el : '#div-child'        });    </script>

# CamelCase vs kebab-case

HTML特性不区分大小写,当时用非字符串模板时,prop的名字形式会从camelCase转为kebab-case(短横线隔开),下面就是将上面例子的改写,prop的名字就变成了my-message

    <div id="div-child">        <child my-message="hello"></child>    </div>    <script type="text/javascript">        Vue.component('child', {            /*就像data一样,prop可以用在模板内*/            props : ['myMessage'],            template: '<span>{{ myMessage }}</span>'        });        new Vue({            el : '#div-child'        });    </script>

但是,当你使用的是模板字符串,就不用在意这些限制

# 动态Prop

类似于用v-bind绑定HTML特性到一个表达式,也可以用v-bind动态绑定props的值到父组件的数据中,每当父组件的数据变化时,该变化也会传导给子组件

    <div id="div-data">        <input v-model="parentmsg">        <br>        <child v-bind:my-message="parentmsg"></child>    </div>    <script type="text/javascript">        new Vue({           el: "#div-data",            data : {                parentmsg : ""            }        });    </script>

#字面量语法 vs 动态语法

<!-- 传递了一个字符串 "1" --><comp some-prop="1"></comp>

上面的例子中,some-prop是一个字面量,它大的值是字符串“1”而不是number,如果想要传递一个实际的Number,许哟啊使用v-bind,从而让它的值被当作JavaScript表达式计算:

<!-- 传递实际的 number --><comp v-bind:some-prop="1"></comp>

#单向数据流

prop是单向数据流:当父组件的属性变化时,将传导给子组件,但是不会反回来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解

另外,当父组件更新时,子组件的所有prop都会更新为最新值。这意味着不应该在子组件内改变prop。如果这么做了,Vue会在控制台给出警告。

为什么我们会有修改prop中数据的冲动呢?通常是这两种原因:

  1. prop作为初始值传入后,子组件会想把它当作局部数据来用
  2. prop作为初始值传入,由子组件处理成其他数据输出

对于这两个原因,正确的应对方式是:

  1. 定义一个局部变量,并用prop的值初始化它

    props: ['initialCounter'],data: function () { return { counter: this.initialCounter }}
  2. 定义一个计算属性,处理prop的值并返回

    props: ['size'],computed: { normalizedSize: function () {   return this.size.trim().toLowerCase() }}

在JavaScript中数组和对象是引用类型的,指向同一个内存空间,如果prop是一个对象或者数组,在子组件内部改变它会影响父组件的状态

# Prop验证

我们可以为组件的props指定验证规格。如果传入的数据不符合规格,Vue会发出警告,当组件给其他人使用时,这很有用

要指定验证规格,需要对象的形式,而不能用字符串数组

Vue.component('example', {    props : {        //基础类型检测('null'的意思是任何类型都可以)        propA: Number,        //多种类型        propB: [String, Number],        //必传且必须是字符串        propC : {            type : String,            required: true        },        propD: {            type: Number,            default: 100        },        propE: {            type: Object,            default: function() {                return {message: 'hello'}            }        },        propF : {            validator: function(value) {                return value > 10;            }        }    }});

type可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array

type也可以是一个自定义构造函数,使用instanceof检测

当prop验证失败,Vue会发出警告(开发版本)

自定义事件

我们知道,父组件是使用props传递数据给子组件,但如果子组件要把数据传递回去,应该怎么做,那就是自定义事件

# 使用v-on绑定自定义事件

每个Vue实例都实现了事件接口,即

  • 使用$on监听事件
  • 使用$emit触发事件

Vue的事件系统分离自浏览器的EventTarget API。尽管它们的运行类似,但是$on$emit不是addEventListener和dispatchEvent的别名

另外,父组件可以在使用子组件的地方直接用v-on来监听子组件触发的事件

不能用$on监听子组件抛出的事件,而必须在模板里直接用v-on绑定,就像下面的例子:

例如:

<div id="counterEventExp">    <p>{{ total }}</p>    <button-counter v-on:increment="incrementTotal"></button-counter>    <button-counter v-on:increment="incrementTotal"></button-counter></div><script type="text/javascript">    Vue.component('button-counter', {        template: '<button v-on:click="increment">{{ counter }}</button>',        data : function() {            return {                counter: 0            }        },        methods: {            increment: function() {                this.counter += 1;                this.$emit('increment');            }        }    });    new Vue({        el : "#counterEventExp",        data : {            total : 0        },        methods: {            incrementTotal: function() {                this.total += 1;            }        }    });</script>

在上面的例子中,子组件已经和它的外部完全解耦了,它所做的只是报告自己的内部事件,至于父组件是否关心与它无关,在data中利用return使得每个组件可以有自己的计数

给组件绑定原生事件

有时候,可能想在某个组件的根元素上监听一个原生事件,可以使用.native,例如

<my-component v-on:click.native="doTheThing"></my-component>

# .sync修饰符

咋某些情况下,我们可能需要对一个prop进行”双向绑定“,事实上,这正是.sync修饰符所提供的功能,当一个组件改变了一个prop的值时,这个变化也会同步到父组件所绑定的值。这很方便,但是会导致问题,因为它破环了”单向数据流”的假设,由于子组件改变prop的代码和普通的状态改动代码毫无差别,当光看子组件的代码时,你完全不知道它何时修改了父组件的状态,这在debug复杂结构的应用时会带来很高的维护成本

.sync有其适用之处的,比如在开发可复用的组件库时,我们需要做的只是让子组件改变父组件状态的代码更容易被区分

在2.3引入.sync修饰符,但是这次它只是作为一个编译时的语法糖存在,它会被扩展为一个自动更新父组件属性的v-on侦听器

如下代码:

<comp :foo.sync="bar"></comp>

会被扩展为:

<comp :foo="bar" @update:foo="val => bar = val"></comp>

当子组件需要更新foo的值时,它就需要显式地触发一个更新事件:

this.$emit('update:foo', newValue);

# 使用自定义事件的表单输入组件

自定义事件可以用来创建自定义的表单输入组件,使用v-model来进行数据双向绑定

<input v-model="something">

这个也是一个语法糖

<input v-bind:value="something" v-on:input="something = $event.target.value">

所以在组件的使用中,它相当于下面的简写:

<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>

所以要让组件的v-model生效,它必须

  • 接受一个value属性
  • 在有新的Value的时候触发input事件
<div id="current">    <currency-input v-model="price"></currency-input></div><script type="text/javascript">    Vue.component('currency-input', {        template : '<span>$ <input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)"></span>',        props: ['value'],        methods: {            updateValue: function(value) {                var formattedValue = value.trim().slice(0, value.slice(0, value.indexOf('.')+3));                if(formattedValue !== value) {                    this.$refs.input.value = formattedValue;                }                this.$emit('input', Number(formattedValue));            }        }    });    new Vue({        el : "#current",        data: {            price : 0        }    })</script>

事件接口不仅仅可以用来连接组件内部的表单输入,也很容易集成你自己创造的输入类型

<voice-recognizer v-model="question"></voice-recognizer><webcam-gesture-reader v-model="gesture"></webcam-gesture-reader><webcam-retinal-scanner v-model="retinalImage"></webcam-retinal-scanner>

# 非父子组件通信

有时候两个组件也需要通信(非父子关系),在简单的场景下,可以使用一个空的Vue实例作为中央事件总线:

var bus = new Vue()// 触发组件 A 中的事件bus.$emit('id-selected', 1)// 在组件 B 创建的钩子中监听事件bus.$on('id-selected', function (id) {  // ...})

在复杂的情况下,我们应该考虑使用专门的状态管理模式

使用Solt分发内容

在使用组件时,我们常常组合它们使用

<app>  <app-header></app-header>  <app-footer></app-footer></app>

需要注意的问题:

  1. <app>组件不知道它的挂载点会有什么内容,挂载点的内容是由<app>的父组件决定的
  2. <app>组件很可能有它自己的模板

为了让模板可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为*内容分发,Vue实现了一个内容分发API,使用特殊的<slot>元素作为原始内容的插槽

# 编译作用域

在深入内容分发API之前,必须得先明确内容在哪个作用域里编译,假定模板为:

<child-component>{{ message }}</child-component>

message应该是绑定到父组件的数据,组件作用域简单的说就是:父组件模板的内容在父组件作用域内编译,子组件模板的内容在子组件作用域内编译。

一个常见的错误是试图在父组件模板内将一个指令绑定在子组件的属性/方法:

<!-- 无效 --><child-component v-show="someChildProperty"></child-component>

假定someChildProperty是子组件的属性,上例不会如预期那样工作。父组件模板不应该知道子组件的状态

如果要绑定作用域中的指令到一个组件的根节点,应当选择在组件自己的模板上做:

Vue.component('child-component', {  // 有效,因为是在正确的作用域内  template: '<div v-show="someChildProperty">Child</div>',  data: function () {    return {      someChildProperty: true    }  }})

类似的,分发内容是在父作用域内编译

# 单个Slot

除非子组件模板内包含至少一个<slot>插口,否则父组件的内容就会被丢弃。当子组件模板只有一个没有属性的slot时,父组件整个内容片段将插入到slot所在的DOM位置,并替换掉slot标签本身。

最初在<slot>标签中的任何内容都被视为备用内容,备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有插入的内容才显示备用内容

假定my-component组件有下面模板

<div>  <h2>我是子组件的标题</h2>  <slot>    只有在没有要分发的内容时才会显示。  </slot></div>

父组件模板:

<div>  <h1>我是父组件的标题</h1>  <my-component>    <p>这是一些初始内容</p>    <p>这是更多的初始内容</p>  </my-component></div>

渲染结果:

<div>  <h1>我是父组件的标题</h1>  <div>    <h2>我是子组件的标题</h2>    <p>这是一些初始内容</p>    <p>这是更多的初始内容</p>  </div></div>

# 具名Slot

slot元素可以用一个特殊的属性name来配置如何分发内容,多个Slot可以有不同的名字,具有slot将配置内容片段中有对应slot特殊的元素

仍然可以有一个匿名slot,它是默认slot,作为找不到匹配的内容片段的备用卡槽。如果没有默认的slot,这些找不到匹配的内容片段将被抛弃

现在,假定我们有一个app-layout组件,它的模板为:

<div class="container">  <header>    <slot name="header"></slot>  </header>  <main>    <slot></slot>  </main>  <footer>    <slot name="footer"></slot>  </footer></div>

父组件模板:

<app-layout>  <h1 slot="header">这里可能是一个页面标题</h1>  <p>主要内容的一个段落。</p>  <p>另一个主要段落。</p>  <p slot="footer">这里有一些联系信息</p></app-layout>

渲染结果为:

<div class="container">  <header>    <h1>这里可能是一个页面标题</h1>  </header>  <main>    <p>主要内容的一个段落。</p>    <p>另一个主要段落。</p>  </main>  <footer>    <p>这里有一些联系信息</p>  </footer></div>

在组合组件时,内容分发API是非常有用的机制

# 作用域插槽

作用域插槽是一种特殊类型的插槽,用作使用一个(能够传递数据到)可重用模板替换已渲染元素。

在子组件中,只需将数据传递到插槽,就像你将prop传递给组件一样:

<div class="child">  <slot text="hello from child"></slot></div>

在父级中,具有特殊属性scope<template>元素,表示它是作用域插槽的模板。scope的值对应一个临时变量名,此变量接收从子组件传递的prop对象

<div class="parent">  <child>    <template scope="props">      <span>hello from parent</span>      <span>{{ props.text }}</span>    </template>  </child></div>

经过渲染以上结果,得到的输出会是:

<div class="parent">  <div class="child">    <span>hello from parent</span>    <span>hello from child</span>  </div></div>

作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项:

<my-awesome-list :items="items">  <!-- 作用域插槽也可以是具名的 -->  <template slot="item" scope="props">    <li class="my-fancy-item">{{ props.text }}</li>  </template></my-awesome-list>

列表组件的模板:

<ul>  <slot name="item"    v-for="item in items"    :text="item.text">    <!-- 这里写入备用内容 -->  </slot></ul>

动态组件

通过使用保留的<component>元素,动态地绑定到它的is特性,我们让多个组件可以使用同一个挂载点,并动态切换:

var vm = new Vue({  el: '#example',  data: {    currentView: 'home'  },  components: {    home: { /* ... */ },    posts: { /* ... */ },    archive: { /* ... */ }  }})
<component v-bind:is="currentView">  <!-- 组件在 vm.currentview 变化时改变! --></component>

也可以直接绑定到组件对象上:

var Home = {  template: '<p>Welcome home!</p>'}var vm = new Vue({  el: '#example',  data: {    currentView: Home  }})

# keep-alive

如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染,为此可以添加一个keep-alive指令参数:

<keep-alive>  <component :is="currentView">    <!-- 非活动组件将被缓存! -->  </component></keep-alive>

参考:https://cn.vuejs.org/v2/guide/

对于组件那部分的理解的还不是很全面,在后期会单独写有关
组件和其他部分的博客,欢迎继续关注 ^_^

0 0
原创粉丝点击