VUE学习整理

VUE

VUE介绍

vue是前端渐进式js框架,使用声明式编程,与JQuery的命令式编程不同,使用声明式编程可以减少很多代码。

 

作者:尤雨溪

 

作用:动态构建用户界面

vue借鉴了angular的模板和数据绑定技术,借鉴了react的组件化和虚拟DOM技术。

 

特点:

1、遵循MVVM模式

2、编写简洁,体积小,运行效率高

3、本身只关注UI。可以引入vue插件或第三方库(vue插件:依赖vue,第三方库:不依赖vue)

 

官网:

中文:

https://cn.vuejs.org

英文:

https://vuejs.org

 

vue扩展插件:

vue-cli:vue脚手架

vue-resource(axios):ajax请求

vue-router:路由

vuex:状态管理

vue-lazyload:图片懒加载

vue-scroller:页面滑动相关

mint-ui:移动端组件

element-ui:pc端组件

vue的mvvm

其中View是页面,Model是data,ViewModel是Vue实例。

vue通过dom监听和数据绑定同步view和model之间的数据。

基本使用及语法

基本使用:

1、下载vue.js,就一个js文件

 

2、编写test1.html,其中引入vue.js

test1.html内容:

<div id="ddd">

    <input type="text" v-model="message">

    {{message}}

</div>

<script src="vue.js"></script>

 

<script>

    // 创建vue对象,el指定元素,data指定数据

    var vm = new Vue({

        el: '#ddd',

        data: {

            message: 'kkk'

        }

    })

</script>

这里创建了Vue对象(vue.js中定义的),使用el指定vue管理的dom元素的id,data指定数据,然后input标签用v-model绑定数据(双向绑定数据,v-model是一个指令,类似于v-if等),显示数据的地方用{{}}绑定数据(双大括号表达式),都绑定了message这个变量。

这里mvvm中的model是data,view是div对象,vm是Vue实例对象。

注意:当input标签绑定数据以后,表示这个标签交给vue来生成了。

 

3、效果:

当输入框的值改变时,下面文本显示也跟着变(声明式编程的好处,不需要去手动写事件操作)。

 

 

4、ps:

1、data里面的变量会设置到Vue对象的属性中,如控制台手动更改变量,界面上绑定的变量也会自动更新:

2、vue是一个渐进式框架,它可以对现有的页面进行逐步替换,比如对其中某一块内容进行vue渲染。vue可以定义多个对象,如:

 

 

 

虽然都定义了message的变量数据,但它们互不影响:

 

 

5、安装vue devtools插件:

在线安装(可能需要先FQ):

 

该插件默认不能对本地文件进行使用,可以打开选项让其支持:

 

F12debug窗口里点开即可看到效果:

 

 

基本语法:

双大括号表达式和v-text、v-html指令

 

html:

<div id="ddd">

    <p>{{msg}}</p>

    <p>{{msg.toUpperCase()}}</p>

    <p>{{bbb}}</p>

    <p v-text="bbb"></p>

    <p v-html="bbb"></p>

</div>

 

<script src="vue.js"></script>

 

<script>

    var vm = new Vue({

        el: '#ddd',

        data: {

            msg: 'kkk',

            bbb: '<a href="https://www.baidu.com">go to baidu</a>'

        }

    })

</script>

 

效果:

 

双大括号相当于v-text,会转义成文本,v-html相当于html中的innerHtml,会翻译成html。

 

指令:

一般以"v-"开头,也有例外,如ref指令。

常用指令有v-on、v-bind、v-if(v-else)、v-show、ref、v-cloak、v-for、v-text、v-html、v-model

强制数据绑定(v-bind:或:)

<div id="ddd">

    <img :src="imgUrl"/>

</div>

 

<script src="vue.js"></script>

 

<script>

    var vm = new Vue({

        el: '#ddd',

        data: {

            imgUrl: 'https://cn.vuejs.org/images/logo.png'

        }

    })

</script>

 

img的src属性从data中取值,不能用{{}}表达式,而用<img :src="imgUrl"/>绑定,

它是<img v-bind:src="imgUrl"/>的缩写。

 

也就是说在html标签中的属性名前面加冒号或者加v-bind:就可以将属性值指定成变量名,其实就是个js表达式,如:<img v-bind:src="'https://cn.vuejs.org/images/logo.png'"/>也能正常显示。

 

绑定事件@click

<div id="ddd">

    <button @click="ctest">click me</button>

    <button @click="ctest2('bbb')">常量参数</button>

    <button @click="ctest2(t2)">变量参数</button>

</div>

 

<script src="vue.js"></script>

 

<script>

    var vm = new Vue({

        el: '#ddd',

        data: {

            t2: 'ttt'

        },

        methods: {

            ctest() {

                alert('clicked')

            },

            ctest2(aaa) {

                alert(aaa);

            }

        }

 

    })

</script>

 

其中@click是v-on:click的缩写.

事件方法都写在methods里。这里mothods里面直接写函数是es6的语法支持。

这里的:

ctest() {

    alert('clicked')

}

相当于

ctest: function() {

        alert('clicked')

    }

 

 

ref指令

ref指令不以v-开头,通过ref指令为dom元素指定唯一标识,类似于id,然后通过vue的$refs.属性值获取到dom元素,如:

<div id="test">

    <p ref="mmm">test message</p>

    <button @click="showMsg">showIt</button>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

        el: '#test',

        methods: {

            showMsg() {

                // this.$refs获取到ref属性定义的标签集合,然后通过属性名获取到具体标签dom元素

                alert(this.$refs.mmm.innerHTML)

                // alert(this.$refs.mmm.textContent)

            }

        }

    })

</script>

 

这里为p标签指定ref="mmm",然后通过this.$refs.mmm获取到该dom对象。

 

v-cloak指令

v-cloak用于对标签做一个标识,通过与css样式相结合,可以做到防止闪现的问题,如下程序:

 

<div id="test">

    <p>{{msg}}</p>

</div>

 

<script src="vue.js"></script>

 

<script>

    alert(1)

    new Vue({

        el: '#test',

        data: {

                msg: 'hello'

            }

    })

</script>

在Vue创建之前弹出alert(1),这时候页面会有{{msg}}显示出来,如果页面很大加载时间很长,就会出现这种问题,{{msg}}在Vue挂载完以后才会被替换。

 

使用v-cloak指令可以解决这个问题,如:

<style>

    [v-cloak]{

        display:none;    

    }

</style>

<div id="test">

    <p v-cloak>{{msg}}</p>

</div>

 

<script src="vue.js"></script>

 

<script>

    alert(1)

    new Vue({

        el: '#test',

        data: {

                msg: 'hello'

            }

    })

</script>

 

为p标签加了一个v-cloak指令属性,它在Vue挂载完成前一直存在,而样式中[v-cloak]是css的属性选择器,为它指定了display:none属性,也就是Vue挂载完成前一直不显示。

 

由于vue挂载完成后所有指令属性都会从html中移除,因此挂载完成后v-cloak将被移除,此时display:none属性不生效,于是标签又会显示出来,而此时已经替换完了。

自定义指令

自定义指令类似于v-if、v-show等,也是用在标签上的,自定义指令分为全局的和局部的,全局指令的可以用在多个Vue实例上,局部指令定义在创建Vue的参数里。

如:

<div id="test1">

    <p v-upper-text="msg"></p>

    <p v-lower-text="msg"></p>

</div>

 

<div id="test2">

    <p v-upper-text="msg"></p>

    <p v-lower-text="msg"></p>

</div>

 

<script src="vue.js"></script>

 

<script>

    // 定义全局指令upper-text,转为大写,使用时加v-,写成v-upper-text

    Vue.directive(

        // 第一个参数el是指令所在的标签,第二个参数binding是指令包含指令参数相关数据的对象,binding.value就能得到参数值

        'upper-text', function(el, binding) {

            el.textContent = binding.value.toUpperCase();

        }

    )

new Vue({

        el: '#test1',

        data: {

                msg: 'Hello'

            },

        // 定义局部指令lower-text,转为小写,使用时加v-,写成v-lower-text

        directives: {

            'lower-text': function(el, binding) {

                el.textContent = binding.value.toLowerCase();

            }

        }

    })    

    

new Vue({

        el: '#test2',

        data: {

                msg: 'Apache Tomcat'

            }

    })    

</script>

 

由于'lower-text'是局部指令,定义在第一个Vue的参数里,只能在第一个Vue实例管理的范围#test1里使用。因此第二个Vue实例管理的范围#test2就找不到该指令.

 

computed计算属性和watch监视

computed的属性不需要再写在data里面,而监视的属性与data相互独立。

computed计算属性

使用,如:

<script src="vue.js"></script>

<div id="test">

    <input type="text" v-model="firstName"/> <br/>

    <input type="text" v-model="lastName"/> <br/>

    <input type="text" v-model="fullName"/> <br/>

</div>

 

<script>

    var vm = new Vue({

        el: "#test",

        // data属性

        data: {

            firstName: 'aaa',

            lastName: 'bbb'

        },

        // 计算属性

        computed: {

            fullName: {

                get() {

                    console.log("初始化或者firstName或lastName值发生了改变")

                    return this.firstName + " " + this.lastName

                },

                set(val) {

                    console.log("fullName值发生了改变")

                    // this.firstName = val.split(' ')[0]

                    // this.lastName = val.split(' ')[1]

                }

            }

        }

    })

</script>

这里的fullName属性在firstName或者lastName更改时都需要刷新,因此将它定义到computed中。computed中的方法只有在相关属性发生改变时才会调用,不相关的属性发生改变不会调用。

computed跟data属性类似,data属性分为数据改变同步到view,view改变同步到数据这两种情况。computed与之对应的是get与set方法。

1、数据改变同步到view这种情况下,couputed可以同时监视多个属性的变化,其中有任何一个属性值改变时,它就会被调用一次get()方法(计算一次)。

2、view改变同步到数据这种情况下,computed属性在view里的值发生改变,会自动回调set方法,而不是更新数据(因为computed没有像data那样绑定属性,而是绑定的一个表达式,可能含有多个属性)。

 

