=============================
H5 前端课程系列
=============================
Vue 全家桶
第 1 章:Vue 核心 1.1. Vue 的基本认识 1.1.1. 官网
英文官网: https://vuejs.org/
中文官网: https://cn.vuejs.org/
1.1.2. 介绍描述
渐进式 JavaScript 框架
作者: 尤雨溪(一位华裔前 Google 工程师)
作用: 动态构建用户界面
1.1.3. Vue 的特点
遵循 MVVM 模式
编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发
它本身只关注 UI, 可以轻松引入 vue 插件或其它第三库开发项目
1.1.4. 与其它前端 JS 框架的关联
借鉴 angular 的模板和数据绑定技术
借鉴 react 的组件化和虚拟 DOM 技术
1.1.5. Vue 扩展插件
vue-cli: vue 脚手架
vue-resource(axios): ajax 请求
vue-router: 路由
vuex: 状态管理
vue-lazyload: 图片懒加载
vue-scroller: 页面滑动相关
mint-ui: 基于 vue 的 UI 组件库(移动端)
element-ui: 基于 vue 的 UI 组件库(PC 端)
1.2. Vue 的基本使用 1.2.1. 效果 (01_HelloWorld/test.html) 01_helloworld.gif
1.2.2. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <div id="app"> <input type="text" v-model="username"> <p>Hello, {{username}}</p> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> new Vue({ el: '#app', data: { username: 'atguigu' } }) </script>
1.2.3. 使用 vue 开发者工具调试 1 2 Vue.js devtools_3.1.2_0.crx
1.2.4. 理解 Vue 的 MVVM 1.3. 模板语法 1.3.1. 效果 (02_模板语法/test.html) 02._模板语法.gif
插值语法是往标签体内写入内容的。
指令语法是往标签属性里面写入内容的。
1.3.2. 模板的理解
动态的 html 页面
包含了一些 JS 语法代码
a. 双大括号表达式
b. 指令(以 v-开头的自定义标签属性)
1.3.3. 双大括号表达式
语法:
功能: 向页面输出数据
可以调用对象的方法
1.3.4. 指令一: 强制数据绑定
功能: 指定变化的属性值
完整写法: v-bind:xxx=’yyy’ //yyy 会作为表达式解析执行
简洁写法: :xxx=’yyy’
1.3.5. 指令二: 绑定事件监听
功能: 绑定指定事件名的回调函数
完整写法:
1 2 v-on:keyup='xxx' v-on:keyup='xxx(参数)' v-on:keyup.enter='xxx' 3) 简洁写法: @keyup='xxx' @keyup.enter='xxx' 4
1.3.6. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <div id="app"> <h2>1. 双大括号表达式</h2> <p>{{msg}}</p> <p>{{msg.toUpperCase()}}</p> <h2>2. 指令一: 强制数据绑定</h2> <a href="url">访问指定站点</a><br><!--不能使用--> <a v-bind:href="url">访问指定站点 2</a><br> <a :href="url">访问指定站点 3</a><br> <h2>3. 指令二: 绑定事件监听</h2> <button v-on:click="handleClick">点我</button> <button @click="handleClick">点我 2</button> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> new Vue({ el: '#app', data: {// data 的所有属性都会成功 vm 对象的属性, 而模板页面中可以直接访问 msg: 'NBA I Love This Game!', url: 'http://www.baidu.com' }, methods: { handleClick () { alert('处理点击') } } }) </script>
单向数据绑定 v-bind, 数据只能从 data 流向页面。
1 2 3 双向数据绑定 v-model 只能用在表单元素上(输入类元素上,和用户产生交互的,元素有 value 属性的标签) 双向绑定一般都应用在表单元素上 v-model:value 可以简写 为 v-model,因为 v-model 默认收集的就是 value 的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <div id="root"> 单向数据绑定 <input type="text" v-bind:value="name"/><br/> 双向数据绑定 <input type="text" v-model:value="name"/><br/> <hr/> <!-- 下面是 简写的形式 --> 单向数据绑定 <input type="text" :value="name"/><br/> 双向数据绑定 <input type="text" v-model="name"/><br/> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { name: 'mage', age: 18, url:'http://www.atguigu.com' } }) </script>
el 的 2 种写法 vue 实例和容器绑定的 2 中写法,一种直接通过 el 指定,另外一种 通过 $mount 方法绑定。
1 2 3 4 5 6 7 8 9 10 const vm = new Vue({ // el: '#root', data: { name: 'mage', age: 18, url:'http://www.atguigu.com' } }) vm.$mount('#root')
data 的 2 种写法,写一个函数,此函数里面的 this 就是 vue 实例对象。函数返回一个对象。不能写成箭头函数。
1 2 3 4 5 6 7 8 9 10 const vm2 = new Vue({ // el: '#root', data: function (){ return { name: 'mage222', age: 181, url:'http://www.atguigu.com' } } })
vm 实例身上有的东西,都可以在模板插值里面使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <div id="root"> <h1>hello {{name}}</h1><br/> <h1>hello {{1+1}}</h1><br/> <h1>hello {{$options}}</h1><br/> <h1>hello {{$emit}}</h1><br/> <h1>hello {{_c}}</h1><br/> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { name: 'mage', age: 18, url:'http://www.atguigu.com' } }) </script>
Object.defineProperty 讲解, vue 中的数据代理 就是用的这个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let person = { name:"mage", sex:'nan' } Object.defineProperty(person, 'age', { value: 18, enumerable:true, // 控制属性是否可以枚举,就是是否可以遍历。默认false。 writable:true, // 控制属性是否可以被修改。默认false。 configurable:true // 控制属性是否可以被删除。 }) console.log(person)
getter 和 setter 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 let person = { name:"mage", sex:'nan' } let number = 180 Object.defineProperty(person, 'age', { // value: 18, // enumerable:true, // writable:true, // configurable:true, get() { return number }, set(v) { console.log("modify age: ", v) } }) console.log(person)
1.4. 计算属性和监视 1.4.1. 效果 (03_计算属性和监视/test.html) 03.._计算属性和监视.gif
1.4.2. 计算属性
在 computed 属性对象中定义计算属性的方法
在页面中使用来显示计算的结果
计算属性, get 和 set 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <div id="root"> <input type="text" v-model="firstName"/><br/> <input type="text" v-model="lastName"/><br/> <br/> <h1>hello {{firstName}} -- {{lastName.slice(0, 3)}}</h1><br/> <h1>hello {{fullName}} </h1><br/> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { firstName: 'mage', lastName: '123123', }, methods: {}, computed: { fullName:{ get(){ return this.lastName + ",,,," + this.firstName }, set(v){ this.lastName = v.slice(0,3) this.firstName = v.slice(3,6) } } } }) </script>
计算属性的简写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <div id="root"> <input type="text" v-model="firstName"/><br/> <input type="text" v-model="lastName"/><br/> <br/> <h1>hello {{firstName}} -- {{lastName.slice(0, 3)}}</h1><br/> <h1>hello {{fullName}} </h1><br/> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { firstName: 'mage', lastName: '123123', }, methods: {}, computed: { // 写成一个函数,这个函数就只能当做get函数使用了。 fullName: function () { return this.lastName + ",,,," + this.firstName } } computed: { // 下面是其还能继续简写的方式。 fullName() { return this.lastName + ",,,," + this.firstName } } }) </script>
1.4.3. 监视属性
通过通过 vm 对象的$watch()或 watch 配置来监视指定的属性
当属性变化时, 回调函数自动调用, 在函数内部进行计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <div id="root"> <br/> <h1>今天天气很:{{info}}</h1><br/> <button @click="change">switch</button> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { isHot: true, }, methods: { change() { this.isHot = !this.isHot } }, computed: { info() { return this.isHot ? '炎热' : '凉爽' } }, watch: { //当属性变化时, 回调函数自动调用, 在函数内部进行计算 isHot: { // 其实这里的 isHot 是简写方式,对象的key 应该是一个字符串的,要用引号 的 handler(newv, oldv) { console.log("ishot modify", newv, oldv) } } // 可以简写为下面的形式 isHot(newv, oldv) { console.log("ishot modify", newv, oldv) }, } }) </script>
上面检测的都是普通类型的属性, 如果检测一个复杂的呢?例如一个对象属性呢?检测多级结构中某个属性的变化的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { isHot: true, numbers: { a: 0, b: 0 } }, watch: { isHot: { handler(newv, oldv) { console.log("ishot modify", newv, oldv) } }, "numbers.a": { // 如果检测一个复杂的呢?例如一个对象属性呢? handler(newv, oldv) { console.log("numbers.a modify", newv, oldv) } } // 下面这样写 本意是想监控numbers 里面所有数据的,但是不会生效的。 // 如果整体重新替换掉 numbers 那么下面的这个监控会生效的。例如 // numbers = {c:11, d:222} // numbers: { // handler(newv, oldv) { // console.log("numbers modify", newv, oldv) // } // } // 要想彻底解决上面的问题,可以加一个 deep=true 属性 numbers: { deep: true, handler(newv, oldv) { console.log("numbers modify", newv, oldv) } } } })
vue 中的 watch 默认不监测对象内部值的改变(一层 ) 配置 deep:true 可以监测对象内部值改变(多层)
vue 自身可以监测对象内部的值的改变,但 vue 提供的 watch 默认不可以 使用 watch 时根据数据的具体结构,决定是否采用深度监测。
1.4.4. 计算属性高级
通过 getter/setter 实现对属性数据的显示和监视
计算属性存在缓存, 多次读取只执行一次 getter 计算
1.4.5. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <div id="demo"> 姓: <input type="text" placeholder="First Name" v-model="firstName"><br> 名: <input type="text" placeholder="Last Name" v-model="lastName"><br> 姓名 1(单向): <input type="text" placeholder="Full Name" v-model="fullName1"><br> 姓名 2(单向): <input type="text" placeholder="Full Name" v-model="fullName2"><br> 姓名 3(双向): <input type="text" placeholder="Full Name2" v-model="fullName3"><br> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> var vm = new Vue({ el: '#demo', data: { firstName: 'Kobe', lastName: 'bryant', fullName2: 'Kobe bryant' }, computed: { fullName: function () { return this.firstName + " " + this.lastName }, fullName3: { get: function () { return this.firstName + " " + this.lastName }, set: function (value) { var names = value.split(' ') this.firstName = names[0] this.lastName = names[1] } } }, watch: { lastName: function (newVal, oldVal) { this.fullName2 = this.firstName + ' ' + newVal } } }) vm.$watch('firstName', function (val) { this.fullName2 = val + ' ' + this.lastName })
1 2 3 4 5 6 7 8 9 10 methods,computed,watch 里面管理的函数都是交给 vue 管理的,里面的 this 都是指向 vue 的,不能使用箭头函数。 计算属性不能开启异步任务,监听属性可以开启异步任务。 computed 和 watch 之间的区别 computed 能完成的功能,watch 都可以完成 watch 能完成的功能,computed 不一定能完成,例如 watch 可以进行异步操作。 两个重要的小原则: 所有被 vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或者组件实例对象。 所有不被 vue 所管理的函数(定时器的回调函数,ajax 的回调函数,promis 的回调函数等)最好写成箭头函数,这样 this 的指向才是 vm 或者组件实例对象。
1.5. class 与 style 绑定 1.5.1. 效果 (04_class 与 style 绑定/test.html) 04_class 与 style 绑定.gif
1.5.2. 理解
在应用界面中, 某个(些)元素的样式是变化的
class/style 绑定就是专门用来实现动态样式效果的技术
1.5.3. class 绑定
:class=’xxx’ 绑定的样式会和元素静态的样式合并到一起
表达式是字符串: ‘classA’
表达式是对象: {classA:isA, classB: isB}
表达式是数组: [‘classA’, ‘classB’]
绑定 class 样式,字符串写法,适用于 样式的类名不确定,需要动态指定。mood 是 data 里面的一个属性,字符串类型的。
1 <div class="demo1" :class="mood" @click="changeMood">{{mood}}</div>
绑定 class 样式,数组写法,适用于 样式的类名不确定,个数也不确定。classArr 是 data 里面的一个数组。
1 2 3 4 5 6 <div class="demo1" :class="classArr" @click="changeMood">{{mood}}</div> classArr = ["atguigu1", "atguigu2", "atguigu3"] // 这里使用3个样式 classArr = ["atguigu1", "atguigu3"] // 这里使用2个样式
绑定 class 样式,对象写法,适用于 样式的类名确定,个数也确定,但要不要用不确定。classObj 是 data 里面的一个对象。
1 2 3 4 5 6 7 <div class="demo1" :class="classObj" @click="changeMood">{{mood}}</div> classObj:{ atguigu1:false, // 设置false就是不启用这个样式 atguigu2:true }
1.5.4. style 绑定
:style=”{ color: activeColor, fontSize: fontSize + ‘px’ }”
其中 activeColor/fontSize 是 data 属性
style 可以写成数组写法,数组里面的每个元素是个样式对象。
style 绑定的样式 要写成一个对象的形式。
1.5.5. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <style> .classA { color: red; } .classB { background: blue; } .classC { font-size: 20px; } </style> <div id="demo"> <h2>1. class 绑定: :class='xxx'</h2> <p class="classB" :class="a">表达式是字符串: 'classA'</p> <p :class="{classA: isA, classB: isB}">表达式是对象: {classA:isA, classB: isB}</p> <p :class="['classA', 'classC']"> 表达式是数组: ['classA', 'classB']</p> <h2>2. style 绑定</h2> <p :style="{color, fontSize}">style="{ color: activeColor, fontSize: fontSize + 'px' }"</p> <button @click="update">更新</button> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> new Vue({ el : '#demo', data : { a: 'classA', isA: true, isB: false, color: 'red', fontSize: '20px' }, methods : { update () { this.a = 'classC' this.isA = false this.isB = true this.color = 'blue' this.fontSize = '30px' } } }) </script>
1.6. 条件渲染 1.6.1. 效果 (05_条件渲染/test.html) 05._条件渲染.gif
1.6.2. 条件渲染指令
v-if 与 v-else v-else-if
v-show
v-show,如果是 flase 的话就好给这个标签的 style 加上个 display:none 属性。, 如果是 v-if 那么整个标签都会消失的。好像留个个 html 的注释的标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <div id="root"> <br/> <h1 v-show="isHot">今天天气很:{{isHot}}</h1><br/> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { isHot: true, }, }) </script>
template 的使用,只能和 v-if 下使用,v-show 无效。
1 2 3 4 5 6 <template v-if="n===3"> <h1>今天天气很:{{isHot}} 1</h1> <h1>今天天气很:{{isHot}} 2</h1> <h1>今天天气很:{{isHot}} else</h1><br/> </template>
1.6.3. 比较 v-if 与 v-show
如果需要频繁切换 v-show 较好
当条件不成立时, v-if 的所有子节点不会解析(项目中使用)
1.6.4. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <div id="demo"> <h2 v-if="ok">表白成功</h2> <h2 v-else>表白失败</h2> <h2 v-show="ok">求婚成功</h2> <h2 v-show="!ok">求婚失败</h2> <br> <button @click="ok=!ok">切换</button> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> var vm = new Vue({ el: '#demo', data: { ok: false } }) </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 v-if 写法: v-if="表达式" v-else-if="表达式" v-else 适用于: 切换频率低的场景。 特点,不展示的 DOM 元素直接被移除 注意:v-if 可以和 v-else-if 和 v-else 一起使用,但要求结构不能被打断。 v-show 写法: v-show="表达式" 适用于:切换频率较高的 场景 特点: 不展示的 DOM 元素未被移除,紧紧是用样式隐藏掉了。 备注,使用 v-if 时,元素可能无法获取到,而使用 v-show 一定可以获取到。
1.7. 列表渲染 1.7.1. 效果 (06_列表渲染/test.html) 06列表渲染.gif 06 列表的过滤和排序.gif
列表显示指令
数组: v-for / index
对象: v-for / key
列表的更新显示
删除 item
替换 item
列表的高级处理
列表过滤
列表排序
1.7.2. 编码 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <div id="demo"> <h2>测试: v-for 遍历数组</h2> <ul> <li v-for="(p, index) in persons" :key="index"> {{index}}--{{p.name}}--{{p.age}} -- <button @click="deleteItem(index)">删除</button> -- <button @click="updateItem(index, {name:'Jok',age:15})">更新</button> </li> </ul> <h2>测试: v-for 遍历对象</h2> <ul> <li v-for="(value, key) in persons[0]"> {{ key }} : {{ value }} </li> </ul> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> new Vue({ el: '#demo', data: { persons: [ {id: 1, name: 'Tom', age: 13}, {id: 2, name: 'Jack', age: 12}, {id: 3, name: 'Bob', age: 14} ] }, methods: { deleteItem(index) { this.persons.splice(index, 1) }, updateItem(index, p) { // this.persons[index] = p // 页面不会更新 this.persons.splice(index, 1, p) } } }) </script>
1.7.3. 编码 2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <div id="demo"> <input type="text" name="searchName" placeholder="搜索指定用户名" v-model="searchName"> <ul> <li v-for="(p, index) in filterPerson" :key="index"> {{index}}--{{p.name}}--{{p.age}} </li> </ul> <button @click="setOrderType(1)">年龄升序</button> <button @click="setOrderType(2)">年龄降序</button> <button @click="setOrderType(0)">原本顺序</button> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> new Vue({ el: '#demo', data: { orderType: 0, //0 代表不排序, 1 为升序, 2 为降序 searchName: '', persons: [ {id: 1, name: 'Tom', age: 13}, {id: 2, name: 'Jack', age: 12}, {id: 3, name: 'Bob', age: 17}, {id: 4, name: 'Cat', age: 14}, {id: 4, name: 'Mike', age: 14}, {id: 4, name: 'Monica', age: 16} ] }, methods: { setOrderType (orderType) { this.orderType = orderType } }, computed: { filterPerson() { let {orderType, searchName, persons} = this // 过滤 persons = persons.filter(p => p.name.indexOf(searchName)!=-1) // 排序 if(orderType!==0) { persons = persons.sort(function (p1, p2) { if(orderType===1) { return p1.age-p2.age } else { return p2.age-p1.age } }) } return persons } } }) </script>
1 2 3 4 5 6 7 8 9 10 v-for 指令 用于展示列表数据 语法 v-for="(item,index) in xxx" :key="yyy" 可以遍历: 数组,对象,字符串,指定次数。 动态的给 data 里面添加个 数据: vm.$set(target, key, val) 或者 Vue.set( target, propertyName/index, value ) 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi') 注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
1.8. 事件处理 1.8.1. 效果 (07_事件处理/test.html) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <div id="root"> <h1>hello {{name}}</h1><br/> <button v-on:click="showInfo">click me to show info</button> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { name: 'mage', age: 18, url: 'http://www.atguigu.com' }, methods: { showInfo(event) { // 被vue管理的函数不要写成箭头函数。 // 方法内 `this` 指向 vm console.log('Hello ' + this.name + '!') // `event` 是原生 DOM 事件 console.log(event.target.innerHTML) }, } }) </script>
1.8.2. 绑定监听: 1 2 3 4 5 1. v-on:xxx="fun" 2. @xxx="fun" 3. @xxx="fun(参数)" 4. 默认事件形参: event 5. 隐含属性对象: $event
1 2 3 4 5 1) 使用 v-on:click 或者 @click 绑定事件,其中 click 是事件名。 2) 事件的回调需要配置在 methods 对象中,最终会在 vm 上。 3) methods 中配置的函数,不要用箭头函数,否则 this 指向就不是 vm 了。 4) methods 中配置的函数,都是被 vue 所管理的函数,this 的指向是 vm,或者组件实例对象。 5) @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参。
1.8.3. 事件修饰符 1 2 3 4 5 6 1. .prevent : 阻止事件的默认行为 event.preventDefault() 2. .stop : 停止事件冒泡 event.stopPropagation() 3. .once 事件只触发一次,就是按钮你点击一次,再次点击就没事件触发了。就无效了。 4. .capture 使用事件的捕获 模式, 内外元素嵌套的时候,顺序是 事件 捕获阶段是 外到内。事件冒泡阶段是 内到外。这个模式就会让顺序变成 捕获阶段。默认是冒泡阶段的。 5. .self 只有 event.target 是当前操作的元素时才触发事件,self 修饰也能阻止冒泡,就是外层元素加上.self,事件冒泡上来的元素是内侧那个。 6. .passive 事件的默认行为立即执行,无需等待事件回调执行完毕。如果回调函数执行的很慢,加上这个 就不会等待回调执行完毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <div id="root"> <h1>hello {{name}}</h1><br/> <a v-on:click="showInfo" :href="url">click me to atguigu</a><br/> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { name: 'mage', age: 18, url: 'http://www.atguigu.com' }, methods: { showInfo(event) { event.preventDefault() 阻止事件的默认行为,a标签就不会跳转了, 这里不调用,直接标签那里 写成 v-on:click.prevent 或者简写 @click.prevent // 方法内 `this` 指向 vm console.log('Hello ' + this.name + '!') // `event` 是原生 DOM 事件 console.log(event.target.innerHTML) }, } }) </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <div id="root"> <h1>hello {{name}}</h1><br/> <div class="demo1" @click="showInfo2"> <button @click="showInfo1">click me to show info</button> </div> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { name: 'mage', age: 18, url: 'http://www.atguigu.com' }, methods: { showInfo1(event) { event.stopPropagation() 停止事件冒泡 event.stopPropagation(), 这里不调用,直接标签那里 写成 v-on:click.stop 或者简写 @click.stop, 另外一种阻止冒泡的方法是 外层的元素 加上 @click.self console.log('Hello111 ' + this.name + '!', event.target.innerHTML, event.target) }, showInfo2(event) { console.log('Hello222 ' + this.name + '!', event.target.innerHTML, event.target) }, } }) </script>
1.8.4. 按键修饰符
.keycode : 操作的是某个 keycode 值的键
.keyName : 操作的某个按键名的键(少部分)1 2 3 4 5 6 7 8 9 10 vue 中 常用的按键别名 回车 enter 删除 delete (捕获删除 和 退格 键) 退出 esc 空格 space 制表符 tab (特殊,必须配合 keydown 才好使) 上 up 下 down 左 left 右 right
vue 中未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转换为 短横线命名。
系统修饰键 ,ctrl, alt, shift,meta。 配合 keyup 使用,按下修饰键同时再按下其他键,随后释放其他键,事件才被触发。 配合 keydown 使用,正常触发事件
也可以使用 keycode 去指定具体的按键
Vue.config.keyCodes.自定义键名 = 键码 可以去定制按键别名
修饰符可以 点号 连续写
1 v-on:click.prevent.stop 阻止默认行为,然后阻止冒泡。
1 @keyup.ctrl.y 表示 按着 ctrl 键,然后再按 y 键。
1.8.5. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <div id="example"> <h2>1. 绑定监听</h2> <button v-on:click="test1">Greet</button> <button @click="test1">Greet2</button> <button @click="test2($event, 'hello')">Greet3</button> <h2>2. 事件修饰符</h2> <!-- 阻止事件默认行为 --> <a href="http://www.baidu.com" @click.prevent="test3">百度一下</a> <br/> <br/> <!-- 停止事件冒泡 --> <div style="width: 200px;height: 200px;background: red" @click="test4"> <div style="width: 100px;height: 100px;background: green" @click.stop="test5"></div> </div> <h2>3. 按键修饰符</h2> <input @keyup.8="test6"> <input @keyup.enter="test6"> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> new Vue({ el: '#example', data: { name: 'Vue.js' }, methods: { test1 (event) { // 方法内 `this` 指向 vm // alert('Hello ' + this.name + '!') // `event` 是原生 DOM 事件 alert(event.target.innerHTML) }, test2 (event, msg) { alert(event.target.innerHTML + '---' + msg) }, test3() { alert('阻止事件的默认行为') }, test4() { alert('out') }, test5() { alert('inner') }, test6(event) { alert(event.keyCode + '---' + event.target.value) } } }) </script>
1.9. 表单输入绑定 1.9.1. 效果 (08_表单输入绑定/test.html) 08._表单输入绑定.gif
1.9.2. 使用 v-model 对表单数据自动收集
text/textarea
checkbox
radio
select
1.9.3. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <div id="demo"> <form @submit.prevent="handleSubmit"> <span>用户名: </span> <input type="text" v-model="user.username"><br> <span>密码: </span> <input type="password" v-model="user.pwd"><br> <span>性别: </span> <input type="radio" id="female" value="female" v-model="user.sex"> <label for="female">女</label> <input type="radio" id="male" value="male" v-model="user.sex"> <label for="male">男</label><br> <span>爱好: </span> <input type="checkbox" id="basket" value="basketball" v-model="user.likes"> <label for="basket">篮球</label> <input type="checkbox" id="foot" value="football" v-model="user.likes"> <label for="foot">足球</label> <input type="checkbox" id="pingpang" value="pingpang" v-model="user.likes"> <label for="pingpang">乒乓</label><br> <span>城市: </span> <select v-model="user.cityId"> <option value="">未选择</option> <option v-for="city in allCitys" :value="city.id"> {{ city.name }} </option> </select><br> <span>介绍: </span> <textarea v-model="user.desc" rows="10"></textarea><br><br> <input type="submit" value="注册"> </form> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> var vm = new Vue({ el: '#demo', data: { user: { username: '', pwd: '', sex: 'female', likes: [], cityId: '', desc: '', }, allCitys: [{id: 1, name: 'BJ'}, {id: 2, name: 'SZ'},{id: 4, name: 'SH'}], }, methods: { handleSubmit (event) { alert(JSON.stringify(this.user)) } } }) </script>
1 2 3 4 5 6 7 收集表单数据 若 <input type="text"/> 则 v-model 收集的是 value 的值,用户输入的就是 value 的值。 若 <input type="radio"/> 则 v-model 收集的是 value 的值,且要给标签配置 value 值。 若 <input type="checkbox"/> 1. 没有配置 input 的 value 属性,那么收集的 就是 checked(勾选 or 未勾选,是布尔值) 2. 配置 input 的 value 属性 1.v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值) 2. v-model 的初始值是数组,那么收集的就是 value 组成的数组 备注:v-model 的三个修饰符 1.lazy 失去焦点的时候再收集数据,懒收集的意思。 2. number:输入字符串转为数字 3. trim: 删除前后空格
1.10. Vue 实例生命周期 1.10.1. 效果 (09Vue 实例 生命周期/test.html) 09Vue 实例 生命周期.gif
1.10.2. 生命周期流程图 1.10.3. vue 生命周期分析
初始化显示1 - beforeCreate() _ created() _ beforeMount() \* mounted()
更新状态:1 this.xxx = value _ beforeUpdate() _ updated()
销毁 vue 实例:1 vm.$destory() _ beforeDestory() _ destoryed()
1.10.4. 常用的生命周期方法
created()/mounted(): 发送 ajax 请求, 启动定时器等异步任务
beforeDestory(): 做收尾工作, 如: 清除定时器
1.10.5. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <div> <button @click="destoryVue">destory vue</button> <p v-show="isShowing">{{msg}}</p> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> var vue = new Vue({ el: 'div', data: { msg: '尚硅谷 IT 教育', isShowing: true, persons: [] }, beforeCreate () { console.log('beforeCreate() msg=' + this.msg) }, created () { console.log('created() msg='+this.msg) this.intervalId = setInterval(() => { console.log('-----') this.isShowing = !this.isShowing }, 1000) }, beforeMount () { console.log('beforeMount() msg='+this.msg) }, mounted () { console.log('mounted() msg='+this.msg) }, beforeUpdate() { console.log('beforeUpdate isShowing='+this.isShowing) }, updated () { console.log('updated isShowing='+this.isShowing) }, beforeDestroy () { console.log('beforeDestroy() msg='+this.msg) clearInterval(this.intervalId) }, destroyed () { console.log('destroyed() msg='+this.msg) }, methods: { destoryVue () { vue.$destroy() } } }) </script>
实现一个 一上来就显示一段 文本,然后慢慢变透明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <div id="root"> <h1 :style="{opacity: n}">xxxxxxxxxxxxxxxxxn=={{n}}</h1><br/> <template v-once>{{change()}}</template> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { n: 1, }, methods: { change() { methods 里面的方法 要么 事件 回调使用, 要么插值语法 主动调用,不能实现 以上来就开启一个定时器。 console.log("setInterval") setInterval(() => { this.n -= 0.01 if (this.n <= 0) { this.n = 1 } }, 10) } } }) </script>
1.11. 过渡&动画 1.11.1. 效果 (10_过渡&动画/test.html) 10过渡&动画 1.gif 10 过渡&动画 2.gif
1.11.2. vue 动画的理解 1 2 3 4 5 6 1. 操作 css 的 trasition 或 animation 2. vue 会给目标元素添加/移除特定的 class 3. 过渡的相关类名 xxx-enter-active: 指定显示的 transition xxx-leave-active: 指定隐藏的 transition xxx-enter/xxx-leave-to: 指定隐藏时的样式
1.11.3. 基本过渡动画的编码 1 2 3 4 1. 在目标元素外包裹<transition name="xxx"> 2. 定义 class 样式 指定过渡样式: transition 指定隐藏时的样式: opacity/其它
1.11.4. 编码 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <style> .fade-enter-active, .fade-leave-active { transition: opacity .5s } .fade-enter, .fade-leave-to { opacity: 0 } /* 可以设置不同的进入和离开动画 */ .slide-fade-enter-active { transition: all .3s ease; } .slide-fade-leave-active { transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0); } .slide-fade-enter, .slide-fade-leave-to { transform: translateX(10px); opacity: 0; } </style> <div id="demo1"> <button @click="show = !show"> Toggle1 </button> <transition name="fade"> <p v-if="show">hello</p> </transition> </div> <div id="demo2"> <button @click="show = !show"> Toggle2 </button> <transition name="slide-fade"> <p v-if="show">hello</p> </transition> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> new Vue({ el: '#demo1', data: { show: true } }) new Vue({ el: '#demo2', data: { show: true } }) </script>
1.11.5. 编码 2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <style> .bounce-enter-active { animation: bounce-in .5s; } .bounce-leave-active { animation: bounce-in .5s reverse; } @keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.5); } 100% { transform: scale(1); } } </style> <div id="test2"> <button @click="show = !show">Toggle show</button> <br> <transition name="bounce"> <p v-if="show" style="display: inline-block">Look at me!</p> </transition> </div> <script type="text/javascript" src="../js/vue.js"></script> <script> new Vue({ el: '#test2', data: { show: true } }) </script>
1.12. 过滤器 1.12.1. 效果 (11_过滤器/test.html) 1.12.2. 理解过滤器
功能: 对要显示的数据进行特定格式化后再显示
注意: 并没有改变原本的数据, 可是产生新的对应的数据
1.12.3. 定义和使用过滤器
定义过滤器
1 2 3 4 5 6 7 Vue.filter(filterName, function(value[,arg1,arg2,...]){ // 进行一定的数据处理 return newValue }) 2) 使用过滤器 <div>{{myData | filterName}}</div> <div>{{myData | filterName(arg)}}</div>
1.12.4. 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <div id="test"> <p>当前时间为: {{currentTime}}</p> <p>当前时间 1 为: {{currentTime | dateStr}}</p> <p>当前时间 2 为: {{currentTime | dateStr('YYYY-MM-DD')}}</p> <p>当前时间 3 为: {{currentTime | dateStr('HH:mm:ss')}}</p> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript" src="https://cdn.bootcss.com/moment.js/2.19.0/moment.js"></script> <script> // 注册过滤器 Vue.filter('dateStr', function (value, format) { return moment(value).format(format || 'YYYY-MM-DD HH:mm:ss') }) new Vue({ el: '#test', data: { currentTime: new Date() } }) </script>
1 2 3 4 过滤器 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑处理)。 语法: 1.注册过滤器 `Vue.filter(name, callback)` 或者 `new Vue({filters:{}})` 2. 使用过滤器 {{ xxx | 过滤器名 }} 或者 v-bind:属性 = "xxx | 过滤器" 备注: 1. 过滤器也可以接受额外参数,多个过滤器也可以串联 2. 并没有改变原本的数据,是产生新的对应的数据
1.13. 内置指令与自定义指令 1.13.1. 效果 (12_指令/test.html) 12指令 内置指令.gif
1.13.2. 常用内置指令
v:text : 更新元素的 textContent
v-html : 更新元素的 innerHTML
v-if : 如果为 true, 当前标签才会输出到页面
v-else: 如果为 false, 当前标签才会输出到页面
v-show : 通过控制 display 样式来控制显示/隐藏
v-for : 遍历数组/对象
v-on : 绑定事件监听, 一般简写为@
v-bind : 强制绑定解析表达式, 可以省略 v-bind
v-model : 双向数据绑定
ref : 指定唯一标识, vue 对象通过$els 属性访问这个元素对象
v-cloak : 防止闪现, 与 css 配合: [v-cloak] { display: none }
v-once: 所在节点在初次动态渲染后,就视为静态内容了,以后数据的改变不会引起v-once所在结构更新,可以用于优化性能。
v-pre: 跳过所在节点的编译过程,可以利用它跳过没有使用指令语法,没有使用插值语法的节点,会加快编译渲染。
1.13.3. 自定义指令
注册全局指令1 2 3 4 5 6 Vue.directive('my-directive', function(el, binding){ el.innerHTML = binding.value.toupperCase() }) ``` 2. 注册局部指令
directives : { ‘my-directive’ : { bind (el, binding) { el.innerHTML = binding.value.toupperCase() } } }
v-my-directive=’xxx’ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ### 1.13.4. 编码 1(内置指令) ``` <style> [v-cloak] { display: none } </style> <div id="example"> <p v-text="url"></p> <p v-html="url"></p> <img :id="myid" :src="imageSrc"> <p> <span ref="message">atguigu.com</span> <button @click="showMsg">显示左侧文本</button> </p> <p v-cloak>{{url}}</p> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> alert('模拟加载慢') new Vue({ el: '#example', data: { url: '<a href="http://www.atguigu.com">尚硅谷</a>', myid: 'abc123', imageSrc: 'http://cn.vuejs.org/images/logo.png' }, methods: { showMsg: function () { alert(this.$refs.message.textContent) } } }) </script>
1.13.5. 编码 2(自定义指令) 需求: 自定义 2 个指令
功能类型于 v-text, 但转换为全大写
功能类型于 v-text, 但转换为全小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <div id="demo1"> <p v-upper-text="msg"></p> <p v-lower-text="msg"></p> </div> <div id="demo2"> <p v-upper-text="msg2"></p> <p v-lower-text="msg2"></p> <!--局部指令, 此处不能使用--> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> //注册全局指令 Vue.directive('upper-text', function (el, binding) { el.innerHTML = binding.value.toUpperCase() }) new Vue({ el: '#demo1', data: { msg: 'NBA love this game!' }, directives: { // 注册局部指令 'lower-text': { bind (el, binding) { el.innerHTML = binding.value.toLowerCase() } } } }) new Vue({ el: '#demo2', data: { msg2: 'I Like You' } }) </script>
自定义指令 函数式写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 directives: { big函数何时会被调用, 指令与元素成功 绑定时,一上来。 指令所用的数据发生更新时。 big(el, binding, args) { console.log(el) console.log(binding) console.log(args) el.innerText = binding.value +100 } } el 参数是 标签本身 binding 参数比较重要: expression 是 标签上写的表达式 name 是 big 指令名 rawName 是 v-big value 是 值
自定义指令 对象方式写法, 函数式写法 其实 就是对 bind() 和 update() 的调用,因为这2个函数里面执行的内容通常是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 directives: { fbind: { 3个钩子方法, 后面vue的生命周期学习会具体讲解。 bind() { 首先调用这个, 指令与元素绑定时,一上来 时 }, inserted() { 然后调用这个,指令所在的元素被插入到页面时。 }, update() { 每次更新的时候调用这个,指令所在的模板被重新解析时。 } } }
所有指令中的 this 都是 window
1.14. 自定义插件 1.14.1. 效果 (13_插件/test.html) 13_插件.gif
1.14.2. 说明
Vue 插件是一个包含 install 方法的对象
通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等
1.14.3. 编码
插件 JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 (function () { const MyPlugin = {} MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或属性 Vue.myGlobalMethod = function () { alert('Vue 函数对象方法执行') } // 2. 添加全局资源 Vue.directive('my-directive', function (el, binding) { el.innerHTML = "MyPlugin my-directive " + binding.value }) // 3. 添加实例方法 Vue.prototype.$myMethod = function () { alert('vue 实例对象方法执行') } } window.MyPlugin = MyPlugin })()
页面使用插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <div id="demo"> <!--使用自定义指令--> <p v-my-directive="msg"></p> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript" src="vue-myPlugin.js"></script> <script type="text/javascript"> //使用自定义插件 Vue.use(MyPlugin) var vm = new Vue({ el: '#demo', data: { msg: 'atguigu' } }) //调用自定义的静态方法 Vue.myGlobalMethod() //调用自定义的对象方法 vm.$myMethod() </script>
第 2 章:vue 组件化编码 2.1. 使用 vue-cli 创建模板项目 2.1.1. 说明
vue-cli 是 vue 官方提供的脚手架工具
github: https://github.com/vuejs/vue-cli
作用: 从 https://github.com/vuejs-templates 下载模板项目 33
2.1.2. 创建 vue 项目 1 2 3 4 5 npm install -g vue-cli vue init webpack vue_demo cd vue_demo npm install npm run dev
访问: http://localhost:8080/
2.1.3. 模板项目的结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |-- build : webpack 相关的配置文件夹(基本不需要修改) |-- dev-server.js : 通过 express 启动后台服务器 |-- config: webpack 相关的配置文件夹(基本不需要修改) |-- index.js: 指定的后台服务的端口号和静态资源文件夹 |-- node_modules |-- src : 源码文件夹 |-- components: vue 组件及其相关资源文件夹 |-- App.vue: 应用根主组件 |-- main.js: 应用入口 js |-- static: 静态资源文件夹 |-- .babelrc: babel 的配置文件 |-- .eslintignore: eslint 检查忽略的配置 |-- .eslintrc.js: eslint 检查的配置 |-- .gitignore: git 版本管制忽略的配置 |-- index.html: 主页面文件 |-- package.json: 应用包配置文件 |-- README.md: 应用描述说明的 readme 文件
2.1.4. 效果 2.2. 项目的打包与发布 2.2.1. 打包:
2.2.2. 发布 1: 使用静态服务器工具包 1 2 npm install -g serve serve dist
访问: http://localhost:5000
2.2.3. 发布 2: 使用动态 web 服务器(tomcat) 1 2 3 4 5 6 7 8 9 修改配置: webpack.prod.conf.js output: { publicPath: '/xxx/' //打包文件夹的名称 } 重新打包: npm run build 修改 dist 文件夹为项目名称: xxx 将 xxx 拷贝到运行的 tomcat 的 webapps 目录下 访问: http://localhost:8080/xxx
2.3. eslint 2.3.1. 说明
ESLint 是一个代码规范检查工具
它定义了很多特定的规则, 一旦你的代码违背了某一规则, eslint 会作出非常有用的提示
官网: http://eslint.org/
基本已替代以前的 JSLint
2.3.2. ESLint 提供以下支持
ES
JSX
style 检查
自定义错误和提示
2.3.3. ESLint 提供以下几种校验
语法错误校验
不重要或丢失的标点符号,如分号
没法运行到的代码块(使用过 WebStorm 的童鞋应该了解)
未被使用的参数提醒
确保样式的统一规则,如 sass 或者 less
检查变量的命名
2.3.4. 规则的错误等级有三种
0:关闭规则。
1:打开规则,并且作为一个警告(信息打印黄色字体)
2:打开规则,并且作为一个错误(信息打印红色字体)
2.3.5. 相关配置文件 1 2 3 4 5 6 7 8 9 10 11 1. .eslintrc.js : 全局规则配置文件 'rules': { 'no-new': 1 } 2. 在 js/vue 文件中修改局部规则 /_ eslint-disable no-new _/ new Vue({ el: 'body', components: { App } }) 3. .eslintignore: 指令检查忽略的文件 _.js _.vue
2.4. 组件定义与使用 2.4.1. vue 文件的组成(3 个部分) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. 模板页面 <template> 页面模板 </template> 2. JS 模块对象 <script> export default { data() {return {}}, methods: {}, computed: {}, components: {} } </script> 3. 样式 <style> 样式定义 </style>
2.4.2. 基本使用
引入组件
映射成标签
使用组件标签1 2 3 4 5 6 7 8 9 10 11 <template> <HelloWorld></HelloWorld> <hello-world></hello-world> </template> <script> import HelloWorld from './components/HelloWorld' export default { components: { HelloWorld } } </script>
2.4.3. 关于标签名与标签属性名书写问题
写法一: 一模一样
写法二: 大写变小写, 并用-连接
2.5. 组件间通信 2.5.1. 组件间通信基本原则
不要在子组件中直接修改父组件的状态数据
数据在哪, 更新数据的行为(函数)就应该定义在哪
2.5.2. vue 组件间通信方式
props
vue 的自定义事件
消息订阅与发布(如: pubsub 库)
slot
vuex(后面单独讲)
2.6. 组件间通信 1: props 2.6.1. 使用组件标签时
2.6.2. 定义 MyComponent 时
在组件内声明所有的 props
方式一: 只指定名称1 props: ['name', 'age', 'setName']
方式二: 指定名称和类型1 2 3 props: { name: String, age: Number, setNmae: Function }
方式三: 指定名称/类型/必要性/默认值1 2 props: { name: {type: String, required: true, default:xxx}, }
2.6.3. 注意
此方式用于父组件向子组件传递数据
所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
问题: a. 如果需要向非子后代传递数据必须多层逐层传递 b. 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以
2.7. 组件间通信 2: vue 自定义事件 2.7.1. 绑定事件监听 // 方式一: 通过 v-on 绑定
1 @delete_todo="deleteTodo"
// 方式二: 通过$on()
1 2 3 this.$refs.xxx.$on('delete_todo', function (todo) { this.deleteTodo(todo) })
2.7.2. 触发事件 1 2 // 触发事件(只能在父组件中接收) this.$emit(eventName, data)
2.7.3. 注意:
此方式只用于子组件向父组件发送消息(数据)
问题: 隔代组件或兄弟组件间通信此种方式不合适
2.8. 组件间通信 3: 消息订阅与发布(PubSubJS 库) 2.8.1. 订阅消息 1 PubSub.subscribe('msg', function(msg, data){})
2.8.2. 发布消息 1 PubSub.publish('msg', data)
2.8.3. 注意
优点: 此方式可实现任意关系组件间通信(数据)
2.8.4. 事件的 2 个重要操作(总结) 1 2 3 4 5 6 7 1. 绑定事件监听 (订阅消息) 目标: 标签元素 <button> 事件名(类型): click/focus 回调函数: function(event){} 2. 触发事件 (发布消息) DOM 事件: 用户在浏览器上对应的界面上做对应的操作 自定义: 编码手动触发
2.9. 组件间通信 4: slot 2.9.1. 理解 此方式用于父组件向子组件传递标签数据
2.9.2. 子组件: Child.vue
1 2 3 4 5 6 7 8 <template> <div> <slot name="xxx">不确定的标签结构 1</slot> <div>组件确定的标签结构</div> <slot name="yyy">不确定的标签结构 2</slot> </div> </template>
2.9.3. 父组件: Parent.vue 1 2 3 4 <child> <div slot="xxx">xxx 对应的标签结构</div> <div slot="yyy">yyyy 对应的标签结构</div> </child>
demo1_comment manage.gif
2.11. demo2: todo list demo2_todolist.gif
第 3 章:vue-ajax 3.1. vue 项目中常用的 2 个 ajax 库 3.1.1. vue-resource vue 插件, 非官方库, vue1.x 使用广泛
3.1.2. axios 通用的 ajax 请求库, 官方推荐, vue2.x 使用广泛
3.2. vue-resource 的使用 3.2.1. 在线文档 https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
3.2.2. 下载 1 npm install vue-resource --save
3.2.3. 编码 1 2 3 4 5 6 7 8 9 10 11 // 引入模块 import VueResource from 'vue-resource' // 使用插件 Vue.use(VueResource) // 通过 vue/组件对象发送 ajax 请求 this.$http.get('/someUrl').then((response) => { // success callback console.log(response.data) //返回结果数据 }, (response) => { // error callback console.log(response.statusText) //错误信息 })
3.3. axios 的使用 3.3.1. 效果 ajax_test.gif
3.2. 在线文档 https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
3.3. 下载: 1 npm install axios --save
3.4. 编码 1 2 3 4 5 6 7 8 9 // 引入模块 import axios from 'axios' // 发送 ajax 请求 axios.get(url) .then(response => { console.log(response.data) // 得到返回结果数据 }) .catch(error => { console.log(error.message) })
3.4. 测试接口 1 2 接口 1: https://api.github.com/search/repositories?q=v&sort=stars 接口 2: https://api.github.com/search/users?q=aa
3.5. demo3: github users demo3_usersearch.gif
第 4 章:vue UI 组件库 4.1. 常用
Mint UI:
a. 主页: http://mint-ui.github.io/#!/zh-cn
b. 说明: 饿了么开源的基于 vue 的移动端 UI 组件库
Elment
a. 主页: http://element-cn.eleme.io/#/zh-CN
b. 说明: 饿了么开源的基于 vue 的 PC 端 UI 组件库
4.2. 使用 Mint UI 4.2.1. 下载: 1 npm install --save mint-ui
4.2.2. 实现按需打包
下载
1 npm install --save-dev babel-plugin-component
修改 babel 配置
1 2 3 4 5 6 "plugins": ["transform-runtime",["component", [ { "libraryName": "mint-ui", "style": true } ]]]
4.2.3. mint-ui 组件分类
标签组件
非标签组件
4.2.4. 使用 mint-ui 的组件
index.html1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></scrip t> <script> if ('addEventListener' in document) { document.addEventListener('DOMContentLoaded', function() { FastClick.attach(document.body); }, false); } if(!window.Promise) { document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js" '+'>'+'<'+'/'+'script>'); } </script>
main.js1 2 import {Button} from 'mint-ui' Vue.component(Button.name, Button)
App.vue1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <mt-button @click="handleClick" type="primary" style="width: 100%">Test</mt-button> </template> <script> import {Toast} from 'mint-ui' export default { methods: { handleClick () { Toast('点击了测试'); } } } </script>
第 5 章:vue-router 5.1. 理解 5.1.1. 说明
官方提供的用来实现 SPA 的 vue 插件
github: https://github.com/vuejs/vue-router
中文文档: http://router.vuejs.org/zh-cn/
下载: npm install vue-router –save
5.1.2. 相关 API 说明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 1. VueRouter(): 用于创建路由器的构建函数 new VueRouter({ // 多个配置项 }) 2. 路由配置 routes: [ { // 一般路由 path: '/about', component: About },{ // 自动跳转路由 path: '/', redirect: '/about' } ] 3. 注册路由器 import router from './router' new Vue({ router }) 4. 使用路由组件标签 1) <router-link>: 用来生成路由链接 <router-link to="/xxx">Go to XXX</router-link> 2) <router-view>: 用来显示当前路由组件界面 <router-view></router-view>
5.2. 基本路由 5.2.1. 效果 基本路由.gif
5.2.2. 路由组件
5.2.3. 应用组件: App.vue 1 2 3 4 5 6 7 <div> <!--路由链接--> <router-link to="/about">About</router-link> <router-link to="/home">Home</router-link> <!--用于渲染当前路由组件--> <router-view></router-view> </div>
5.2.4. 路由器模块: src/router/index.js 1 2 3 4 5 6 7 8 9 10 export default new VueRouter({ routes: [ { path: '/', redirect: '/about' },{ path: '/about', component: About },{ path: '/home', component: Home } ] })
5.2.5. 注册路由器: main.js 1 2 3 4 import Vue from 'vue' import router from './router' // 创建 vue 配置路由器 new Vue({ el: '#app', router, render: h => h(app) })
5.2.6. 优化路由器配置 1 linkActiveClass: 'active', // 指定选中的路由链接的 class
5.2.7. 总结: 编写使用路由的 3 步
定义路由组件
注册路由
使用路由1 2 <router-link> <router-view>
5.3. 嵌套路由 5.3.1. 效果 嵌套路由.gif
5.3.2. 子路由组件
5.3.3. 配置嵌套路由: router.js 1 2 3 4 5 6 7 path: '/home', component: home, children: [ { path: 'news', component: News },{ path: 'message', component: Message } ]
5.3.4. 路由链接: Home.vue 1 2 3 <router-link to="/home/news">News</router-link> <router-link to="/home/message">Message</router-link> <router-view></route-view>
5.4. 向路由组件传递数据 5.4.1. 效果 向路由组件传递数据.gif
5.4.2. 方式 1: 路由路径携带参数(param/query)
配置路由
1 2 3 4 5 children: [ { path: 'mdetail/:id', component: MessageDetail } ]
路由路径
1 <router-link :to="'/home/message/mdetail/'+m.id">{{m.title}}</router-link>
路由组件中读取请求参数
5.4.3. 方式 2: 属性携带数据 1 <router-view :msg="msg"></router-view>
5.5. 缓存路由组件对象 5.5.1. 理解
默认情况下, 被切换的路由组件对象会死亡释放, 再次回来时是重新创建的
如果可以缓存路由组件对象, 可以提高用户体验
5.5.2. 编码实现 1 2 3 <keep-alive> <router-view></router-view> </keep-alive>
5.6. 编程式路由导航 5.6.1. 效果 编程导航.gif
5.6.2. 相关 API
this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
this.$router.back(): 请求(返回)上一个记录路由
this.$router.go(-1): 请求(返回)上一个记录路由
this.$router.go(1): 请求下一个记录路由
第 6 章:vuex 6.1. vuex 理解 6.1.1. vuex 是什么
github 站点: https://github.com/vuejs/vuex
在线文档: https://vuex.vuejs.org/zh-cn/
简单来说: 对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)
6.1.2. 状态自管理应用
state: 驱动应用的数据源
view: 以声明方式将 state 映射到视图
actions: 响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)
6.1.3. 多组件共享状态的问题
多个视图依赖于同一状态
来自不同视图的行为需要变更同一状态
以前的解决办法 a. 将数据以及操作数据的行为都定义在父组件 b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
vuex 就是用来解决这个问题的
6.2. vuex 核心概念和 API 6.2.1. state
vuex 管理的状态对象
它应该是唯一的1 2 3 const state = { xxx: initValue }
6.2.2. mutations
包含多个直接更新 state 的方法(回调函数)的对象
谁来触发: action 中的 commit(‘mutation 名称’)
只能包含同步的代码, 不能写异步代码1 2 3 4 5 const mutations = { yyy (state, {data1}) { // 更新 state 的某个属性 } }
6.2.3. actions
包含多个事件回调函数的对象
通过执行: commit()来触发 mutation 的调用, 间接更新 state
谁来触发: 组件中: $store.dispatch(‘action 名称’, data1) // ‘zzz’ 4) 可以包含异步代码(定时器, ajax)1 2 3 4 5 const actions = { zzz ({commit, state}, data1) { commit('yyy', {data1}) } }
6.2.4. getters
包含多个计算属性(get)的对象
谁来读取: 组件中: $store.getters.xxx1 2 3 4 5 6 const getters = { mmm (state) { return ... 55 } }
6.2.5. modules
包含多个 module
一个 module 是一个 store 的配置对象
与一个组件(包含有共享数据)对应
6.2.6. 向外暴露 store 对象 1 2 3 export default new Vuex.Store({ state, mutations, actions, getters })
6.2.7. 组件中 1 2 3 4 5 6 7 8 9 import {mapState, mapGetters, mapActions} from 'vuex' export default { computed: { ...mapState(['xxx']), ...mapGetters(['mmm']), } methods: mapActions(['zzz']) } {{xxx}} {{mmm}} @click="zzz(data)" 6.2.8. 映射 store import store from './store' new Vue({ store })
6.2.9. store 对象
所有用 vuex 管理的组件中都多了一个属性$store, 它就是一个 store 对象
属性:
1 2 state: 注册的 state 对象 getters: 注册的 getters 对象
方法:
1 dispatch(actionName, data): 分发调用 action
6.3. demo1: 计数器 counter.gif
6.3.1. store.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 /\*\* - vuex 的 store 对象模块 _/ import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) /_ state 对象 类似于 data _/ const state = { count: 0 // 初始化状态数据 } /_ mutations 对象 包含个方法: 能直接更新 state 一个方法就是一个 mutation mutation 只能包含更新 state 的同步代码, 也不会有逻辑 mutation 由 action 触发调用: commit('mutationName') _/ const mutations = { INCREMENT(state) { state.count++ }, DECREMENT (state) { // ctrl + shift + x state.count-- } } /_ actions 对象 包含个方法: 触发 mutation 调用, 间接更新 state 一个方法就是一个 action action 中可以有逻辑代码和异步代码 action 由组件来触发调用: this.$store.dispatch('actionName') \*/ const actions = { increment ({commit}) { commit('INCREMENT') }, decrement ({commit}) { commit('DECREMENT') }, incrementIfOdd ({commit, state}) { if(state.count%2===1) { commit('INCREMENT') } }, incrementAsync ({commit}) { setTimeout(() => { commit('INCREMENT') }, 1000) } } /_ getters 对象 包含多个 get 计算计算属性方法 _/ const getters = { oddOrEven (state) { return state.count%2===0 ? '偶数' : '奇数' }, count (state) { return state.count } } // 向外暴露 store 实例对象 export default new Vuex.Store({ state, mutations, actions, getters })
6.3.2. main.js 1 2 3 4 5 6 7 8 9 import Vue from 'vue' import app from './app1.vue' // import app from './app.vue' import store from './store' new Vue({ el: '#app', render: h => h(app), store // 所有组件都多个一个属性: $store })
6.3.3. app.vue(未优化前) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <template> <div> <p>clicked: {{$store.state.count}} times, count is {{oddOrEven}}</p> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementIfOdd">increment if odd</button> <button @click="incrementAsync">increment async</button> </div> </template> <script> export default { computed: { oddOrEven () { return this.$store.getters.oddOrEven } }, methods: { increment () { this.$store.dispatch('increment') }, decrement () { this.$store.dispatch('decrement') }, incrementIfOdd () { this.$store.dispatch('incrementIfOdd') }, incrementAsync () { this.$store.dispatch('incrementAsync') } } } </script> <style> </style>
6.3.4. app2.vue(优化后) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> <p>clicked: {{count}} times, count is {{oddOrEven2}}</p> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementIfOdd">increment if odd</button> <button @click="incrementAsync">increment async</button> </div> </template> <script> import {mapGetters, mapActions} from 'vuex' export default { computed: mapGetters({ // 名称不一样 oddOrEven2: 'oddOrEven', count: 'count' }), methods: mapActions(['increment', 'decrement', 'incrementIfOdd', 'incrementAsync']) // 名称一样 } </script> <style> </style>
6.4. demo2: todo list todo list.gif
6.3.1. store/types.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /** * 包含多个 mutation name */ export const RECEIVE_TODOS = 'receive_todos' export const ADD_TODO = 'add_todo' export const REMOVE_TODO = 'remove_todo' export const DELETE_DONE = 'delete_done' export const UPDATE_ALL_TODOS = 'update_all_todos' 6.3.2. store/mutations.js import {RECEIVE_TODOS, ADD_TODO, REMOVE_TODO, DELETE_DONE, UPDATE_ALL_TODOS} from './types' export default { [RECEIVE_TODOS] (state, {todos}) { state.todos = todos }, [ADD_TODO] (state, {todo}) { state.todos.unshift(todo) }, [REMOVE_TODO] (state, {index}) { state.todos.splice(index, 1) }, [DELETE_DONE] (state) { state.todos = state.todos.filter(todo => !todo.complete) }, [UPDATE_ALL_TODOS] (state, {isCheck}) { state.todos.forEach(todo => todo.complete = isCheck) } }
6.3.3. store/actions.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import storageUtil from '../util/storageUtil' import {RECEIVE_TODOS, ADD_TODO, REMOVE_TODO, DELETE_DONE, UPDATE_ALL_TODOS} from './types' export default { readTodo ({commit}) { setTimeout(() => { const todos = storageUtil.fetch() // 提交 commit 触发 mutation 调用 commit(RECEIVE_TODOS, {todos}) }, 1000) }, addTodo ({commit}, todo) { commit(ADD_TODO, {todo}) }, removeTodo ({commit}, index) { commit(REMOVE_TODO, {index}) }, deleteDone ({commit}) { commit(DELETE_DONE) }, updateAllTodos ({commit}, isCheck) { commit(UPDATE_ALL_TODOS, {isCheck}) } }
6.3.4. store/getters.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export default { todos (state) { return state.todos }, totalSize (state) { return state.todos.length }, completeSize (state) { return state.todos.reduce((preTotal, todo) => { return preTotal + (todo.complete ? 1 : 0) }, 0) }, isAllComplete (state, getters) { return getters.totalSize===getters.completeSize && getters.totalSize>0 } }
6.3.5. store/index.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import Vue from 'vue' import Vuex from 'vuex' import mutations from './mutations' import actions from './actions' import getters from './getters' Vue.use(Vuex) const state = { todos: [] } export default new Vuex.Store({ state, mutations, actions, getters })
6.3.6. components/app.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div class="todo-container"> <div class="todo-wrap"> <todo-header></todo-header> <todo-main></todo-main> <todo-footer></todo-footer> </div> </div> </template> <script> import todoHeader from './todoHeader.vue' import todoMain from './todoMain.vue' import todoFooter from './todoFooter.vue' import storageUtil from '../util/storageUtil' export default { created () { // 模拟异步读取数据 this.$store.dispatch('readTodo') }, components: { todoHeader, todoMain, todoFooter } } </script> <style> .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="addItem"/> </div> </template> <script type="text/ecmascript-6"> export default { data () { return { title: null } }, methods: { addItem () { const title = this.title && this.title.trim() if (title) { const todo = { title, complete: false } this.$store.dispatch('addTodo', todo) this.title = null } } } } </script> <style> .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
6.3.8. components/todoMain.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <template> <ul class="todo-main"> <todo-item v-for="(todo, index) in todos" :todo="todo" :key="index" :index="index"></todo-item> </ul> </template> <script type="text/ecmascript-6"> import todoItem from './todoItem' import storageUtil from '../util/storageUtil' export default { components: { todoItem }, computed: { todos () { return this.$store.getters.todos } }, watch: { todos: {// 深度监视 todos, 一旦有变化立即保存 handler: storageUtil.save, deep: true } }, } </script> <style> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
6.3.9. components/todoItem.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <template> <li :style="{background: libg}" @mouseenter="handleStyle(true)" @mouseleave="handleStyle(false)"> <label> <input type="checkbox" v-model="todo.complete"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" v-show="isShown" @click="deleteItem">删除</button> </li> </template> <script type="text/ecmascript-6"> export default { props: ['todo', 'index'], data () { return { isShown: false, libg: '#fff' } }, methods: { handleStyle (isEnter) { if (isEnter) { this.isShown = true this.libg = '#ddd' } else { this.isShown = false this.libg = '#fff' } }, deleteItem () { const {todo, deleteTodo, index} = this if (window.confirm(`确定删除${todo.title}的评论吗?`)) { // deleteTodo(index) this.$store.dispatch('removeTodo', index) } } } } </script> <style> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <template> <div class="todo-footer"> <label> <input type="checkbox" v-model="isAllDone"/> </label> <span> <span>已完成{{completeSize}}</span> / 全部{{totalSize}} </span> <button class="btn btn-danger" @click="deleteDone" v-show="completeSize>0">清除已 完成任务</button> </div> </template> <script> import {mapGetters, mapActions} from 'vuex' export default { methods: mapActions(['deleteDone']), computed: { isAllDone: { get () { return this.$store.getters.isAllComplete }, set (value) { //this.$emit('updateTodos', value) this.$store.dispatch('updateAllTodos', value) } }, ...mapGetters(['completeSize', 'totalSize']) } } /_ const arr1 = [1, 3, 5] const arr2 = [4, ...arr1, 7] const obj = { a: 1, b () { } } const obj2 = { c: 3, ...obj }_/ </script> <style> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; 71 cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
6.3.11. util/storageUtil.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 var STORAGE_KEY = 'todos' export default { fetch () { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') }, save (todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) } } 6.3.12. base.css body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; }
6.3.13. main.js 1 2 3 4 5 6 7 8 9 10 11 import Vue from 'vue' import App from './components/app' import store from './store' import './base.css' /_ eslint-disable no-new _/ new Vue({ el: '#app', render: h => h(App), store })
6.5. vuex 结构分析 第 7 章:vue 源码分析 7.1. 说明
分析 vue 作为一个 MVVM 框架的基本实现原理 数据代理 模板解析 数据绑定
不直接看 vue.js 的源码
剖析 github 上某基友仿 vue 实现的 mvvm 库
地址: https://github.com/DMQ/mvvm
7.2. 准备知识 1 2 3 4 5 6 7 8 9 10 11 12 1. [].slice.call(lis): 将伪数组转换为真数组 2. node.nodeType: 得到节点类型 3. Object.defineProperty(obj, propName, {}): 给对象添加/修改属性(指定描述符) configurable: true/false 是否可以重新 define enumerable: true/false 是否可以枚举(for..in / keys()) value: 指定初始值 writable: true/false value 是否可以修改 get: 回调函数, 用来得到当前属性值 set: 回调函数, 用来监视当前属性值的变化 4. Object.keys(obj): 得到对象自身可枚举的属性名的数组 5. DocumentFragment: 文档碎片(高效批量更新多个节点) 6. obj.hasOwnProperty(prop): 判断 prop 是否是 obj 自身的属性
7.3. 数据代理 1 2 3 4 5 6 7 1. 数据代理: 通过一个对象代理对另一个对象(在前一个对象内部)中属性的操作(读/写) 2. vue 数据代理: 通过 vm 对象来代理 data 对象中所有属性的操作 3. 好处: 更方便的操作 data 中的数据 4. 基本实现流程 a. 通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符 b. 所有添加的属性都包含 getter/setter c. getter/setter 内部去操作 data 中对应的属性数据
7.4. 模板解析 7.4.1. 模板解析的基本流程
将 el 的所有子节点取出, 添加到一个新建的文档 fragment 对象中
对 fragment 中的所有层次子节点递归进行编译解析处理
对大括号表达式文本节点进行解析
对元素节点的指令属性进行解析
事件指令解析
一般指令解析
将解析后的 fragment 添加到 el 中显示
7.4.2. 模板解析(1): 大括号表达式解析
根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1 name
从 data 中取出表达式对应的属性值
将属性值设置为文本节点的 textContent
7.4.3. 模板解析(2): 事件指令解析
从指令名中取出事件名
根据指令的值(表达式)从 methods 中得到对应的事件处理函数对象
给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
指令解析完后, 移除此指令属性
7.4.4. 模板解析(3): 一般指令解析
得到指令名和指令值(表达式) text/html/class msg/myClass
从 data 中根据表达式得到对应的值
根据指令名确定需要操作元素节点的什么属性
v-text—textContent 属性
v-html—innerHTML 属性
v-class–className 属性
将得到的表达式的值设置到对应的属性上
移除元素的指令属性
7.5. 数据绑定 7.5.1. 数据绑定 一旦更新了 data 中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会 更新
7.5.2. 数据劫持
数据劫持是 vue 中用来实现数据绑定的一种技术
基本思想: 通过 defineProperty()来监视 data 中所有属性(任意层次)数据的变化, 一旦变 化就去更新界面
7.5.3. 四个重要对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 1. Observer a. 用来对 data 所有属性数据进行劫持的构造函数 b. 给 data 中所有属性重新定义属性描述(get/set) c. 为 data 中的每个属性创建对应的 dep 对象 2. Dep(Depend) a. data 中的每个属性(所有层次)都对应一个 dep 对象 b. 创建的时机: \* 在初始化 define data 中各个属性时创建对应的 dep 对象 - 在 data 中的某个属性值被设置为新的对象时 c. 对象的结构 { id, // 每个 dep 都有一个唯一的 id subs //包含 n 个对应 watcher 的数组(subscribes 的简写) } d. subs 属性说明 - 当 watcher 被创建时, 内部将当前 watcher 对象添加到对应的 dep 对象的 subs 中 - 当此 data 属性的值发生改变时, subs 中所有的 watcher 都会收到更新的通知, 77 从而最终更新对应的界面 3) Compiler a. 用来解析模板页面的对象的构造函数(一个实例) b. 利用 compile 对象解析模板页面 c. 每解析一个表达式(非事件指令)都会创建一个对应的 watcher 对象, 并建立 watcher 与 dep 的关系 d. complie 与 watcher 关系: 一对多的关系 4) Watcher a. 模板中每个非事件指令或表达式都对应一个 watcher 对象 b. 监视当前表达式数据的变化 c. 创建的时机: 在初始化编译模板时 d. 对象的组成 { vm, //vm 对象 exp, //对应指令的表达式 cb, //当表达式所对应的数据发生改变的回调函数 value, //表达式当前的值 depIds //表达式中各级属性所对应的 dep 对象的集合对象 //属性名为 dep 的 id, 属性值为 dep } 5) 总结: dep 与 watcher 的关系: 多对多 a. data 中的一个属性对应一个 dep, 一个 dep 中可能包含多个 watcher(模板中有几个 表达式使用到了同一个属性) b. 模板中一个非事件表达式对应一个 watcher, 一个 watcher 中可能包含多个 dep(表 达式是多层: a.b) c. 数据绑定使用到 2 个核心技术 - defineProperty() \* 消息订阅与发布
7.6. MVVM 原理图分析 7.7. 双向数据绑定
双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
双向数据绑定的实现流程:
a. 在解析 v-model 指令时, 给当前元素添加 input 监听
b. 当 input 的 value 发生改变时, 将最新的值赋值给当前表达式所对应的 data 属性