Vue组件,动画,路由 ----Q17

三、组件

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML
元素,封装可重用的代码。

定义组件

Vue自定义组件分为两种:全局注册和局部注册,全局组件可以在任何地方引用,局部组件只能在当前Vue实例使用。

全局注册

使用Vue.component(tagName, options)来定义:

/*定义全局组件*/

Vue.component('my-component',{

template:'<h4>我是自定义组件</h4>'

});

注意,HTML
特性是不区分大小写的,所有在定义组件时尽量使用中划线"-"来指定组件名。即使,使用了驼峰标示命名如:myComponent,在页面引用时仍然要使用<my-component>进行引用。

2)局部注册

在Vue实例中使用components属性来定义:

var app = new Vue({
    el:'#app',
    //使用components关键字
    components:{
        'inner-component':{
            template:'<h4>我是局部注册组件</h4>'
        }
    }
});

2、使用组件

<div id="app">
    <!-- 使用组件 -->
    <my-component></my-component>
    <inner-component></inner-component>
</div>


image.png

完整代码:

<div id="app">
    <!-- 使用组件 -->
    <my-component></my-component>
    <inner-component></inner-component>
</div>
<script>
    /*定义全局组件*/
    Vue.component('my-component',{
        template:'<h4>我是自定义组件</h4>'
    });
    /***
     * 此种定义方式全局注册,在任何地方都可以通过自定义标签名进行引用
     * */
    var app = new Vue({
        el:'#app',
        //使用components关键字
        components:{
            'inner-component':{
                template:'<h4>我是局部注册组件</h4>'
            }
        }
    });
</script>

3、is属性

在table标签中直接使用自定义组件,无法正常显示。DOM解析时会解析到<table>标签的外部:

<table id="app">

<my-component></my-component>

</table>

DOM解析:


image.png

原因是:table/ol/ul/select
这种html标签有特殊的结构要求,不能直接使用自定义标签。他们有自己的默认嵌套规则,比如:

  • table> tr> [th, td];

  • ol/ul > li;

  • select > option

  • 解决上述问题,可以使用is进行标签转换,形式:

< is="my-component">

<table id="app">

<tr is="my-component"></tr>

</table>

image.png

完整代码:

<table id="app">
    <!-- 使用组件 -->
 <!--   <my-component></my-component>-->
    <!-- 使用 is -->
    <tr is="my-component"></tr>
</table>
<script>
    /***
     * 原因是: table/ol/ul/select 这种特殊结构要求,不能使用自定义标签:
     * 比如: table> tr> [th, td]; ol/ul > li; select > option
     * 可以使用is进行标签转换
     * */
    /*定义组件1*/
    Vue.component('my-component',{
        template:'<h4>我是自定义组件</h4>'
    });
    var app = new Vue({
        el:'#app'
    });
</script>

4.模板

当模板的html结构比较复杂时,直接在template属性中定义就不现实了,效率也会很低,此时我们可以使用模板,定义模板的四种形式:

  1. 直接使用字符串定义

  2. 使用<script type="text/x-template">

  3. 使用<template>标签

  4. 使用.vue组件,需要使用模块加载机制

在使用直接字符串模板时、x-template和.vue组件时,不需要is进行转义。

直接字符串

var temp = '<h4>直接字符串</h4>';
Vue.component('my-component1',{
    template:temp
});

2)x-template模板

<!-- 使用x-template -->
<script type="text/x-template" id="template2">
    <ul>
        <li>01</li>
        <li>02</li>
    </ul>
</script>
Vue.component('my-component2',{
    template:'#template2'
});

3)template标签

<!-- 使用template标签 -->
<template id="template3">
    <ol>
        <li>a</li>
        <li>b</li>
    </ol>
</template>
Vue.component('my-component3',{
    template:'#template3'
});

4)单标签引用

<!-- 单标签使用 -->
<template id="template4">
    <my-component1></my-component1>
    <!-- 等效 -->
    <my-component1/>
</template>

5)省略is

<!-- 使用x-template -->
<script type="text/x-template" id="template5">
    <table>
        <my-component1></my-component1>
    </table>
</script>
Vue.component('my-component6',{
    template:'#template5'
});

除了<template>模板,其他的都可以直接嵌套,不用is指定。

5、data属性

通过data属性指定自定义组件的初始数据,要求data必须是一个函数,如果不是函数就会报错。

Vue.component('my-component',{
    template:'<button @click="count += 1">计数{{count}}</button>',
    data: function () {
        return{count:0}
    }
});

综合例子:

<div id="app">
    <my-component></my-component>
    <my-component></my-component>
    <my-component></my-component>
</div>

<script>
    /***
     * 定义组件内部data: 必须通过函数定义
     * */
    Vue.component('my-component',{
        template:'<button @click="count += 1">计数{{count}}</button>',
        data: function () {
            return{count:0}
        }
    });
    var app = new Vue({
        el:'#app'
    });
</script>

image.png

6.prop属性

组件可以嵌套使用,叫做父子组件。那么父组件经常要给子组件传递数据这叫做父子组件通信。父子组件的关系可以总结为 prop向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的:

image.png

1)声明prop

Vue.component('child', {
    //声明props
    props:['a','b'],
    //使用父组件传递的数据
    template:'<span>{{a}} == {{b}}</span>'
});

2)父组件

var app = new Vue({
    el:'#app',
    data:{
        msg:'来自父组件的消息',
        greetText:'你好Child'
    }
});

3)用指定prop传递数据