computed通常用在需要多个属性通过表达式计算出一个结果的情况,它可以自动监视多个属性的变化,而不需要对每个属性都去单独写一个监视函数命令式的计算。

 

ps:

1、通常computed只用get而不用set,因此可以只写一个方法,默认就是get,如:

 

        computed: {

            fullName() {

                console.log("初始化或者firstName或lastName值发生了改变")

                return this.firstName + " " + this.lastName

            }

        }

相当于:

 

        computed: {

            fullName: {

                get() {

                    console.log("初始化或者firstName或lastName值发生了改变")

                    return this.firstName + " " + this.lastName

                }

            }

        }

 

2、计算属性使用了缓存,多次调用只要没有相关属性发生改变,则只会调用一次(缓存的key就是计算属性的名字,如fullName)。

如:

只执行了一次

watch监视

监听某个属性的变化,执行回调函数,与计算属性的自动监视相关属性改变不同,这里是自己指定需要监视的属性。

<script src="vue.js"></script>

<div id="test">

    <input type="text" v-model="firstName"/> <br/>

    <input type="text" v-model="lastName"/> <br/>

    <input type="text" v-model="fullName"/> <br/>

</div>

 

<script>

    var vm = new Vue({

        el: "#test",

        data: {

            firstName: 'aaa',

            lastName: 'bbb',

            fullName: ''

        },

        // 通过设置Vue的watch属性设置监视

        watch: {

            firstName: function(newVal, oldVal) {

                console.log("oldVal=" + oldVal)

                this.fullName = newVal + ' ' + this.lastName

            }

        }

    })

    // 通过调用Vue的$watch方法设置监视

    vm.$watch('lastName', function(newVal){

        this.fullName = this.firstName + ' ' + newVal

    })

</script>

 

 

有2种方式,设置watch属性或者调用Vue的$watch方法,第一个参数是新值,第二个参数是旧值。

 

$watch对应官方api文档:

 

动态样式控制

:class绑定

:class可以用指定一个js表达式,可以是普通字符串或者对象、数组。

class属性使用普通字符串或者变量

<script src="vue.js"></script>

 

<head>

    <style>

        .aClass{color: red;}

    </style>

</head>

 

<div id="test">

    <p :class="'aClass'">xxx字符串</p>

</div>

 

<script>

    new Vue({

        el: '#test'

    })

</script>
这里'aClass'的单引号不能少,因为它是一个js表达式。

 

或者

<script src="vue.js"></script>

 

<head>

    <style>

        .aClass{color: red;}

    </style>

</head>

 

<div id="test">

    <p :class="a">xxx字符串</p>

</div>

 

<script>

    new Vue({

        el: '#test',

        data: {

            a: 'aClass'

        }

    })

</script>

这里的a是一个变量。

测试:

:class属性使用js对象

如:

<script src="vue.js"></script>

 

<head>

    <style>

        .aClass{color: red;}

        .bClass{color: blue;}

        .cClass{font-size: 20px;}

    </style>

</head>

 

<div id="test">

    <p :class="{aClass: isA, bClass: isB, cClass: isC}">xxx字符串</p>

    <p><button @click='update'>更新</button></p>

</div>

 

<script>

    new Vue({

        el: '#test',

        data: {

            isA: true,

            isB: false,

            isC: false

        },

        methods: {

            update() {

                this.isA = false,

                this.isB = true,

                this.isC = true

            }

        }

    })

</script>

用一个json对象表示:class属性{aClass: isA, bClass: isB, cClass: isC},然后通过变量的true或false分别控制每个样式是否加入(属性名用class名字,属性值用布尔值)。

点更新前:

 

 

点更新后:

:class属性使用数组(很少使用)

如:

<script src="vue.js"></script>

 

<head>

    <style>

        .aClass{color: red;}

        .bClass{color: blue;}

        .cClass{font-size: 20px;}

    </style>

</head>

 

<div id="test">

    <p :class="['aClass', 'cClass']">xxx字符串</p>

</div>

 

<script>

    new Vue({

        el: '#test'

    })

</script>

由于class的属性可以直接用空格间隔写出来,使用数组指定class的场景很少。

:style绑定

:style可以用普通字符串或者js对象绑定。

:style属性使用普通字符串或变量

如:

普通字符串:

<script src="vue.js"></script>

 

<div id="test">

    <p :style="'color: red'">xxx字符串</p>

</div>

 

<script>

    new Vue({

        el: '#test'

    })

</script>

这里'color: red'的单引号不能省略,因为它是一个js表达式。

 

使用变量:

<script src="vue.js"></script>

 

<div id="test">

    <p :style="a">xxx字符串</p>

</div>

 

<script>

    new Vue({

        el: '#test',

        data: {

            a: 'color: red'

        }

    })

</script>

这里a是一个变量

效果:

:style属性使用js对象

<script src="vue.js"></script>

 

<div id="test">

    <p :style="{color:'red', fontSize: bbb + 'px'}">xxx字符串</p>

</div>

 

<script>

    new Vue({

        el: '#test',

        data: {

            bbb: 30

        }

    })

</script>

 

js对象的属性名和属性值跟style里面的相对应,不过像font-size这种有中横线的属性名可以用驼峰来写,写成fontSize,当然用font-size也可以,不过有这种特殊字符的属性名上需要加单引号才行,如:

<p :style="{color:'red', 'font-size': bbb + 'px'}">xxx字符串</p>

显示:

条件渲染

用户v-if、v-else、v-show控制

如:

<div id="test">

    <p v-if="ok">成功了</p>

    <p v-else>失败了</p>

    

    <p v-show="ok">成功了show</p>

    

    <button @click="ok=!ok">切换</button>

</div>

 

 

<script src="vue.js"></script>

<script>

    new Vue({

        el: "#test",

        data:{

            ok: true

        }

    })

</script>

 

v-if和v-else控制的标签只会显示其中一个,这里用ok这个变量来判断。如果v-if标签和v-else标签中间有其他标签,其他标签的显示不受影响。

 

v-show跟v-if类似,只是没有对应的v-else

v-if采用不生产标签元素的方式,而v-show采用display:none来隐藏,因此在页面显示隐藏切换频繁的情况下v-show的效率会高一些。

列表渲染

遍历数组

采用v-for控制标签,标签会根据集合的长度显示多个,如:

<div id="test">

    <ul>

        <li v-for="(p, index) in persons">

            {{index}}----{{p.name}}---{{p.age}}

            <button @click="deleteP(index)">删除</button>

            <button @click="updateP(index, {name: 'zhangsan', age: '30'})">更新</button>

        </li>

    </ul>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

        el: "#test",

        data: {

            persons: [

                {name: 'aaa', age: '17'},

                {name: 'bbb', age: '18'},

                {name: 'ccc', age: '19'},

                {name: 'ddd', age: '20'}

            ]

        },

        methods: {

            // 根据下标删除数组元素

            deleteP(idx) {

                this.persons.splice(idx, 1)

            },

            // 根据下标更新数组元素

            updateP(idx, newP) {

                // 直接用下标替换数组元素不会触发界面view自动刷新

                //this.persons[idx] = newP

                // 采用数组的变异方法才能触发界面view自动刷新

                this.persons.splice(idx, 1, newP)

            }

        }

    })

</script>

 

说明:

1、这里用v-for控制li标签,遍历数组,li会显示4个(数组长度为4)

2、(p, index) in persons中p是元素,index是遍历的下标。

3、这里的删除按钮点击时删除当前行,更新按钮点击时将当前行更新为zhangsan 30。

4、直接用下标替换数组元素不会触发界面view自动刷新,因为vue不知道。需要数组的变异方法才能触发界面view自动刷新。变异方法是经过vue重写的数组方法。见如下api说明:

 

5、数组的splice方法可以删除、更新、插入元素,功能比较强大,它的第一个参数是下标,第二个参数是删除元素的个数,第三个参数是替换后的元素,如:

删除:

persons.splice(idx, 1)

从idx下标开始删除1个元素,没有第三个参数表示不替换。

 

更新:

persons.splice(idx, 1, newP)

将idx下标的元素删除1个,替换为newP

 

插入:

persons.splice(idx, 0, newP)

将idx下标的元素删除0个,也就是不删除,再插入一个newP元素。

 

6、给v-for加:key属性可以提高渲染效率,如:

<li v-for="(p, index) in persons" :key="index">

遍历对象

如:

<div id="test">

    <ul>

        <li v-for="(value, key) in person" :key="key">

            {{key}}---{{value}}

        </li>

    </ul>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

        el: "#test",

        data: {

            person: {name: 'aaa', age: '17'}

        }

    })

</script>

 

效果:

 

对象有多个属性时v-for会生成多个标签,:key属性值一般用对象的key,而不用value,因为key不会重复。

 

案例:列表的模糊搜索

实现效果:输入框输入关键字,列表自动模糊匹配名字中包含关键字的行,其他行过滤掉。

 

思路:

1、将列表变量对应到一个computed变量中,包含所有列的数组放到其他地方(如data中或者定义到Vue外面),回调函数中模糊匹配数组中包含关键字的列生成新数组返回。

实现:

<div id="test">

    <p>

        <input type="text" v-model="searchName"></input>

    </p>

    <ul>

        <li v-for="(p, index) in fPersons">

            {{index}}---{{p.name}}----{{p.age}}

        </li>

    </ul>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

        el: "#test",

        data:{

            searchName: '',

            persons: [

                {name: 'Tom', age: '17'},

                {name: 'Jack', age: '18'},

                {name: 'Bomb', age: '19'},

                {name: 'ggg', age: '20'}

            ]

        },

        computed: {

            fPersons() {

                /*

                // 常规方式:遍历数组元素,匹配上的放入新数组

                var arr = [];

                for(var i = 0; i < this.persons.length; i++) {

                    if (this.persons[i].name.indexOf(this.searchName) >= 0) {

                        arr.push(this.persons[i])

                    }

                }

                return arr

                */

                // 使用数组的filter方法更简洁

                return this.persons.filter(p => p.name.indexOf(this.searchName) >= 0)

            }

        }

    })

</script>

这里将persons放到了data中,也可以放到Vue的外面。

 