<div id="app">
    <!-- v-bind:a 简写成 :a -->
    <child :a="msg" :b="greetText"></child>
</div>

4)完整代码

<div id="app">
    <!-- v-bind:a 简写成 :a -->
    <child :a="msg" :b="greetText"></child>
</div>
<script>
    /***
     * 父子组件通信: 父组件通过prop属性向子组件进行数据传递
     * 使用方式: 子组件定义时用props指定接收参数名
     * */
    Vue.component('child', {
        //声明props
        props:['a','b'],
        //使用父组件传递的数据
        template:'<span>{{a}} == {{b}}</span>'
    });
    var app = new Vue({
        el:'#app',
        data:{
            msg:'来自父组件的消息',
            greetText:'你好Child'
        }
    });
</script>

image.png

7.prop校验

子组件在接收父组件传入数据时,
可以进行prop校验,来确保数据的格式和是否必传。可以指定一下属性:

1) type: 指定数据类型 String Number Object
...注意不能使用字符串数组,只能是对象大写形式

2) required: 指定是否必输

3) default: 给默认值或者自定义函数返回默认值

4) validator: 自定义函数校验

形式如下:

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
      }
    }
  }

})

完整代码:

<div id="app">
    <child :a="msg" :c="greetText" :f="hello"></child>
</div>
<script>
    /***
     * 子组件在接收父组件传入数据时, 可以进行prop校验
     * 1) type: 指定数据类型 String Number Object ...注意不能使用字符串数组,只能是对象大写形式
     * 2) required: 指定是否必输
     * 3) default: 给默认值或者自定义函数返回默认值
     * 4) validator: 自定义函数校验
     * */
    Vue.component('child', {
        //声明props 检验
        props:{
            'a': String,
            'b': [Number,String],
            'c': {
                required:true
            },
            'd':{
                default:100
            },
            e:{
                type: Number,
                default: function () {
                    return 1;
                }
            },
            f:{
                type:Number,
                validator: function (value) {
                    return value < 100;
                }
            }
        },
        template:'<span>{{a}} == {{d}} == {{e}} == {{f}}</span>'
    });
    var app = new Vue({
        el:'#app',
        data:{
            msg:'来自父组件的消息',
            greetText:'你好Child',
            hello:12
        }
    });
</script>

image.png

8、非prop属性

引用子组件时,非定义的prop属性,自动合并到子组件上,class和style也会自动合并。

<div id="app">
    <child data-index="0" class="cont" style="font-size: 20px;"></child>
</div>
<script>
    /***
     * 引用子组件: 非定义的prop属性,自动合并到子组件上,class和style也会自动合并
     * */
    Vue.component('child', {
        template:'<span class="item" style="color:red;">我是child</span>'
    });
    var app = new Vue({
        el:'#app'
    });
</script>

image.png

9、自定义事件

父组件给子组件传值使用props属性,
那么需要子组件更新父组件时,要使用自定义事件$on和$emit

  • $on监听: 不能监听驼峰标示的自定义事件,
    使用全部小写(abc)或者-(a-b-c)

  • $emit主动触发: $emit(事件名,传入参数)

1)声明父组件

var app = new Vue({
    el:'#app',
    data:{
        count:0
    },
    methods:{
        //定义计数方法
        changeCount:function(value){
            console.log(value);
            //计数
            this.count += 1;
        }
    }
});

2)自定义事件

<div id="app">
    <!-- 自定义事件 -->
    <child v-on:update-count="changeCount"></child>
    <p>{{count}}</p>
</div>

在事件v-on:update-count中的update-count就是自定义事件的名字,不要使用驼峰标示,html不区分大小写,会导致子元素无法主动触发父组件的自定义事件。

3)定义子组件

Vue.component('child', {
    template:'<button v-on:click="update">子组件Child</button>',
    methods:{
        update: function () {
            console.log('update');
            //主动触发事件执行
            this.$emit('update-count', '子组件参数');
        }
    }
});

子组件child中定义的update方法,内部通过$emit('update-count')主动触发父元素事件的执行。

完整代码:

<div id="app">
    <!-- 自定义事件 -->
    <child v-on:update-count="changeCount"></child>
    <p>{{count}}</p>
</div>
<script>
    Vue.component('child', {
        template:'<button v-on:click="update">子组件Child</button>',
        methods:{
            update: function () {
                console.log('update');
                //主动触发事件执行
                this.$emit('update-count', '子组件参数');
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            count:0
        },
        methods:{
            //定义计数方法
            changeCount:function(value){
                console.log(value);
                //计数
                this.count += 1;
            }
        }
    });
</script>

image.png

4)主动挂载

自定义事件不仅可以绑定在子组件,也可以直接挂载到父组件,使用$on绑定和$emit触发

var app = new Vue({
    el:'#app',
    data:{
        count:0
    },
    methods:{
        changeCount:function(value){
            console.log(value);
            //计数
            this.count += 1;
        }
    }
});

//主动挂载
app.$on('update-count', function(value){
    console.log(value);
    //计数
    this.count += 1;
});
app.$emit('update-count',123);

四、插槽分发

父子组件使用时,有时需要将父元素的模板跟子元素模板进行混合,这时就要用到slot插槽进行内容分发,
简单理解就是在子模板中先占个位置<slot>等待父组件调用时进行模板插入

1、slot插槽

1)子组件插槽

<template id="child-template">
    <div>
        <div>我是子组件</div>
        <div>{{msg}}</div>
        <!-- 定义slot插槽进行占位 -->
        <slot>我是默认内容,父组件不传入时我显示</slot>
    </div>
</template>
Vue.component('child', {
    template:'#child-template',
    props:['msg']
});

在子组件模板中使用<slot>标签定义插槽位置,标签中可以填写内容,当父组件不传入内容时显示此内容。

2)父组件分发

<div id="app">
    <!-- 传入数据 -->
    <child :msg="msgText">
        <!-- 传入模板,混合子模板 -->
        <h4>父组件模板</h4>
        <h5>模板混入....</h5>
    </child>
</div>

在引用<child>子组件时,标签中的内容会放在子组件的<solt>插槽中。

完整代码:

<div id="app">
    <!-- 传入数据 -->
    <child :msg="msgText">
        <!-- 传入模板,混合子模板 -->
        <h4>父组件模板</h4>
        <h5>模板混入....</h5>
    </child>
</div>

<template id="child-template">
    <div>
        <div>我是子组件</div>
        <div>{{msg}}</div>
        <!-- 定义slot插槽进行占位 -->
        <slot>我是默认内容,父组件不传入时我显示</slot>
    </div>
</template>
<script>
    Vue.component('child', {
        template:'#child-template',
        props:['msg']
    });
    var app = new Vue({
        el:'#app',
        data:{
            msgText:'父组件数据'
        }
    });
</script>

image.png

2、具名插槽

具名插槽slot,
就是给插槽起个名字
。在子组件定时可以定定义多个<slot>插槽,同时通过name属性指定一个名字,如:<slot
name='header'>,父组件引用时使用< slot='header'>进行插槽选择。

1)定义具名插槽

<template id="child-template">
    <div>
        <!-- 插槽header -->
        <slot name="header"></slot>
        <div>我是子组件</div>
        <div>{{msg}}</div>
        <!-- 插槽footer -->
        <slot name="footer"></slot>
    </div>
</template>

模板定义了两个插槽header和footer,分别使用name属性进行名称的指定。

2)父组件分发

<div id="app">
    <!-- 传入数据 -->
    <child :msg="msgText">
        <!-- 传入模板,混合子模板 -->
        <h4 slot="header">头部</h4>
        <h4 slot="footer">底部</h4>
    </child>
</div>

通过slot属性,来确定内容需要分发到那个插槽里面。

完整代码:

<div id="app">
    <!-- 传入数据 -->
    <child :msg="msgText">
        <!-- 传入模板,混合子模板 -->
        <h4 slot="header">头部</h4>
        <h4 slot="footer">底部</h4>
    </child>
</div>

<template id="child-template">
    <div>
        <!-- 插槽header -->
        <slot name="header"></slot>
        <div>我是子组件</div>
        <div>{{msg}}</div>
        <!-- 插槽footer -->
        <slot name="footer"></slot>
    </div>
</template>
<script>
    Vue.component('child', {
        template:'#child-template',
        props:['msg']
    });
    var app = new Vue({
        el:'#app',
        data:{
            msgText:'父组件数据'
        }
    });
</script>

image.png

3、slot-scope

作用域插槽slot-scope,父组件通过<slot>插槽混入父组件的内容,
子组件也可以通过slot作用域向插槽slot内部传入数据,使用方式:<slot
text='子组件数据'>
,父组件通过<template
slot-scope="props">
进行引用。

1)子组件定义

<template id="child-template">
    <div>
        <!-- 插槽text值 -->
        <slot text="子组件数据" ></slot>
    </div>
</template>

在slot标签中指定属性值,类似于prop属性的使用。

2)父组件使用

<div id="app">
    <!-- 传入数据 -->
    <child>
        <template slot-scope="props">
            <div>{{msgText}}</div>
            <div>{{props.text}}</div>
        </template>
    </child>
</div>

引用时用template标签指定,slot-scope属性指定接收数据的变量名,就可以使用花括号形式取值了。

完整代码:

<div id="app">
    <!-- 传入数据 -->
    <child>
        <template slot-scope="props">
            <div>{{msgText}}</div>
            <div>{{props.text}}</div>
        </template>
    </child>
</div>

<template id="child-template">
    <div>
        <!-- 插槽text值 -->
        <slot text="子组件数据" ></slot>
    </div>
</template>
<script>
    Vue.component('child', {
        template:'#child-template'
    });
    var app = new Vue({
        el:'#app',
        data:{
            msgText:'父组件数据'
        }
    });
</script>

3)版本更新

在2.5+之后,可以不局限于<template>,
任何元素都可以,同时可以使用解构赋值的方式进行数据解析。

子组件:

<template id="child-template">
    <div>
        <!-- 插槽text值 -->
        <slot name="head" text="header"></slot>
        <slot name="foot" text="footer" value="18"></slot>
        <slot name="cont" text="content" title="main"></slot>
    </div>
</template>

父组件使用:

<div id="app">
    <!-- 传入数据 -->
    <child>
        <!-- div标签使用slot-scope -->
        <div slot="head" slot-scope="props">子组件数据: {{props.text}} <span>{{fa}}</span></div>
        <div slot="foot" slot-scope="props">{{props.text}} == {{props.value}}</div>
        <!-- 结构赋值 -->
        <div slot="cont" slot-scope="{text, title}">{{text}} == {{title}}</div>
    </child>
</div>

js部分:

Vue.component('child', {
    template:'#child-template'
});
var app = new Vue({
    el:'#app',
    data:{
        fa:'father 数据'
    }
});