2、常规思路:将列表值对应到data的一个数组便令中,用watch监听输入框变量的改变,一旦输入框有改变,就执行回调函数,回调函数中模糊匹配关键字,生成新数组赋值给数组变量,列表就会更新列表。

实现:

<div id="test">

    <p>

        <input type="text" v-model="searchName"></input>

    </p>

    <ul>

        <li v-for="(p, index) in fPersons">

            {{index}}---{{p.name}}----{{p.age}}

        </li>

    </ul>

</div>

 

<script src="vue.js"></script>

 

<script>

    var persons = [

                {name: 'Tom', age: '17'},

                {name: 'Jack', age: '18'},

                {name: 'Bomb', age: '19'},

                {name: 'ggg', age: '20'}

            ]

    

    new Vue({

        el: "#test",

        data:{

            searchName: '',

            fPersons: persons

        },

        watch:{

            searchName() {

                this.fPersons = persons.filter(p => p.name.indexOf(this.searchName) >= 0)

            }

        }

    })

</script>

案例:列表的排序

接上例,加入排序的功能

 

思路,定义一个排序变量orderType,computed列表数组变量回调函数中判断它的值来决定升序、降序还是原本顺序,然后在按钮上绑定改变该变量值的回调函数,当该变量改变时computed会自动重新计算(因为orderType是相关属性)

实现:

<div id="test">

    <p>

        <input type="text" v-model="searchName"></input>

    </p>

    <ul>

        <li v-for="(p, index) in fPersons">

            {{index}}---{{p.name}}----{{p.age}}

        </li>

    </ul>

    

    <button @click="setOrderType(1)">升序</button>

    <button @click="setOrderType(2)">降序</button>

    <button @click="setOrderType(0)">原本顺序</button>

</div>

 

<script src="vue.js"></script>

 

<script>

    var vm = new Vue({

        el: "#test",

        data:{

            searchName: '',

            orderType: 0,

            persons: [

                {name: 'Tom', age:17},

                {name: 'Jack', age:16},

                {name: 'Bomb', age:21},

                {name: 'ggg', age:20}

            ]

        },

        methods: {

            setOrderType(orderType) {

                this.orderType = orderType

            }

        },        

        computed: {

            fPersons() {

                // 过滤

                var arr = this.persons.filter(p => p.name.indexOf(this.searchName) >= 0)

                if (this.orderType == 0) {

                    return arr;

                }

                // 排序,利用数组的sort方法

                arr.sort(function(p1, p2) {

                    if (vm.orderType == 1) {

                        return p1.age - p2.age

                    } else {

                        return p2.age- p1.age

                    }

                })

                return arr

            }

        }

    })

</script>

事件处理-扩展

事件的$event参数

事件的参数,无参数时默认传了一个$event,有自定义参数时,不会自动传$event。

因此需要同时传自定义参数和$event时,需要手动写出$event参数。

如:

<div id="test">

    <button @click="test1">默认测试1</button>

    <button @click="test1($event)">默认测试2</button>

    <button @click="test2('aaa', $event)">默认测试3</button>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

        el: '#test',

        methods: {

            test1(event){

                alert(event.target.innerHTML)

            },

            test2(a, event){

                alert(a + "------" + event.target.innerHTML)

            }

        }

    })

</script>

第一个按钮和第二个按钮的作用相同,第一个是第二个的简写,没有参数时默认会自动加一个$event参数。

第三个按钮需要传2个参数,其中一个是$event,因此需要手动写出来。

 

事件的修饰符(阻止冒泡、停止标签默认行为)

修饰符.stop阻止冒泡,.prevent停止标签默认行为。

如:

<div id="test">

    <div style="width:200px; height:200px; background-color:red;" @click="test1">

        <div style="width:100px; height:100px; background-color:blue;" @click.stop="test2">

        </div>

    </div>

 

    <a href="https://www.baidu.com" @click.prevent="test3">去百度</a>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

        el: '#test',

        methods: {

            test1() {

                alert("outter")

            },

            test2() {

                alert("inner")

            },

            test3() {

                alert("停止去百度")

            }

        }

    })

</script>

这里如果没有.stop则会先弹出inner,后弹出outter,加了以后只会弹出inner,停止了向外冒泡。

如果没有.prevent则页面会在弹出test3以后跳转到百度去,加了以后就不跳转了。

按键修饰符

可以指定特定按键才触发keyup等按键事件,如:

<div id="test">

    <input type="text" @keyup.13="test"/>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

        el: '#test',

        methods: {

            test() {

                alert(event.target.value)

            }

        }

    })

</script>

 

只有放开回车键(回车键的keyCode是13)时才弹出alert,其他按键不调用test方法。@keyup.13也可以写成@keyup.enter

常用表单组件数据绑定和收集

如:

<div id="test">

    <form action="xxx">

        <p>用户名:<input type="text" v-model="username"/></p>

        <p>性别:

            <input type="radio" value="female" v-model="sex"/>女

            <input type="radio" value="male" v-model="sex"/>男

        </p>

        <p>爱好:

                <input type="checkbox" value="games" v-model="interest"/>游戏

                <input type="checkbox" value="football" v-model="interest"/>足球

                <input type="checkbox" value="basketball" v-model="interest"/>篮球

        </p>

        <p>城市:

            <select v-model="city">

                <option :value="cityOption.id" v-for="cityOption in cities">{{cityOption.name}}</option>

            </select>

        </p>

        <p>说明:

            <textarea v-model="remark"></textarea>

        </p>

        

        <input type="submit" value="提交" @click.prevent="submitForm"></input>

    </form>

</div>

 

<script src="vue.js"></script>

<script>

    new Vue({

        el: '#test',

        data: {

            username: 'test username',

            sex: 'male',

            interest: ['games', 'basketball'],

            cities: [

                {id: 'bj', name: '北京'},

                {id: 'sh', name: '上海'},

                {id: 'cd', name: '成都'}

            ],

            city: 'cd',

            remark: 'test remark'

        },

        methods:{

            submitForm() {

                console.log("表单数据为:", this.username, this.sex, this.interest, this.cities, this.remark)

            }

        }

    })

</script>

 

这里用@click.prevent="submitForm"阻止了表单的默认提交。

Vue的生命周期

概念

Vue的生命周期分为初始化、更新、销毁3个阶段,可以自定义每个阶段的回调函数,

初始化阶段

1、首先创建Vue对象,创建完成后回调beforeCreate

2、对变量完成数据绑定,对view完成监听绑定后回调created

3、检查是否有template选项,如果有就编译template的配置,如果没有就编译el对应的标签作为template,完了以后执行回调beforeMount

4、vue会对view需要显示的数据先在内存中全计算好,然后一次性挂载到页面上去,比如view里面多次引用一个computed的变量,只会在内存中计算一次,挂载完成后回调mounted

初始化阶段只执行一次

更新阶段

5、每次数据的改变会自动更新view,更新之前回调beforeUpdate,更新完成回调updated

更新阶段可以执行多次

销毁阶段

6、当调用vue的$destory()方法时会销毁vue,销毁后数据绑定和view监听等都会没有,页面也没有交互了。销毁前回调beforeDestroy,销毁后回调destroyed、

销毁阶段只执行一次

 

常用的是mounted和beforeDestroy方法:

初始化阶段完成回调mounted,常用来做一些ajax取数、启动定时器等异步任务操作。

页面销毁前回调beforeDestroy常用来做一些收尾工作,如清除定时器等。

 

案例

实现一个页面加载后一个标签每隔1秒切换一次显示(隐藏)的自动闪烁效果,点击按钮后停止闪烁并且销毁vm,利用mouted回调函数在vm挂载(初始化)之后执行,beforeDestroy回调函数在vm销毁前执行,实现代码如下:

<div id="test">

    <button @click="destroyIt">destroy vm</button>

    

    <p v-show="isShow">测试的文本<p>

</div>

 

<script src="vue.js"></script>

 

<script>

    var vm = new Vue({

        el: '#test',

        data: {

            isShow: true

        },

        beforeCreate() {

            console.log('beforCreate')

        },

        created() {

            console.log('created')

        },

        beforeMount() {

            console.log('beforeMount')

        },

        

        // vm挂载后启动定时器,测试文本开始闪烁,每隔1秒切换一次显示(隐藏)

        mounted() {

            console.log('mounted')

            this.intervalId = setInterval(function() {

                console.log("--------")

                vm.isShow = !vm.isShow

            }, 1000)

        },

        

        beforeUpdate() {

            console.log('beforeUpdate')

        },

        updated() {

            console.log('updated')

        },

        // vm销毁前清除定时器

        beforeDestroy() {

            console.log('beforeDestroy')

            clearInterval(this.intervalId)

        },

        destroyed() {

            console.log('destroyed')

        },

        

        methods: {

            // 手动执行销毁vm,vm销毁后isShow变量将不再跟view绑定,测试文本停止闪烁

            destroyIt() {

                vm.$destroy()

            }

        }

    })    

</script>

动画效果

Vue的动画可以用在条件渲染 (使用 v-if)、条件展示 (使用 v-show)、动态组件、组件根节点等情况下。

如使用v-show时,v-show分为显示隐藏2个状态。在这2个状态切换时可以加入动画过渡效果。Vue定义动画时,整个动画过程是个独立的过程,动画的最初状态和最终状态不一定跟v-show的最初和最终状态相对应。

 

动画分为显示(enter)、隐藏(leave)2种,每种又对应3种状态,分别是:

显示开始(enter)、显示中(enter-active)、显示结束(enter-to)、隐藏开始(leave-active)、隐藏中(leave-active)、隐藏结束(leave-to)。

 

使用方法:

1、使用transition标签包裹v-show等标签,v-show加name属性

2、然后在css中定义xxx-enter、xxx-enter-to等属性,其中xxx是v-show的name。

 

当v-show有显示隐藏等操作时,会自动加入动画执行,动画执行不影响v-show的最终显示。

如下案例:

<style>

.kkk-enter-active{

    transition: all 1s;

}

.kkk-enter{

    padding-left: 50px

}