image.png

五、动态组件

使用<component>标签的is属性,动态绑定多个组件到一个挂载点,通过改变is绑定值,切换组件。

1、使用方式

1)定义多个子组件

Vue.component('index', {
    template:'<h5>首页</h5>'
});
Vue.component('news', {
    template:'<h5>新闻页</h5>'
});
Vue.component('login', {
    template:'<h5>登陆页</h5>'
});

2)使用component引用

<component :is="page"></component>

3)指定导航

/ <a href='#' @click.prevent="page='index'">首页</a>
/ <a href='#' @click.prevent="page='news'">新闻</a>
/ <a href='#' @click.prevent="page='login'">登陆</a>

4)完整代码

<div id="app">
    / <a href='#' @click.prevent="page='index'">首页</a>
    / <a href='#' @click.prevent="page='news'">新闻</a>
    / <a href='#' @click.prevent="page='login'">登陆</a>
    <hr>
    <component :is="page"></component>
</div>

<script>
    /***
     * 使用<component>标签的is属性,动态绑定多个组件到一个挂载点,
     * 通过改变is绑定值,切换组件
     * */
    Vue.component('index', {
        template:'<h5>首页</h5>'
    });
    Vue.component('news', {
        template:'<h5>新闻页</h5>'
    });
    Vue.component('login', {
        template:'<h5>登陆页</h5>'
    });
    var app = new Vue({
        el:'#app',
        data:{
            page:'index'
        }
    });
</script>

image.png

2、keep-alive

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

<div id="app">
    / <a href='#' @click.prevent="page='index'">首页</a>
    / <a href='#' @click.prevent="page='news'">新闻</a>
    / <a href='#' @click.prevent="page='login'">登陆</a>
    <hr>
    <keep-alive>
        <component :is="page"></component>
    </keep-alive>
</div>

使用keep-alive嵌套component。

Vue.component('index', {
    template:'<h5>首页</h5>',
    mounted: function () {
        console.log('挂载...首页');
    }
});
Vue.component('news', {
    template:'<h5>新闻页</h5>',
    mounted: function () {
        console.log('挂载...新闻页');
    }
});
Vue.component('login', {
    template:'<h5>登陆页</h5>',
    mounted: function () {
        console.log('挂载...登陆页');
    }
});

用生命周期中的mounted(挂载)钩子函数进行组件渲染监听,当组件第一次被渲染后就保存在内存中,下次切换不会被重新渲染。

3、refs

使用ref
给每个组件起一个固定的名字,方便后续直接引用操作,在父组件中使用$refs访问子组件。

<div id="app">
    <child ref="btn1"></child>
    <child ref="btn2"></child>
</div>
<script>
    /***
     * ref 给每个组件起一个固定的名字,方便后续直接引用操作
     * */
    Vue.component('child', {
        template:'<button>{{count}}</button>',
        data:function(){
            return {count:0}
        }
    });

    var app = new Vue({
        el:'#app'
    });

    app.$refs.btn1.count = 1;
    app.$refs.btn2.count = 2;
</script>

六、动画过渡

Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。

包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class

  • 可以配合使用第三方 CSS 动画库,如 Animate.css

  • 在过渡钩子函数中使用 JavaScript 直接操作 DOM

  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

Vue组件添加动画效果使用<transition name='v'>
标签
,并指定一个name名引用css动画,如:fade,然后通过以下6个class定义动画过程:

  • v-enter: 显示动画初始状态

  • v-enter-active: 显示动画执行状态

  • v-enter-to: 显示动画结束状态

  • v-leave: 隐藏动画初始状态

  • v-leave-active: 隐藏动

  • v-leave-to: 隐藏动画结束状态

1、基本使用

1)使用动画

<transition name="fade">
    <h6 v-show="show">{{greetText}}</h6>
</transition>

定义class

<style>
    .fade-enter-active, .fade-leave-active{
        transition: opacity 1s;
    }
    .fade-enter, .fade-leave-to{
        opacity: 0;
    }
</style>

js部分

var app = new Vue({
    el:'#app',
    data:{
        show:true,
        greetText:'Hello Vue!'
    }
});

image.png

5)使用css动画

<style>
    .fade-enter-active, .fade-leave-active{
        transition: all 1s;
    }
    .fade-enter, .fade-leave-to{
        transform: translateX(10px);
        opacity: 0;
    }
</style>

2.自定义过渡类名

可以手动指定过渡类名:

  • enter-class

  • enter-active-class

  • enter-to-class (2.1.8+)

  • leave-class

  • leave-active-class

  • leave-to-class (2.1.8+)

配合animate.css库一起使用:

<link rel="stylesheet" href="animate.min.css">
<div id="app">
    <button @click="show = !show" class="">切换显示</button>
    <transition
        enter-active-class="animated tada"
        leave-active-class="animated flipOutX"
    >
        <h6 v-show="show">{{greetText}}</h6>
    </transition>
</div>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            show:true,
            greetText:'Hello Vue!'
        }
    });
</script>

3、初始过渡

通过 appear 特性设置节点的在初始渲染的过渡

<transition appear name="fade">
    <h6 v-show="show">{{greetText}}</h6>
</transition>

4、过渡状态

通过mode属性可以指定动画切换效果先后顺序:

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开。

  • out-in:当前元素先进行过渡,完成之后新元素过渡进入。

1)指定过渡状态

<div id="app">
    <button @click="show = !show" class="">切换显示</button>
    <br>
    <br>
    <transition appear name="fade" mode="out-in">
       <!-- <button v-if="show" key="login">登陆</button>
        <button v-else key="register">注册</button>-->
        <button :key="show">{{show ? '登陆' : '注册'}}</button>
    </transition>
</div>

2)定义css

<style>
    .fade-enter-active, .fade-leave-active{
        transition: all .5s;
        position: absolute;
    }
    .fade-enter{
        transform: translateX(43px);
        opacity: 0;
    }
    .fade-leave-to{
        transform: translateX(-43px);
        opacity: 0;
    }
</style>


3)js部分

/****
 * 初始过渡 appear:
 * mode: in-out out-in
 * */
var app = new Vue({
    el:'#app',
    data:{
        show:true
    }
});

5、列表过渡

<transition-group>标签会以一个真实元素呈现:默认为一个 <span>。你也可以通过 tag 特性更换为其他元素。而且
内部元素需要提供唯一的 key 属性值。

css:

<style>
    .item {
        margin-right: 10px;
        /* 必须是块元素才行*/
        display: inline-block;
    }

    .fade-enter-active, .fade-leave-active {
        transition: all 1s;
    }

    .fade-enter, .fade-leave-to {
        transform: translateY(30px);
        opacity: 0;
    }
</style>

html:

<div id="app">
    <button @click="add">添加</button>
    <button @click="remove">删除</button>
    <transition-group tag="div" name="fade" mode="out-in">
        <span class="item" v-for="i in list" :key="i">{{i}}</span>
    </transition-group>
</div>

js:

<script>
    /****
     * 列表过渡:
     * <transition-group>
     * */
    var app = new Vue({
        el: '#app',
        data: {
            list: [1, 2, 3, 4, 5, 6, 7]
        },
        methods: {
            randomIndex: function () {
                return parseInt(Math.random() * this.list.length);
            },
            add: function () {
                var newNum = parseInt(Math.random() * 10);
                var index = this.randomIndex();
                console.log(newNum, index);
                this.list.splice(index, 0, newNum);
            },
            remove: function () {
                this.list.splice(this.randomIndex(), 1);
            }
        }
    });
</script>

七、数据处理

1、watch属性

在Vue组件中,使用watch属性来监听数据的变化,同时可以指定监听那个属性。

<div id="root"></div>
<script>
    var vm = new Vue({
        el:'#root',
        data:{
            name:'Tim'
        },
        watch:{
            name: function (newValue, oldValue) {
                console.log('newValue:'+newValue+';oldValue'+oldValue);
            }
        }
    });
    //改变name
    vm.name = 'Cat';//newValue:Cat;oldValueTim
</script>

例子中通过watch监听name属性的变化,回调函数中第一个参数是新值,第二个参数是旧值。

<div id="root">
    <p>
        firstName: <input type="text"
                          v-bind:value="firstName"
                          @keyup="changeFirstName($event);">
    </p>
    <p>
        lastName: <input type="text"
                         v-bind:value="lastName"
                         @keyup="changeLastName($event)">
    </p>
    <h4>{{fullName}}</h4>
</div>
<script>
    var vm = new Vue({
        el:'#root',
        data:{
            firstName:'Hello',
            lastName:'Kitty',
            fullName:'Hello Kitty'
        },
        watch:{
            firstName: function (newValue, oldValue) {
                console.log('newValue:'+newValue+';oldValue'+oldValue);
                this.fullName = newValue+' '+this.lastName;//更新fullname
            },
            lastName: function (newValue, oldValue) {
                console.log('newValue:'+newValue+';oldValue'+oldValue);
                this.fullName = this.firstName+' '+newValue;//更新fullname
            }
        },
        methods:{
            changeFirstName: function (event) {
                this.firstName = event.target.value;
            },
            changeLastName: function (event) {
                this.lastName = event.target.value;
            }
        }
    });
</script>

image.png

fullName由firstName和lastName共同决定,当改变firstName时需要重新计算fullName,改变lastName也一样。那么就需要监听这两个属性的变化去更新fullName,这时候就可以使用watch监听。

2.$watch

除了在组件内部使用watch也可以使用内部命令$watch进行属性监听。

<div id="root"></div>
<script>
    var vm = new Vue({
        el:'#root',
        data:{
            name:'Tim'
        }
    });
    //$watch 使用
    var unwatch = vm.$watch('name',function (newValue, oldValue) {
        console.log('newValue:'+newValue+';oldValue'+oldValue);
    });
    //改变name
    vm.name = 'Cat';
    //unwatch(); 取消监听
</script>

$watch第一个参数是需要监听的属性,第二个是回调函数用法和watch一样。需要取消监听只需拿到监听对象的引用,这个引用是返回一个函数对象,执行该对象就可以取消监听。

同时监听多个属性,可以不指定属性:

<div id="root"></div>
<script>
    var vm = new Vue({
        el:'#root',
        data:{
            name:'Tim',
            age:12
        }
    });
    //$watch 使用
    vm.$watch(function(){
        return this.name+this.age;
    },function (newValue, oldValue) {
        console.log('newValue:'+newValue+';oldValue'+oldValue);
    });
    //改变name
    vm.name = 'Cat';
    vm.age = 15;
    //newValue:Cat15;oldValueTim12
</script>

3、computed属性

computed计算属性用于定义比较复杂的属性计算,比如上边计算fullName的时候,需要使用watch两个属性:firstName和lastName,比较繁琐,但是使用computed就很简单。