/*如果不写下面的.kkk-enter-to样式,则文字会从右向左移动*/

/*默认的.kkk-enter-to样式就是显示后的最终状态*/

.kkk-enter-to{

    padding-left: 150px

}

 

</style>

 

<div id="test">

    <button @click="isShow=!isShow">切换</button>

    

    <transition name="kkk">

        <p v-show="isShow">一段测试文字</p>

    </transition>

</div>

 

<script src="vue.js"></script>

 

<script>

    new Vue({

         el: '#test',

         data: {

            isShow: true

        }

     })    

</script>

 

1、动画开始:文字处于padding-left:50px处, .kkk-enter的样式

 

2、然后动画开始,文字向右移动到padding-left:150px处,移动1s,由.kkk-enter-active决定。动画的最终位置由.kkk-enter-to决定。

 

3、动画完成后,文字显示到没有padding-left定义处,也就是最左边。动画不影响最终效果

Vue过滤器

相当于自定义一个函数,供view调用,用Vue.filter定义,第一个参数是过滤器名字(函数名),第二个参数是回调函数。回调函数中的第一个参数是需要过滤的变量,如:

<div id="test">

    默认显示: {{date}}    <br/>

    完整显示:{{date | dateFormat}}    <br/>

    显示年月日:{{date | dateFormat('YYYY-MM-DD')}}    <br/>

    显示时分秒:{{date | dateFormat('HH:mm:ss')}}    <br/>

</div>

 

<script src="vue.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.js"></script>

<script>

    Vue.filter('dateFormat', function(value, formatPattern){

            return moment().format(formatPattern || 'YYYY-MM-DD HH:mm:ss');

        })

    new Vue({

            el: '#test',

            data:{

                    date: new Date()

                }

        })    

</script>

 

这里定义了一个格式化日期时间输出的过滤器dateFormat,过滤器第一个参数就是需要过滤的变量,这里就是date,调用的时候用一个竖线隔开即可。

 

Vue插件

定义Vue插件就是对Vue进行扩展,比如定义一些Vue的全局方法、自定义指令、自定义过滤器、自定义Vue的实例方法等,将其单独定义到一个js中,成为插件。使用的时候引入该js,然后通过一个Vue.use(xxx)命令一次性将这些自定义的东西安装进来就能使用。

步骤:

1、编写插件文件my-vue-plugin.js:

var MyPlugin = {}

// install会在Vue的use方法执行时被回调

MyPlugin.install = function(Vue, options) {

 

    // install方法中对Vue进行扩展,自定义自己的Vue全局方法、自定义指令、自定义过滤器、自定义Vue的实例方法等

 

    // 自定义一个测试的VUE的函数对象的方法

    Vue.customGlobalMethod = function() {

        console.log('customGlobalMethod')

    }

    

    // 自定义一个测试的指令

    Vue.directive(

    'upper-text', function(el, binding) {

        el.textContent = binding.value.toUpperCase();

    })

    

    // 自定义一个Vue实例的方法(通过prototype得到原型对象,原型对象上定义的方法就是实例的方法)

    // Vue实例方法约定都以$开头,以区别于Vue的全局方法。

    Vue.prototype.$customObjectMethod = function() {

        console.log('$customObjectMethod')

    }

    // 将插件放到window里,以便使用者可以访问到。

    window.MyPlugin = MyPlugin

}

 

这里编写了一个名为MyPlugin的插件,自定义了一个全局方法customGlobalMethod,一个自定义指令upper-text,一个Vue实例方法$customObjectMethod。

 

2、使用插件,test8.html:

<div id="test">

    <!-- 1、使用插件的自定义指令upper-text -->

    <p v-upper-text="msg"></p>

</div>

 

<script src="vue.js"></script>

<!-- 1、引入插件的js -->

<script src="my-vue-plugin.js"></script>

 

<script>

    // 2、声明使用插件,它会回调插件的install方法

    Vue.use(MyPlugin)

    // 调用插件的全局方法

    Vue.customGlobalMethod()

    

    var vm = new Vue({

        el: '#test',

        data: {

            msg: 'Hello Ketty'

        }

    })

    // 调用插件的实例方法

    vm.$customObjectMethod()

</script>

 

效果:

 

基于组件化开发

实际中使用vue都是基于组件化进行开发,而不是单纯引入vue.js使用。

将一个页面分成不同的部分,每个部分都由一个根标签如div包裹起来,每个部分就是一个页面组件。

一个页面组件通常放到一个.vue文件里,然后由一个主页面将它们引入进来。组件里进行二次拆分成多个组件。每个组件对应一个单独的Vue实例,组件之间可以传递参数。

 

最后由脚手架工具vue-cli运行npm run build将页面组件进行构建,它会将组件最后构建成html+js+css页面,类似于maven的打包。

使用脚手架vue-cli搭建vue项目

npm安装

npm是集成在nodejs中的一个包管理工具,因此安装好nodejs后,npm命令就能运行了,类似于maven。

npm有一个中心服务器,各种第三方库都上传代码到那里,如jquery、vue-cli、grunt等,npm也有很多镜像,也可以配置镜像的地址下载库。如淘宝镜像,类似于maven的镜像仓库。

通过"npm install 包名字"可以下载库包,它会自动下载依赖库包,类似于maven。

 

vue测试项目的安装步骤

vue-cli是vue的脚手架,使用它可以构建vue的项目。

 

1、npm全局安装vue-cli脚手架

npm -g install vue-cli

 

2、使用脚手架初始化创建vue项目

vue init webpack vue-demo

初始化完成后如下:

其中webpack是一个选项

 

3、进入到vue-demo,启动项目:

cd vue-demo

npm run dev

默认8080端口。

 

4、访问测试:

 

vue项目结构及说明

项目结构说明:

main.js是入口js,它负责创建Vue实例,其他的组件文件都以.vue结尾,main.js引入App.vue组件,然后App.vue再引入其他组件,其他组件都放在components目录下。

使用webstorm打开脚手架创建的项目,有各种代码自动补全,错误提示等。

vue采用组件的方式进行模块划分,每个组件就是一个单独的文件,例如可以是一个子页面,有自己的html、js、css,多个组件拼装成一个完整页面。

 

在npm run dev时,脚手架会读取main.js内容,将其解析的vue内容填充到index.html对应的id元素下,访问时访问的是index.html如:

index.html:

 

main.js

 

这里的id是"app"。

 

import App表示引入App.vue组件,起别名为App,components属性又为App起别名为abc,最后template表示使用abc这个组件模板。

可以使用render简写:

render属性值是一个箭头函数,这个函数的内容是以App作为参数进行回调,等价于:

 

模块组件的定义,如:App.vue

分为三部分:html(对应template标签)、js(对应script标签)、css(对应style标签)

 

其中template可以理解为前面写测试功能时的div,只是这里不用指定id。template部分必须有一个根标签,否则会报错,这里是div。

 

js部分的export default表示该组件的返回值,这里是一个对象,对应到前面写测试时的new Vue。

只不过这里的data不能直接用大括号json,而要用一个函数返回json。、

 

style一般会加一个scoped属性,表示该style只应用于本vue,不影响引用它的vue。

 

组件的引用,如main.js

组件引用其实就是引用文件,import引用的时候指定文件路径,然后给被引用的组件起一个名字。

如:

import App from "./App.vue"表示引用了./App.vue这个文件,并且给它起了个名字叫App,这个名字也可以定义成其他的。

 

引入vue组件或者一些使用npm install安装好的vue插件时,不需要指定文件路径,如:

import Vue from 'vue'
import
Vuex from 'vuex'

 

components标签声明需要使用的组件,这里为它起了别名abc,如果不起别名而使用默认的,可以写成{App}即可。

 

AppVue组件引入HelloVue组件的例子

HelloVue.vue

 

 

App.vue

用标签即可使用被引用的组件,如<hello_vue/>

项目打包发布

运行npm run build即可打包项目,自动生成dist目录。

serve运行

安装serve插件可以运行dist

npm -g install serve

然后运行

serve dist

默认5000端口访问

 

tomcat运行

1、打包时需要修改一个配置:

修改webpack.prod.conf.js

output里加
publicPath: '/vue-demo/'

这里的vue-demo需要与tomcat的webapps下的工程目录名一致

 

 

2、然后运行npm run build打包,然后将dist放到tomcat的webapps下,重命名成vue-demo,启动tomcat即可访问。

案例1

效果:

 

对应静态html demo:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Title</title>

</head>

<body>

<!-- 对应Head.vue -->

<div>请发表对vue的评论</div>

 

<!-- 对应Add.vue -->

<div>

用户名:<input type="text"/>

评论内容:<input type="text"/>

<button>提交</button>

<br/><br/>

</div>

 

<!-- 对应List.vue -->

<div>

<div>评论回复:</div>

 

<!-- 对应Item.vue -->

<div>tom说:vue还不错<button>删除</button></div>

<div>jack说:vue还不错<button>删除</button></div>

<div>Richard说:Just so so<button>删除</button></div>

</div>

</body>

 

 

实现

main.js

import Vue from 'vue'
import
App from './App'

new
Vue({
el: '#app',
components: {App},
template: '<App/>'
})

 

App.vue

<template>
<div>
<Head/>
<Add :addItem="addItem"/>
<List :comments="comments" :deleteItem="deleteItem"/>
</div>
</template>

<script>
import Head from './components/Head'
import
Add from './components/Add'
import
List from './components/List'

export default
{
components: {Head, Add, List},
data: function () {
return {
comments: [
{name: 'tom', content: 'vue还不错'},
{name: 'jack', content: 'vue还不错'},
{name: 'Richard', content: 'Just so so'}
]
}
},
methods: {
addItem(comment) {
this.comments.unshift(comment)
},
deleteItem(index) {
if (window.confirm(`确定删除第${index + 1}条记录吗`)) {
this.comments.splice(index, 1)
}
}
}
}
</script>

<style>
</style>

 

其中:

1、引用组件的时候传递参数:

<List :comments="comments" :deleteItem="deleteItem"/>

标签名字一般跟参数名字同名

定义参数的位置一般选择几个vue都要用到该数据的父组件,如这里的comments就定义在App.vue中。
第一个参数comments是data的数据,第二个参数deleteItem是methods的方法。

2、一般数据在哪个组件,更新数据的方法就在哪个组件里定义。

 

3、`是es6新增的模板字符串表示,更简洁,如:

`确定删除第${index + 1}条记录吗`

如果用单引号需要写成

'确定删除第' + (index + 1) + '条记录吗'

List.vue

<template>
<div>
<div>评论回复:</div>
<div v-for="(comment, index) in comments"><Item :comment="comment" :deleteItem="deleteItem" :index="index"/></div>
</div>
</template>

<script>
import Item from './Item'
export default
{
props: ['comments', 'deleteItem'],
components: {Item}
}
</script>

<style>

</style>

 

List.vue引用了Item.vue,并且把deleteItem方法、index、comment对象传给了它,这里v-for会使该div产生多个。

props用于接收参数,接收到的参数相当于当前vue实例中也定义了。

接收参数的方式有几种:

1、最简单的方式:

props: ['comments']:

 

2、可选的配置方式:

props: {
// 2.1声明类型(中等复杂方式)
height: Number,
// 2.2声明类型 + 其他验证 + 设置默认值(最复杂方式)
age: {
type: Number,
default: 0,
required: true,
validator: function (value) {
return value >= 0
}
}
}

 

Item.vue

<template>
<div>{{comment.name}}说:{{comment.content}}<button @click="deleteItem(index)">删除</button></div>
</template>

<script>
export default {
props:['comment', 'deleteItem', 'index']
}
</script>

<style>

</style>

这里删除方法直接使用List传过来的deleteItem方法。

 

Add.vue

<template>
<div>
用户名:<input type="text" v-model="name"/>
评论内容:<input type="text" v-model="content"/>
<button @click="add(name, content)">提交</button>
<br/><br/>
</div>
</template>

<script>
export default {
props:['addItem'],
data: function() {
return {
name: '',
content: ''
}
},
methods: {
add(name, content) {
let comment = {}
comment.name = name
comment.content = content
this.addItem(comment)
}
}
}
</script>

<style>

</style>

Head.vue

<template>
<div>请发表对vue的评论</div>
</template>

<script>
export default {
name: "Head"
}
</script>

<style scoped>

</style>

 

案例2

效果:

对应静态html demo:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<!-- 对应Add.vue -->
<div>
<input type="text" placeholder="请输入代办任务,回车确认"/>
<br/><br/>
</div>

<!-- 对应List.vue -->
<ul>
<!-- 对应Item.vue -->
<li>
<input type="checkbox">吃饭<button>删除</button>
</li>
<li>
<input type="checkbox">睡觉<button>删除</button>
</li>
<li>
<input type="checkbox">coding<button>删除</button>
</li>
</ul>

<!-- 对应Footer.vue -->
<div>
<input type="checkbox">已完成0/全部3<button>清除已完成任务</button>
</div>
</body>

 

 

实现:

main.js:

import Vue from 'vue'
import
App from './App'

new
Vue({
el: "#app",
components: {App},
template: '<App/>'
})

 

App.vue:

<template>
<div>
<Add :todos="todos" :addTodo="addTodo"/>
<List :todos="todos" :deleteTodo="deleteTodo"/>
<Footer :todos="todos" :selectAll="selectAll" :deleteFinishedTodos="deleteFinishedTodos"/>
</div>
</template>

<script>
import List from './components/List'
import
Add from './components/Add'
import
Footer from './components/Footer'
export default
{
components:{List, Add, Footer},
data: function () {
return {
todos:[
{name:'吃饭', isFinished: false},
{name:'睡觉', isFinished: true},
{name:'coding', isFinished: false}
]
}
},
methods: {
// 新增
addTodo(todo) {
this.todos.unshift(todo)
},
// 删除
deleteTodo(index) {
this.todos.splice(index, 1)
},
// 清除已完成任务
deleteFinishedTodos() {
this.todos = this.todos.filter(todo => !todo.isFinished)
},
// 全选(全不选)
selectAll(isSelect) {
this.todos.forEach(todo => todo.isFinished = isSelect)
}
}
}
</script>

<style>

</style>

 

这里使用了数组的forEach方法箭头函数改变元素,可以触发view更新。

 

List.vue:

<template>
<ul>
<!-- 对应Item.vue -->
<Item v-for="(todo, index) in todos" :key="index" :todo="todo" :index="index" :deleteTodo="deleteTodo"/>
</ul>
</template>

<script>
import Item from './Item'
export default
{
components:{Item},
props:['todos', 'deleteTodo']
}
</script>

<style>

</style>

 

 

Add.vue

<template>
<div>
<input type="text" placeholder="请输入代办任务,回车确认" v-model="name" @keyup.enter="addTodoInAddVue"/>
<br/><br/>
</div>
</template>

<script>
export default {
props: ['todos', 'addTodo'],
data: function() {
return {
name: ''
}
},
methods: {
addTodoInAddVue() {
if (this.name.trim == '') {
alert('请输入任务内容')
return
}
let todo = {}
todo.name = this.name
todo.isFinished = false
this
.addTodo(todo)
this.name = ''
}
}
}
</script>

<style>

</style>

 

Item.vue:

<template>
<li @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)" :style="{'background-color': bgColor}">
<input type="checkbox" v-model="todo.isFinished">{{todo.name}}
<button v-show="isShowDeleteButton" @click="deleteTodo(index)">删除</button>
</li>
</template>

<script>
export default {
props:['todo', 'index', 'deleteTodo'],
data: function() {
return {
isShowDeleteButton: false,
bgColor: 'white'
}
},
methods: {
handleEnter(isShowDeleteButton) {
if (isShowDeleteButton) {
this.isShowDeleteButton = true
this
.bgColor = 'gray'
} else {
this.isShowDeleteButton = false
this
.bgColor = 'white'
}
}
}
}
</script>

<style>

</style>

这里使用了mouseenter、mouseleave,分别是进入移出元素时触发,而mouseover和mouseout也是进入移出元素触发,不过如果元素内有子元素,进入移出子元素时mouseover和mouseout也会被触发。

 

Footer.vue:

<template>
<div>
<input type="checkbox" v-model="isSelectedAll"/>
已完成{{finishedCount}}/全部{{todos.length}}
<button @click="deleteFinishedTodos">清除已完成任务</button>
</div>
</template>

<script>
export default {
props: ['todos', 'selectAll', 'deleteFinishedTodos'],
methods: {
// selectAllInFooter(isSelectedAll) {
// this.selectAll(isSelectedAll)
// }
},
// 统计完成数量
computed: {
finishedCount() {
// 传统方式计算
// let count = 0;
// for(let i = 0; i < this.todos.length; i++) {
// if (this.todos[i].isFinished) {
// count++
// }
// }
// return count
// 更好的方式,利用reduce计算
return this.todos.reduce((preTotal, todo) => preTotal + (todo.isFinished?1:0) ,0)
},
isSelectedAll: {
get() {
return this.finishedCount === this.todos.length && this.todos.length > 0
},
set(value) {
this.selectAll(value)
}
}
}
}

</script>

<style>

</style>

1、这里利用了数组的reduce方法统计。

2、checkbox绑定的v-model是选中与否的true和false,而不是checkbox的value值。

3、这里的全选框利用了计算属性的get和set方法。

 

深度监视和用localStorage存取


data: function () {
return {
// 从localStorage里取
todos: JSON.parse(window.localStorage.getItem("todos_key") || '[]')
}
},

watch: {
todos: {
// 深度监视
deep: true,
handler: function(value){
// 存储到localStorage里
window
.localStorage.setItem("todos_key", JSON.stringify(value))
}
}
},

这样在浏览器关闭或者刷新后,仍能保持数据。

其中用了深度监视,在被监视的数组中元素属性发生改变(这里是勾选状态)也能监视到。

 

自定义事件

组件间传递参数除了用自定义属性,还能用自定义事件的方式。

自定义事件只能用于父子之间传递参数,无法进行第三层传递,自定义属性则不限制传递层数。

标签内绑定(常用方式)

App.vue中


<!--<Add :todos="todos" :addTodo="addTodo"/>-->
<Add :todos="todos" @addTodo="addTodo"/>

 

自定义事件addtodo,

 

Add.vue中,使用$emit触发事件


// this.addTodo(todo)
this.$emit('addTodo', todo)

 

与自定义属性传参相比,自定义事件会少一点代码,不用写props。

$on绑定(不常用)

写代码绑定:

Add.vue中:


<Add :todos="todos" ref="addDom"/>

 


mounted() {
this.$refs.addDom.$on('addTodo', this.addTodo)
},

 

指定在mouted以后绑定,通过ref找到标签,然后用$on方法绑定,这种方式增加了代码量。

pubsub组件发布/订阅消息

组件间通信除了通过自定义属性、自定义事件,还能通过pubsub发布订阅消息。

使用pubsub可以减少很多代码量,且不用逐层传递参数,跨层直接订阅发布。

使用:

1、安装pubsub-js第三方组件

npm install --save pubsub-js

 

2、Item.vue中调用publish方法发布消息


<button v-show="isShowDeleteButton" @click="deleteTodoInItem(index)">删除</button>

 


// 引入pubsub-js
import PubSub from 'pubsub-js'

 


deleteTodoInItem(index) {
PubSub.publish('deleteIt', index)
}

 

第1个参数是消息名字,类似于mq的topic,第2个参数是业务参数

 

3、App.vue中调用subscribe方法订阅消息


// 引入pubsub-js
import PubSub from 'pubsub-js'

 


mounted() {
PubSub.subscribe('deleteIt', (msg, index) => {
this.deleteTodo(index)
})
}

一般在mouted时订阅

 

slot

slot插槽,用于父子组件通信传递标签时使用。在子组件中定义多个slot,父组件传递标签过来进行对应放置,用name进行对应。如果没有传则不显示,如:

App.vue:

<template>
<div>
App's Html
<br/>
<!-- 为Test组件传递3个标签,其中slot3没有传 -->
<Child>
<div slot="slot2">slot2's html</div>
<div slot="slot1">slot1's html</div>
<div slot="slot3">slot4's html</div>
</Child>
</div>
</template>

<script>
import Child from './components/Child'
export default
{
components: {Child},
name: 'App'
}
</script>

<style scoped>

</style>

 

 

Child.vue:

<template>
<div>
Test's html
<br/>

<!-- 子组件定义的4个插槽 -->
<slot name="slot1"></slot>
<hr/>
<slot name="slot2"></slot>
<hr/>
<slot name="slot3"></slot>
<hr/>
<slot name="slot4"></slot>
</div>
</template>

<script>
export default {
name: 'Test'
}
</script>

<style scoped>

</style>

 

 

效果:

 

创建和使用util工具包

可以将一些常用方法放到单独的js中,成为工具包,工具包可以暴露一个或多个函数供调用,如将存取todos放到工具包中:

创建util目录,下面创建storageUtil.js文件:

 

文件内容:

const TODOS_KEY = 'todos_key'
export default
{
// 存todos到localStorage
saveTodos(todos) {
window.localStorage.setItem(TODOS_KEY, JSON.stringify(todos))
},
// 从localStorage取todos
getTodos() {
return JSON.parse(window.localStorage.getItem(TODOS_KEY) || '[]')
}
}

 

App.vue中使用:


import storageUtil from './util/storageUtil'

 


data: function () {
return {
// 从localStorage里取
todos: storageUtil.getTodos()
}
},

 


watch: {
todos: {
// 深度监视
deep: true,
handler: function(value){
// 存储到localStorage里
storageUtil.saveTodos(value)
}
}
},

 

 

使用Ajax

使用axios(常用)

axios不需要用Vue.use方法安装,直接就能使用。

 

1、npm安装

npm install --save axios

 

2、使用:

App.vue:

<template>
<div>
<div v-if="!searchUrl">loading</div>
<div v-else>the most popular is <a :href="searchUrl">{{searchName}}</a></div>
</div>
</template>

<script>
// 引入axios组件
import axios from 'axios'
export default
{
mounted () {
// 发送ajax请求
const url = 'https://api.github.com/search/repositories?q=v&sort=stars'
axios.get(url).then(
// 成功回调函数
response => {
// response.data是返回数据
const result = response.data
const
mostPopularItem = result.items[0]
this.searchUrl = mostPopularItem.html_url
this.searchName = mostPopularItem.name

}).catch(error => {
alert('失败了')
})
},
data: function() {
return {
searchUrl: '',
searchName: ''
}
}
}
</script>

<style scoped>

</style>

 

使用vue-resource(已淘汰,vue1.0时使用)

1、npm安装

npm install --save vue-resource

 

2、main.js引入和安装

import Vue from 'vue'
import
App from './App'

// 引入vue-resource
import VueResource from 'vue-resource'

// 安装插件
Vue.use(VueResource)

new Vue({
el: '#app',
components: {App},
template: '<App/>'
})

 

3、App.vue使用

<template>
<div>
<div v-if="!searchUrl">loading</div>
<div v-else>the most popular is <a :href="searchUrl">{{searchName}}</a></div>
</div>
</template>

<script>
export default {
mounted () {
// 发送ajax请求
const url = 'https://api.github.com/search/repositories?q=v&sort=stars'
this
.$http.get(url).then(
// 成功回调函数
response => {
// response.data是返回数据
const result = response.data
const
mostPopularItem = result.items[0]
this.searchUrl = mostPopularItem.html_url
this.searchName = mostPopularItem.name

},
// 失败回调函数
response => {
alert('失败了')
}

)
},
data: function() {
return {
searchUrl: '',
searchName: ''
}
}
}
</script>

<style scoped>

</style>

vue-resource插件安装后会在vue实例是注册$http对象,它有get、post等方法。

 

效果:

 

案例(搜索)

需要实现的效果:

输入关键字搜索,到github模糊匹配用户名字搜索,显示用户名和照片。

 

实现:

利用Pubsub发布订阅的方式,Search组件中点搜索按钮时发布消息,Main组件接收消息,发送ajax请求,Search和Main是兄弟组件关系。

 

main.js:

import Vue from 'vue'
import
App from './App'

new
Vue({
el: '#app',
components: {App},
template: '<App/>'
})

 

 

App.vue

<template>
<div>
<Search/>
<UserMain/>
</div>
</template>

<script>
import Search from './components/Search'
import
Main from './components/Main'
export default
{
name: 'App',
components: {
Search,
// Main是关键字,不能用于标签,所以起别名UserMain
UserMain: Main
}
}
</script>

<style scoped>

</style>

 

Search.vue:

<template>
<div>
<input type="text" v-model="searchName">
<button @click="search">搜索</button>
</div>
</template>

<script>
import PubSub from 'pubsub-js'
export default
{
name: 'Search',
data() {
return {
searchName: ''
}
},
methods: {
search() {
// pubsub发送搜索消息
PubSub.publish('search', this.searchName)
}
}
}
</script>

<style scoped>

</style>

 

Main.vue

<template>
<div>
<!--搜索前-->
<div v-show="first">输入名称进行搜索</div>

<!--搜索中-->
<div v-show="isLoading">LOADING</div>

<!--搜索成功-列表-->
<div v-show="users">
<ul>
<li v-for="(user, index) in users">
{{user.name}}
<img :src="user.url" style="max-width: 100px;max-height: 100px;"/>
</li>
</ul>
</div>

<!--搜索失败-->
<div v-show="errorMsg">
{{errorMsg}}
</div>

</div>
</template>

<script>
import PubSub from 'pubsub-js'
import
axios from 'axios'

export default
{
name: 'Main',

data() {
return {
// 定义4种状态
first: true,
isLoading: false,
showList: false,
errorMsg: '',

users: null // {name: '', url: ''}
}
},

mounted () {
// 订阅消息
PubSub.subscribe("search", (msg, searchName) => {
this.first = false
this
.isLoading = true
this
.users = null
this
.errorMsg = ''
// 发送ajax请求搜索
const url = `https://api.github.com/search/users?q=${searchName}`
axios.get(url).then(
// 成功回调函数
response => {
// response.data是返回数据
const result = response.data
// 使用数组的map方法进行属性映射
const users = result.items.map(item => ({
url: item.avatar_url,
name: item.login
}))
this.users = users
// 成功更新状态
this.isLoading = false
}).catch(error => {
this.users = null
this
.isLoading = false
this
.errorMsg = error
})
})
}
}
</script>