<div id="root">
    <p>
        firstName: <input type="text"
                          v-bind:value="firstName"
                          @keyup="changeFirstName($event);">
    </p>
    <p>
        lastName: <input type="text"
                         v-bind:value="lastName"
                         @keyup="changeLastName($event)">
    </p>
    <h4>{{fullName}}</h4>
</div>
<script>
    var vm = new Vue({
        el:'#root',
        data:{
            firstName:'Hello',
            lastName:'Kitty'
        },
        computed:{
            fullName: function () {
                return this.firstName+' '+this.lastName;//计算fullname
            }
        },
        methods:{
            changeFirstName: function (event) {
                this.firstName = event.target.value;
            },
            changeLastName: function (event) {
                this.lastName = event.target.value;
            }
        }
    });
</script>

此时注意,不在data中定义fullName而是在computed属性中指定,回调函数返回值就是fullName的值,这样不管firstName和lastName谁发生变化都会更新fullName。

computed和methods区别:

  • 计算属性使用computed定义, 方法使用methods定义

  • 计算属性使用时不加括号执行符

  • 计算属性是基于它们的依赖进行缓存的,计算属性只有在它的相关依赖发生改变时才会重新求值。否则返回之前计算好的值,性能更高!

修改之前的代码

<h4>{{fullName}}</h4>

改为:

<h4>{{fullName()}}</h4>

在methods中定义fullName,使用效果和使用computed一致:

fullName: function () {

return this.firstName+' '+this.lastName;//计算fullname

}

4.getter和setter

在computed中,同样可以指定setter进行数据更新。上述例子中都是通过firstName和lastName来计算fullName,那么也可以通过fullName来更新firstName和lastName。

<div id="root">
    <p>
        fullName: <input type="text"
                          v-bind:value="fullName"
                          @keyup="changeFullName($event);">
    </p>
    <h4>firstName: {{firstName}}</h4>
    <h4>lastName: {{lastName}}</h4>
</div>
<script>
    var vm = new Vue({
        el: '#root',
        data: {
            firstName: 'Hello',
            lastName: 'Kitty'
        },
        computed: {
            fullName: {
                //getter
                get: function () {
                    return this.firstName + ' ' + this.lastName;//计算fullname
                },
                //setter
                set: function (newValue) {
                    var names = newValue.split(' ');
                    this.firstName = names[0];
                    this.lastName = names[names.length - 1];
                }
            }
        },
        methods: {
            changeFullName: function (event) {
                this.fullName = event.target.value;
            }
        }
    });
</script>

image.png

此时改变fullName就可以同步更新lastName和firstName。

八、生命周期

每个 Vue
实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到
DOM、在数据变化时更新 DOM
等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码。

比如 created 钩子可以用来在一个实例被创建之后执行代码;

常用的生命周期钩子函数有:

1) created: 实例创建完成后被立即调用

2) mounted: 实例挂载完成后被立即调用

3) beforeUpdate: 实例需要渲染之前调用

4) updated: 实例更新后调用

5) destroyed: Vue 实例销毁后调用

image.png
<div id="root">
    {{name}}
</div>
<script>
    var vm = new Vue({
        el:'#root',
        data:{
            name:'Tim'
        },
        created: function () {
            console.log('实例创建...');
        },
        mounted:function () {
            console.log('实例挂载...');
        },
        beforeUpdate:function () {
            console.log('实例将要更新...');
        },
        updated:function () {
            console.log('实例已更新...');
        },
        destroyed:function(){
            console.log('实例卸载...');
        }
    });
    //改变name
    vm.name = 'Cat';
    //vm.$destroy();//卸载
</script>

image.png

九、自定义指令

除了默认设置的核心指令 (v-model 和 v-show),Vue 也允许注册自定义指令。

1、基本使用

1)定义

//自定义全局指令v-focus
Vue.directive('focus',{
    //当绑定元素插入到DOM调用
    inserted: function (el) {
        //元素获取焦点
        el.focus();
    }
});

使用directive定义,第一个参数为指令名,使用时加上v-前缀才能生效。inserted属性指当绑定元素插入到DOM时调用。

定义局部指令使用directives:

var app = new Vue({
    el:'#app',
    directives:{
        focus:{
            inserted: function (el) {
                //元素获取焦点
                el.focus();
            }
        }
    }
});

2)使用

<div id="app">
    <input type="text" v-focus>
</div>

2、钩子函数

指令定义函数提供了几个钩子函数 (可选):

  • bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。

  • inserted:被绑定元素插入父节点时调用 (父节点存在即可调用,不必存在于
    document 中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode
    更新之前。指令的值可能发生了改变也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新

  • componentUpdated:所在组件的 VNode 及其孩子的 VNode 全部更新时调用。

  • unbind:只调用一次,指令与元素解绑时调用。

钩子函数的参数有三个:

  1. el:当前指令绑定元素

  2. binding:当前指令绑定的所有信息对象,有以下属性:

name:指令名,不包括 v- 前缀。

value:指令的绑定值,例如:v-my-directive="1 + 1", value 的值是 2。

oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。

expression:绑定值的字符串形式。例如 v-my-directive="1 +
1" ,expression 的值是 "1 + 1"。

arg:传给指令的参数。例如 v-my-directive:foo,arg 的值是 "foo"。

modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar,
修饰符对象 modifiers 的值是 { foo: true, bar: true }。

3)vnode:Vue 编译生成的虚拟节点

4)oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated

钩子中可用。

<div id="app">
    <input type="text" v-demo:arg.a.b="1+1">
</div>
<script>
    Vue.directive('demo',{
        bind: function (el,binding) {
            console.log(el);
            console.log(binding);
        }
    });
    var app = new Vue({
        el:'#app'
    });
</script>

image.png

3.实例图片懒加载

谷歌图片的加载做得非常优雅,在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来,用自定义指令可以非常方便的实现这个功能。

1)样式

<style>
    .item, .item img{
        width: 200px;
        height: 120px;
        float: left;
    }
</style>

2)自定义v-img指令

//定义全局自定义指令v-img
Vue.directive('img',{
    bind: function (el,binding) {
        //生成随机颜色
        var color = parseInt(Math.random()*0xFFFFFF).toString(16);
        //设置当前元素的背景,提前进行占位等待图片加载
        el.style.background = '#'+color;
        //setTimeout模拟图片加载的延时情况
        setTimeout(function () {
            //创建图片对象
            var img = new Image();
            //通过binding对象获取真实的图片url
            img.src = binding.value;
            //将图片元素插入DOM结构
            el.appendChild(img);
            //随机延时
        },Math.random()*3000+500);
    }
});

3)模拟数据

var app = new Vue({
    el:'#app',
    data:{
        //定义模拟数据
        imgs:[
            {url:'img/01.jpg'},
            {url:'img/02.jpg'},
            {url:'img/03.jpg'},
            {url:'img/04.jpg'}
        ]
    }
});

4)使用

<div id="app">
    <div v-img="item.url" v-for="item in imgs" class="item"></div>
</div>

5)效果

image.png

十、过滤器

Vue允许自定义过滤器,可被用作一些常见的文本格式化。

过滤器可以用在两个地方:mustache 插值和 v-bind 表达式 (后者从 2.1.0+
开始支持)。

过滤器应该被添加在 JavaScript 表达式的尾部,由"管道"符指示:

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

过滤器可以串联:

{{ message | filterA | filterB }}

使用Vue.filter定义全局过滤器,filters在组件内指定局部过滤器。

<div id="root">
    <h4>{{name | upperCase | length | test('A-','-B')}}</h4>
</div>
<script>
    /***
     * filter:过滤器
     * */
    var vm = new Vue({
        el: '#root',
        data: {
            name: 'hello'
        },
        filters: {
            upperCase: function (value) {
                return value.toUpperCase();
            },
            length: function (value) {
                return value+value.length;
            },
            test: function (value, begin, end) {
                console.log(value, begin, end);
                return begin+value+end;
            }
        }
    });
</script>

十一、路由

随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载。

优点:

  1. 用户体验好,和后台网速没有关系,不需要每次都从服务器全部获取,界面展现快。

  2. 可以再浏览器中输入指定想要访问的url路径地址。

  3. 实现了前后端的分离,方便开发。有很多框架都带有路由功能模块。

缺点:

  1. 对SEO不是很友好

  2. 在浏览器前进和后退时候重新发送请求,没有合理缓存数据。

  3. 初始加载时候由于加载所有模块渲染,会慢一点。

1、手动实现路由

前端路由目前主要有两种方法:

1)利用url的hash,就是常用的锚点(#)操作,类似页面中点击某小图标,返回页面顶部,JS通过hashChange事件来监听url的改变,IE7及以下需要轮询进行实现。一般常用框架的路由机制都是用的这种方法,例如Angualrjs自带的ngRoute和二次开发模块ui-router,react的react-route,vue-route...

2)利用HTML5的History模式,使url看起来类似普通网站,以"/"分割,没有"#",但页面并没有跳转,不过使用这种模式需要服务器端的支持,服务器在接收到所有的请求后,都指向同一个html文件,通过historyAPI,监听popState事件,用pushState和replaceState来实现。

由于使用hash方法能够兼容低版本的IE浏览器,简单的的自己搭建前端路由。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>自定义路由</title>
    <script>
        function Router() {
            this.routes = {};
            this.currentUrl = '';
        }
        //route 存储路由更新时的回调到回调数组routes中,回调函数将负责对页面的更新
        Router.prototype.route = function (path, callback) {
            this.routes[path] = callback || function () { };
        };
        //refresh 执行当前url对应的回调函数,更新页面
        Router.prototype.refresh = function () {
            this.currentUrl = location.hash.slice(1) || '/';
            this.routes[this.currentUrl]();
        };
        //init 监听浏览器 url hash 更新事件
        Router.prototype.init = function () {
            //load事件在当前页面加载时触发
            window.addEventListener('load', this.refresh.bind(this), false);
            //hashchange事件在当前页面URL中的hash值发生改变时触发
            window.addEventListener('hashchange', this.refresh.bind(this), false);
        };
        window.Router = new Router();
        window.Router.init();
    </script>
    <title></title>
</head>
<body>
<ul>
    <li><a href="#/">首页</a></li>
    <li><a href="#/home">主页</a></li>
    <li><a href="#/detail">详情页</a></li>
</ul>
<div id="page"></div>
<script>
    var body = document.querySelector('body');
    //切换hash的事件
    function changePage(page) {
        document.querySelector('#page').innerHTML = page;
    }
    //切换
    Router.route('/', function () {
        changePage('这是首页');
    });
    Router.route('/home', function () {
        changePage('这是主页');
    });
    Router.route('/detail', function () {
        changePage('这是详情页');
    });
</script>
</body>
</html>

2.vue-router