<style scoped>

</style>

 

路由router

基本使用

路由类似于nginx,配置路径和页面的映射,只不过这里是配置路径和子组件的映射。

1、安装路由插件

npm install --save vue-router

2、定义路由组件:创建路由映射文件src/router/index.js

import Vue from 'vue'
import
VueRouter from 'vue-router'

import
About from '../views/About'
import
Home from '../views/Home'

Vue.use(VueRouter)

export default new VueRouter({
// n个路由
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home
},
// 主页,重定向到about
{
path: '/',
redirect: '/about'
}
]
})

这里创建了路由对象,配置了路由映射

3、注册路由:在main.js引入

import Vue from 'vue'
import
App from './App'

// 引入路由组件
import router from './router'

new
Vue({
el: '#app',
components: {App},
template: '<App/>',
router // 或者写成router: router
})

 

这里import router from './router',它默认会去找index.js,如果是其他名字如index2.js,则需要写成:import router from './router/index2'

4、编写子组件

About.vue

<template>
<div>about</div>
</template>

<script>
export default {
name: 'About'
}
</script>

<style scoped>

</style>

 

Home.vue

<template>
<div>home</div>
</template>

<script>
export default {
name: 'Home'
}
</script>

<style scoped>

</style>

 

5、父组件中使用路由

App.vue:

 

<template>
<div>
<div>
<router-link to="/about">to about</router-link>
<router-link to="/home">to home</router-link>
</div>

<div>
<router-view/>
</div>
</div>
</template>

<script>
export default {
name: 'App'
}
</script>

<style scoped>
/* 当前活跃的路由项链接为红色 */
.router-link-active{color:red;}
</style>

<router-link>和<router-view>标签配合使用,点击router-link链接时,会自动找到to属性的path对应的子组件,显示到router-view标签。

 

ps:

当前选择的路由链接会自动加上router-link-active这个class属性,可用于控制样式。这个class名字可以自定义,如:linkActiveClass: 'active123',自定义成了active123

 

效果:

6、路由的过程

路由路径可以看做访问url中带的一个参数,通过该参数去找路由配置中的path,找到以后取出对应的vue显示到<router-view/>标签所在的地方。

如果找不到,则不显示路由组件,但主页面也可以正常显示,如:

localhost:8080/#/abcdef这个abcdef路径没有配置,则找不到对应的vue,而页面也可以正常访问。

直接在url的#号后面接路由路径即可指定路由访问,因此router-link点击时相当于动态更新了#号后面的路由路径。

嵌套路由

使用children标签,如:


routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
// 定义home下面的子路由
children: [
{
path: 'news', // 也可以写成/home/news
component: News
},
{
path: 'message',
component: Message
},
{
path: '/',
redirect: 'news'
}
]
},
// 主页,重定向到about
{
path: '/',
redirect: '/about'
}
]

 

链接写成:

<router-link to="/home/news">to news</router-link>
<router-link to="/home/message">to message</router-link>

 

缓存路由组件


<keep-alive>
<router-view/>
</keep-alive>

默认路由组件会切换会销毁,使用keep-alive可以缓存起来,比如输入框中的数据可以保持下来。

 

案例(使用路径参数)

效果:

 

实现:

main.js:

import Vue from 'vue'
import
App from './App'

// 引入路由组件
import router from './router'

new
Vue({
el: '#app',
components: {App},
template: '<App/>',
router // 或者写成router: router
})

 

App.vue

<template>
<div>
<div>
一级菜单:
<router-link to="/about">to about</router-link>
<router-link to="/home">to home</router-link>
</div>

<div>
<keep-alive>
<router-view/>
</keep-alive>
</div>
</div>
</template>

<script>
export default {
name: 'App'
}
</script>