vue-router是Vue官方提供的路由,用 Vue.js + vue-router
创建单页应用,是非常简单的。使用 Vue.js
,我们已经可以通过组合组件来组成应用程序,当你要把 vue-router
添加进来,我们需要做的是,将组件(components)映射到路由(routes),然后告诉
vue-router 在哪里渲染它们。下面是个基本例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue-router</title>
    <script src="../vue.js"></script>
    <script src="../vue-router.js"></script>
</head>
<body>
<div id="app">
    <h3>Hello App!</h3>
    <p>
        <!-- 使用 router-link 组件来导航. -->
        <!-- 通过传入 `to` 属性指定链接. -->
        <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
        <router-link to="/index">Go to index</router-link>
        <router-link to="/news">Go to news</router-link>
    </p>
    <!-- 路由出口 -->
    <!-- 路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
</div>
<script>
    // 1. 定义(路由)组件。
    // 可以从其他文件 import 进来
    const Index = { template: '<div>首页</div>' }
    const News = { template: '<div>新闻页</div>' }

    // 2. 定义路由
    // 每个路由应该映射一个组件。 其中"component" 可以是
    // 通过 Vue.extend() 创建的组件构造器,
    // 或者,只是一个组件配置对象。
    // 我们晚点再讨论嵌套路由。
    const routes = [
        { path: '/index', component: Index },
        { path: '/news', component: News }
    ]

    // 3. 创建 router 实例,然后传 `routes` 配置
    // 你还可以传别的配置参数, 不过先这么简单着吧。
    const router = new VueRouter({
        routes // (缩写)相当于 routes: routes
    })
    // 4. 创建和挂载根实例。
    // 记得要通过 router 配置参数注入路由,
    // 从而让整个应用都有路由功能
    const app = new Vue({
        router
    }).$mount('#app');
    // 现在,应用已经启动了!
</script>
</body>
</html>

3.动态路由

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue-router</title>
    <script src="../vue.js"></script>
    <script src="../vue-router.js"></script>
</head>
<body>
<div id="app">
    <h3>Hello App!</h3>
    <p>
        <router-link to="/user/12">User12</router-link>
        <router-link to="/user/13">User13</router-link>
    </p>
    <router-view></router-view>
</div>
<script>
    // 1. 定义(路由)组件。
    const User = { template: '<div>用户: {{ $route.params.id }}</div>' };

    // 2. 定义动态路由
    const routes = [
        // 动态路径参数 以冒号开头
        { path: '/user/:id', component: User }
    ];

    // 3. 创建 router 实例
    const router = new VueRouter({
        routes
    });
    // 4. 创建和挂载根实例
    const app = new Vue({
        router
    }).$mount('#app');
    // 现在,应用已经启动了!
</script>
</body>
</html>

十二、Ajax

1、vue-resource介绍

vue-resource插件具有以下特点:

1. 体积小

vue-resource非常小巧,在压缩以后只有大约12KB,服务端启用gzip压缩后只有4.5KB大小,这远比jQuery的体积要小得多。

2. 支持主流的浏览器

和Vue.js一样,vue-resource除了不支持IE
9以下的浏览器,其他主流的浏览器都支持。

3.
支持Promise API和URI
Templates

Promise是ES6的特性,Promise的中文含义为"先知",Promise对象用于异步计算。

URI Templates表示URI模板,有些类似于ASP.NET MVC的路由模板。

4. 支持拦截器

拦截器是全局的,拦截器可以在请求发送前和发送请求后做一些处理。

拦截器在一些场景下会非常有用,比如请求发送前在headers中设置access_token,或者在请求失败时,提供共通的处理方式。

2、使用规则

引入vue-resource后,可以基于全局的Vue对象使用http,也可以基于某个Vue实例使用http。

1)基于全局Vue对象使用http

Vue.http.get('/someUrl',[options]).then(successCallback, errorCallback);
Vue.http.post('/someUrl',[body],[options]).then(successCallback,errorCallback);
Vue.http.jsonp('/someUrl',[options]).then(successCallback,errorCallback);

2)在一个Vue实例内使用$http

this.$http.get('/someUrl',[options]).then(successCallback,errorCallback);
this.$http.post('/someUrl',[body],[options]).then(successCallback,errorCallback);
this.$http.jsonp('/someUrl', [options]).then(successCallback, errorCallback);

在发送请求后,使用then方法来处理响应结果,then方法有两个参数,第一个参数是响应成功时的回调函数,第二个参数是响应失败时的回调函数。

3)实例

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../vue.min.js"></script>
    <script src="../vue-resource.js"></script>
</head>
<body>
<div id="root"></div>
<script>
     var vm = new Vue({
        el: '#root',
        data: {
            apiUrl:'https://api.github.com/users/octocat/gists'
        },
        mounted: function () {
            this.$http.get(this.apiUrl).then(function (result) {
                console.table(result.data);
            }, function (result) {
                console.error(result);
            });
        }
    });

</script>
</body>
</html>


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装...
    youins阅读 9,449评论 0 13
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,201评论 0 6
  • 此文基于官方文档,里面部分例子有改动,加上了一些自己的理解 什么是组件? 组件(Component)是 Vue.j...
    陆志均阅读 3,805评论 5 14
  • Yan Ruyu_Vue学习笔记 Vue基本认知 Vue是一个JavaScript框架,它的特点在于使用者不用再思...
    YuJianChi阅读 522评论 0 0
  • 什么是组件 组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用...
    angelwgh阅读 777评论 0 0