<style scoped>
/* 当前活跃的路由项链接为红色 */
.router-link-active{color:red;}
</style>

 

About.vue

<template>
<div>
about
<input type="text">
</div>

</template>

<script>
export default {
name: 'About'
}
</script>

<style scoped>

</style>

 

Home.vue

<template>
<div>
<div>
二级菜单:
<router-link to="/home/news">to news</router-link>
<router-link to="/home/message">to message</router-link>
</div>
<div>
<router-view/>
</div>
</div>
</template>

<script>
export default {
name: 'Home'
}
</script>

<style scoped>

</style>

 

News.vue

<template>
<ul>
<li v-for="(news, index) in newsArr" :key="index">{{news}}</li>
</ul>
</template>

<script>
export default {
name: 'News',
data() {
return {
newsArr: ['news1','news2','news3']
}
}
}
</script>

<style scoped>

</style>

 

Message.vue

<template>
<div>
<ul>
<li v-for="(message, index) in messages">
<router-link :to="`/home/message/messageDetail/${message.id}`">{{message.title}}</router-link>
</li>
</ul>
<router-view/>
</div>
</template>

<script>
export default {
name: 'News',
data() {
return {
messages: null
}
},
mounted () {
// 模拟从后台异步取数据,需要1秒钟
setTimeout
(() => {
this.messages = [
{id: 1, title: 'message1'},
{id: 2, title: 'message2'},
{id: 4, title: 'message4'}
]
}, 1000)
}
}
</script>

<style scoped>

</style>

其中使用`messageDetail/${message.id}`的es6语法定义带参数路径

 

MessageDetail.vue

<template>
<div>
<p>三级详情:</p>
<p>id:{{messageDetail.id}}</p>
<p>title:{{messageDetail.title}}</p>
<p>content:{{messageDetail.content}}</p>
</div>
</template>

<script>
const messageDetails = [
{id: 1, title: 'message1', content: 'content1'},
{id: 2, title: 'message2', content: 'content2'},
{id: 4, title: 'message4', content: 'content4'}
]
export default {
name: 'MessageDetail',
data() {
return {
messageDetail: {}
}
},
mounted () {
// 模拟从后台查询消息详情,需要1秒
setTimeout
(() => {
const id = this.$route.params.id * 1
this.messageDetail = messageDetails.find(messageDetailDb => messageDetailDb.id === id)
},500)
},
watch: {
$route() {
this.messageDetail = {}
// 模拟从后台查询消息详情,需要0.5秒
setTimeout
(() => {
const id = this.$route.params.id * 1
this.messageDetail = messageDetails.find(messageDetailDb => messageDetailDb.id === id)
},500)
}
}
}
</script>

<style scoped>

</style>

其中messageDetails.find(messageDetailDb => messageDetailDb.id === id)数组的find方法查询第一个符合条件的元素

$route.params可以取到路径参数,如果要取query参数,用$route.query

路由链接更新时不会重新调用mounted方法,因此需要监视$route。

this.$route.params.id * 1用字符串乘上数字1可以变成数字,三个等号会比较类型,如果用2个等号就不需要转换。

 

index.js

import Vue from 'vue'
import
VueRouter from 'vue-router'

import
About from '../views/About'
import
Home from '../views/Home'
import
News from '../views/News'
import
Message from '../views/Message'
import
MessageDetail from '../views/MessageDetail'

Vue.use(VueRouter)

export default new VueRouter({
// n个路由
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
// 定义home下面的子路由
children: [
{
path: 'news', // 也可以写成/home/news
component: News
},
{
path: 'message',
component: Message,
children: [
{
path: 'messageDetail/:id',
component: MessageDetail
}
]
},
{
path: '/',
redirect: 'news'
}
]
},
// 主页,重定向到about
{
path: '/',
redirect: '/about'
}
]
})

 

 

其中path: 'messageDetail/:id'用冒号的方式传递了路径参数

 

router-view用属性传参数

路由可以用属性传参数,如:


<router-view :msg="'abc'"/>

 

接收参数用props


export default {
name: 'About',
props: ['msg']
}

效果:

push、replace、back跳转

类似于页面的window.location.href跳转和window.location.replace跳转,vue也可以用js进行组件间跳转,也叫编程式路由导航,区别于router-link链接跳转。用法,如:
this.$router.push(`/home/message/messageDetail/1`)

this.$router.replace(`/home/message/messageDetail/2`)

this.$router.back()等价于this.$router.go(-1):返回上一页

this.$router.go(1):前进到下一页

路由提供了$route 和$router对象,前者是路由对象,可以用于取参数,后者是路由器对象,可以用于页面跳转。

push压栈当前页,replace替换当前页。

style样式的作用范围

vue的style标签如果没有加scoped则会影响包含了它的父vue组件,如果加了scoped则只会影响本身及子组件,因此除了最外层vue组件,其他的vue都最好加上scoped,在最外层vue上可以定义一些公共样式。

路由组件也是同理,不加scoped会影响父组件,最好都加上。

 

Vuex

使用传统方式实现一个计数功能

 

App.vue:

<template>
<div id="app">
clicked: {{count}} times, count is {{type}}
<p>
<button @click="increment">+</button>
<button @click="reduction">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>

</p>
</div>
</template>

<script>

export default {
name: 'App',
data() {
return {
count: 0
}
},
computed : {
type() {
if (this.count % 2 == 0) {
return "偶数"
} else {
return "奇数"
}
}
},
methods: {
// 加1
increment() {
this.count++;
},
// 减1
reduction() {
this.count--;
},
// 奇数时加1
incrementIfOdd() {
if (this.count % 2 == 1) {
this.count++;
}
},
// 延迟1秒加1
incrementAsync() {
setTimeout(() => {
this.count++;
}, 1000)
}
}
}
</script>

<style>
</style>

 

vuex介绍

vuex在线文档:

https://vuex.vuejs.org/zh-cn/

 

vuex是一个vue的插件,只不过没有遵循一般的插件命名方式vue-xxxxx。

vuex对应用中多个组件的共享状态进行集中式管理(读和写)

 

传统状态自管理应用

前面的应用都是"状态自管理应用",组成是:

1、state: 数据,对应上面的count,也叫驱动应用的数据源

2、 view:视图,对应上面示例的template模板部分,它以声明方式将 映射state数据,如{{count}}就是一个声明,它会自动找到count的值展示出来,不用手动去取值。

3、actions:事件,对应上面示例的increment(),reduction(),incrementIfOdd(),incrementAsync(),也叫响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)

 

状态自管理应用都是单向数据流,如上面的流程是:

view上面操作按钮 -> 调用actions事件 -> 更新state数据 -> 再刷新view显示。

 

状态自管理应用存在多组件共享状态的问题:

1) 多个视图依赖于同一状态

2) 来自不同视图的行为需要变更同一状态

3) 以前的解决办法 a. 将数据以及操作数据的行为都定义在父组件 b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递) 4) vuex 就是用来解决这个问题的

 

 

vuex的流程图:

 

多了一个Mutations,中文翻译是变异,在vuex中视图组件通过dispatch调用actions,但actions不直接更新state数据状态,而是提交给mutations去更新。

 

Backend API是服务器后台,通常在Actions中发送Ajax请求跟后台交互。

 

Devtools开发工具监视的是Mutations的调用。

vuex使用案例

安装vuex

npm install --save vuex

 

 

main.js中增加store的配置:

import Vue from 'vue'
import
App from './App'
import
store from './store'

Vue.config.productionTip = false

new
Vue({
el: '#app',
components: { App },
template: '<App/>',
store // 使用$store引用
})

 

这里引入了store.js,它是一个插件,将它定义成名称为store的变量,后面在App.vue中就可以用$store使用了。

编写store.js插件:

// 本文件中定义state、mutations、actions、getters
import Vue from 'vue'
import
Vuex from 'vuex'

Vue.use(Vuex)

// 定义state
const state = {
count: 0
}

// 定义mutations
const mutations = {
INCREMENT(state) {
state.count++
},
REDUCTION(state) {
state.count--
}
}

// 定义actions
const actions = {
increment ({commit}) {
commit('INCREMENT');
},
reduction ({commit}) {
commit('REDUCTION');
},
incrementIfOdd ({commit, state}) {
if (state.count % 2 == 1) {
commit('INCREMENT');
}
},
incrementAsync ({commit}) {
setTimeout(() => {
commit('INCREMENT');
}, 1000)
},
}

// 定义getters
const getters = {
type(state) {
if (state.count % 2 == 0) {
return "偶数"
} else {
return "奇数"
}
}
}

export default new Vuex.Store({
state,
mutations,
actions,
getters
})

 

store.js插件中定义了state、mutations、actions、getters,封装到Vuex.Store中返回。Vuex.Store是一个构造函数。在main.js中将这个插件返回对象命名成了store.

其中参数{commit}表示一个包含commit函数的对象,而不只是commit函数,该对象还可以有其他属性。
				

App.vue调整:

<template>
<div id="app">
clicked: {{$store.state.count}} times, count is {{type}}
<p>
<button @click="increment">+</button>
<button @click="reduction">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>

</p>
</div>
</template>

<script>

export default {
name: 'App',
computed : {
type() {
return this.$store.getters.type
}
},
methods: {
// 加1
increment() {
this.$store.dispatch('increment')
},
// 减1
reduction() {
this.$store.dispatch('reduction')
},
// 奇数时加1
incrementIfOdd() {
this.$store.dispatch('incrementIfOdd')
},
// 延迟1秒加1
incrementAsync() {
this.$store.dispatch('incrementAsync')
}
}
}
</script>

<style>
</style>

 

 

可以看出app.vue的data数据、competed计算属性、methods事件都放到了store.js,并且store.js中由actions调用mutations实现数据更新。

 

效果:

 

 

利用map映射简化编码:

<template>
<div id="app">
clicked: {{count}} times, count is {{type}}
<p>
<button @click="increment">+</button>
<button @click="reduction">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>

</p>
</div>
</template>

<script>

import {mapState, mapGetters, mapActions} from 'vuex'

export default
{
name: 'App',
computed : {
...mapState(['count']),
...mapGetters(['type'])
},
methods : {
...mapActions(['increment', 'reduction', 'incrementIfOdd', 'incrementAsync'])
}
}
</script>

<style>
</style>

 

默认约定的App.uve里面的事件名、属性名等跟store.js中的一样,如果不一样则需要映射(尽量不要写成不一样,约定大于配置)如:

store.js中配置:


// 定义getters
const getters = {
type2(state) {
if (state.count % 2 == 0) {
return "偶数"
} else {
return "奇数"
}
}
}

App.vue中调整:

computed : {
...mapState(['count']),
...mapGetters({type: "type2"})
},

Todos使用vuex改造案例

整体说明:

1、将各组件的共享变量todos交给vuex管理,而组件内部自己使用的变量不用交给vuex管理如Add.vue的name和Item.vue的isShowDeleteButton、bgColor。

 

2、将vuex的各模块单独建立文件state.js、mutations.js、actions.js、getters.js统一放到store目录下,然后用一个index.js引入它们,在main.js中用import store from './store'引入store,他会自动找到index.js引入。

 

3、使用mutation_types.js将mutations的方法名定义为常量

 

注意点:

1、List引用Item时传递的参数todo和index不要交给vuex管理,还是用以前传递参数的方式,因为它是迭代出来的多个变量。

 

2、Footer中的isSelectedAll计算属性由于有set监视方法,因此不能放到mapGetters列表中去声明,而要单独写在computed里。

 

3、方法中除了能默认传递state,还能传递getters,如:isSelectedAll(state, getters)

 

4、json定义中的key使用中括号括起来就表示是个变量,会取变量的值作为key,如mutations.js中的:[ADD_TODO],他会取mutation_types.js中配置的"add_todo"

项目结构:

 

main.js:

import Vue from 'vue'
import
App from './App'
import
store from './store'

new
Vue({
el: "#app",
components: {App},
template: '<App/>',
store
})

 

App.vue

<template>
<div>
<Add/>
<List/>
<Footer/>
</div>
</template>

<script>
import List from './components/List'
import
Add from './components/Add'
import
Footer from './components/Footer'
import
{mapActions} from 'vuex'
export default
{
components:{List, Add, Footer},
}
</script>

<style>

</style>

components/ Add.vue

<template>
<div>
<input type="text" placeholder="请输入代办任务,回车确认" v-model="name" @keyup.enter="addTodoInAddVue"/>
<br/><br/>
</div>
</template>

<script>
export default {
data() {
return {
name: ''
}
},
methods: {
addTodoInAddVue() {
if (this.name.trim == '') {
alert('请输入任务内容')
return
}
let todo = {}
todo.name = this.name
todo.isFinished = false
this
.$store.dispatch('addTodoInAddVue', todo)
this.name = ''
}
}
}
</script>

<style>

</style>

 

 

components/ Footer.vue

<template>
<div>
<input type="checkbox" v-model="isSelectedAll"/>
已完成{{finishedCount}}/全部{{$store.state.todos.length}}
<button @click="deleteFinishedTodos">清除已完成任务</button>
</div>
</template>

<script>
import {mapActions, mapGetters} from 'vuex'
export default
{
methods: {
...mapActions(['deleteFinishedTodos'])
},
// 统计完成数量
computed: {
...mapGetters(['finishedCount']),
isSelectedAll: {
get() {
return this.$store.getters.isSelectedAll;
},
set(value) {
return this.$store.dispatch('selectAll', value)
}
}
}
}

</script>

<style>

</style>

components/ Item.vue

<template>
<li @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)" :style="{'background-color': bgColor}">
<input type="checkbox" v-model="todo.isFinished">{{todo.name}}
<button v-show="isShowDeleteButton" @click="$store.dispatch('deleteTodo', index)">删除</button>
</li>
</template>

<script>
export default {
props:['todo', 'index'],
data: function() {
return {
isShowDeleteButton: false,
bgColor: 'white'
}
},
methods: {
handleEnter(isShowDeleteButton) {
if (isShowDeleteButton) {
this.isShowDeleteButton = true
this
.bgColor = 'gray'
} else {
this.isShowDeleteButton = false
this
.bgColor = 'white'
}
}
}
}
</script>

<style>

</style>

components/ List.vue

<template>
<ul>
<!-- 对应Item.vue -->
<Item v-for="(todo, index) in $store.state.todos" :key="index" :todo="todo" :index="index"/>
</ul>
</template>

<script>
import Item from './Item'
export default
{
components:{Item}
}
</script>

<style>

</style>

store/index.js

import Vue from 'vue'
import
Vuex from 'vuex'
Vue.use(Vuex)

import state from './state'
import
mutations from './mutations'
import
actions from './actions'
import
getters from './getters'

export default new
Vuex.Store({
state,mutations,actions,getters
})

 

 

store/actions.js

import {ADD_TODO, DELETE_TODO, DELETE_FINISHED_TODOS, SELECT_ALL} from './mutation_types'
export default
{

addTodoInAddVue({commit}, todo) {
commit(ADD_TODO, todo)
},

// 删除
deleteTodo({commit, state}, index) {
commit(DELETE_TODO, index)
},

// 清除已完成任务
deleteFinishedTodos({commit}) {
commit(DELETE_FINISHED_TODOS)
},
// 全选(全不选)
selectAll({commit}, isSelect) {
commit(SELECT_ALL, isSelect)
}
}

 

store/getters.js

export default {

finishedCount(state) {
return state.todos.reduce((preTotal, todo) => preTotal + (todo.isFinished?1:0) ,0)
},
isSelectedAll(state, getters) {
return getters.finishedCount === state.todos.length && state.todos.length > 0
}
}

store/mutation_types.js

export const ADD_TODO = "add_todo";
export const DELETE_TODO = "delete_todo";
export const DELETE_FINISHED_TODOS = "delete_finished_todos";
export const SELECT_ALL = "select_all";

 

store/ mutations.js

import {ADD_TODO, DELETE_TODO, DELETE_FINISHED_TODOS, SELECT_ALL} from './mutation_types'
export default
{
// 新增
[ADD_TODO] (state, todo) {
state.todos.unshift(todo)
},
// 删除
[DELETE_TODO](state, index) {
state.todos.splice(index, 1)
},
// 清除已完成任务
[DELETE_FINISHED_TODOS](state) {
state.todos = state.todos.filter(todo => !todo.isFinished)
},
// 全选(全不选)
[SELECT_ALL](state, isSelect) {
state.todos.forEach(todo => todo.isFinished = isSelect)
}

}

 

store/ state.js

export default {
todos:[
{name:'吃饭', isFinished: false},
{name:'睡觉', isFinished: true},
{name:'coding', isFinished: false}
]
}

模拟异步初始化Todos数据:

整体结构:

说明:

1、模拟项目启动时异步从服务器获取初始化数据,这里从localStorage获取。

 

2、使用watch深度监视时,key可以是mapState映射的属性。

 

3、在mounted时请求服务器数据,请求服务器数据的代码在actions中,请求成功后调用mutations初始化数据。

main.js:

同上

App.vue:

<template>
<div>
<Add/>
<List/>
<Footer/>
</div>
</template>

<script>
import List from './components/List'
import
Add from './components/Add'
import
Footer from './components/Footer'
import
storageUtil from './util/storageUtil'

import
{mapState} from 'vuex'

export default
{
components:{List, Add, Footer},
mounted() {
this.$store.dispatch("loadTodos");
},

computed: {
...mapState(['todos'])
},

watch: {
todos: {
// 深度监视
deep: true,
handler: function(value){
// 存储到localStorage里
storageUtil.saveTodos(value)
}
}
}

}
</script>

<style>

</style>

components

Add.vue、Footer.vue、Item.vue、List.vue同上

store/index.js

同上

store/actions.js

import {ADD_TODO, DELETE_TODO, DELETE_FINISHED_TODOS, SELECT_ALL, LOAD_TODOS} from './mutation_types'

import
storageUtil from '../util/storageUtil'
export default
{

addTodoInAddVue({commit}, todo) {
commit(ADD_TODO, todo)
},

// 删除
deleteTodo({commit, state}, index) {
commit(DELETE_TODO, index)
},

// 清除已完成任务
deleteFinishedTodos({commit}) {
commit(DELETE_FINISHED_TODOS)
},
// 全选(全不选)
selectAll({commit}, isSelect) {
commit(SELECT_ALL, isSelect)
},
// 初始化数据
loadTodos({commit}) {
// 模拟异步加载数据
setTimeout
(() => {
commit(LOAD_TODOS, storageUtil.getTodos())
}, 1000)
}
}

 

store/getters.js

同上

store/mutation_types.js

export const ADD_TODO = "add_todo";
export const DELETE_TODO = "delete_todo";
export const DELETE_FINISHED_TODOS = "delete_finished_todos";
export const SELECT_ALL = "select_all";
export const LOAD_TODOS = "load_todos";

store/ mutations.js

import {ADD_TODO, DELETE_TODO, DELETE_FINISHED_TODOS, SELECT_ALL, LOAD_TODOS} from './mutation_types'
export default
{
// 新增
[ADD_TODO] (state, todo) {
state.todos.unshift(todo)
},
// 删除
[DELETE_TODO](state, index) {
state.todos.splice(index, 1)
},
// 清除已完成任务
[DELETE_FINISHED_TODOS](state) {
state.todos = state.todos.filter(todo => !todo.isFinished)
},
// 全选(全不选)
[SELECT_ALL](state, isSelect) {
state.todos.forEach(todo => todo.isFinished = isSelect)
},
// 加载todos
[LOAD_TODOS](state, todos) {
state.todos = todos;
}
}

store/ state.js

export default {
todos:[]
}

 

 

 

util/ storageUtil.js

const TODOS_KEY = 'todos_key'
export default
{
// 存todos到localStorage
saveTodos(todos) {
window.localStorage.setItem(TODOS_KEY, JSON.stringify(todos))
},
// 从localStorage取todos
getTodos() {
return JSON.parse(window.localStorage.getItem(TODOS_KEY) || '[]')
}
}

posted @ 2021-01-27 19:15  吴克兢  阅读(324)  评论(0)    收藏  举报