一、Vue 介绍
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
1.1 MVVM
二、安装Vue
2.1 使用CDN引入
对于制作原型或学习,你可以这样使用最新版本:
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏:
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
如果你使用原生 ES Modules,这里也有一个兼容 ES Module 的构建文件:
<script type="module">
import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js'
</script>
你可以在 cdn.jsdelivr.net/npm/vue 浏览 NPM 包的源代码。
Vue 也可以在 unpkg 和 cdnjs 上获取 (cdnjs 的版本更新可能略滞后)。
请确认了解不同构建版本并在你发布的站点中使用生产环境版本,把 vue.js
换成 vue.min.js
。这是一个更小的构建,可以带来比开发环境下更快的速度体验。
2.2 下载引入
2.3 NPM
三、Vue 使用入门
3.1 Vue中的options
-
el:
类型:string | HTMLElement 作用:决定之后Vue实例会管理哪一个DOM
-
data:
类型:Object | Function (组件当中data必须是一个函数) 作用:Vue实例对应的数据对象。
-
methods:
类型:{ [key: string]: Function } 作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用。
-
computed
计算属性,实现data中一些数据的整理运算,详细看3.4小节
-
filters
过滤器,书写与methods类似,使用时在变量后面加 ‘|’ ,Vue会把前面的值传入到后面的过滤器中 <h3>Total Price: {{totalPrice | showTwoDecimal}} 元</h3> // 实现价格保留2位小数 const app = new Vue({ data:{ totalPrice:58 }, filters:{ showTwoDecimal(price){ return '¥' + price.toFixed(2) } } })
3.2 Vue 生命周期
3.3 指令
1. 文本内容插值操作
-
mustache(双大括号)
-
简单的表达式操作(拼接、倍乘、除法、减法)
<!--mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式--> <h2>{{firstName + lastName}}</h2> <h2>{{firstName + ' ' + lastName}}</h2> <h2>{{firstName}} {{lastName}}</h2> <h2>{{counter * 2}}</h2> <script src="../js/vue.js"></script> data: { firstName: 'kobe', lastName: 'bryant', counter: 100 }
-
-
v-once
-
只渲染第一次后,不受data中的数据影响
<h2 v-once>{{message}}</h2>
-
-
v-html
-
可以对data中的数据,在携带有标签时进行html渲染,不会单独的显示字符串内容。
<h2 v-html="baidu">{{baidu}}</h2> data:{ baidu:'<a href="www.baidu.com">百度</a>' }
-
-
v-text
-
只会显示便是指定的值后面的值会进行覆盖
<h2 v-text="message">, 李银河!</h2>
-
-
v-pre
-
不进行vue的渲染,原封不动的显示标签中字符串内容
<h2 v-pre>{{message}}</h2>
-
-
c-cloak(cloak:斗篷,解析前斗篷存在,解析后斗篷摘掉了)
-
在vue解析前,div中v-cloak属性会存在
-
在vue解析后,div中v-cloak属性会被删除
# 开始时没有解析vue前,网页文本内容为空(display:none) # 1秒后解析了vue,v-cloak属性去掉了,故style样式就不生效了。网页出现内容 <style> [v-cloak] { display: none; } </style> <h2>{{message}}</h2> <script> // 在vue解析之前, div中有一个属性v-cloak // 在vue解析之后, div中没有一个属性v-cloak setTimeout(function () { const app = new Vue({ el: '#app', data: { message: '你好啊' } }) }, 1000) </script>
-
2. 标签属性动态绑定操作
-
v-bind
-
动态绑定标签的属性(基本使用)
<img v-bind:src="imgUrl"> data:{ imgUrl:"www.baidu.com" }
# 语法糖写法 <img :src="imgUrl"> data:{ imgUrl:"www.baidu.com" }
-
动态绑定class(对象语法)
# class 调用的是一个对象 <!--<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>--> <h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2> <button v-on:click="btnClick">按钮</button> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', isActive: true, isLine: true }, methods: { btnClick: function () { this.isActive = !this.isActive }, // 此处为函数调用 getClasses: function () { return {active: this.isActive, line: this.isLine} } } }) </script>
-
动态绑定class(函数语法)
# class 调用的是一个函数 <h2 class="title" v-bind:class="getClasses()">{{message}}</h2> methods: { getClasses: function () { return {active: this.isActive, line: this.isLine} } }
-
动态绑定class(数组语法)-- 使用较少
<h2 class="title" :class="[active, line]">{{message}}</h2> <h2 class="title" :class="getClasses()">{{message}}</h2> const app = new Vue({ el: '#app', data: { message: '你好啊', active: 'aaaaaa', line: 'bbbbbbb' }, methods: { getClasses: function () { return [this.active, this.line] } } })
-
动态绑定style
<h2 :style="{fontSize: finalSize + 'px', backgroundColor: finalColor}">{{message}}</h2> // 对象调用style <h2 :style="getStyles()">{{message}}</h2> // 函数调用style <script> const app = new Vue({ el: '#app', data: { message: '你好啊', finalSize: 100, finalColor: 'red', }, methods: { // 函数调用style getStyles: function () { return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor} } } }) </script>
-
-
v-on
-
事件监听,比如点击,拖拽,键盘事件等
情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
作用:绑定事件监听器 缩写:@ (语法糖) 预期:Function | Inline Statement | Object 参数:event
-
基础使用
<button @click="increment">+</button> <button @click="decrement">-</button> methods: { increment() { this.counter++ }, decrement() { this.counter-- } }
-
带参数使用
<div id="app"> <!--1.事件调用的方法没有参数--> <button @click="btn1Click()">按钮1</button> <button @click="btn1Click">按钮1</button> <!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event事件对象作为参数传入到方法--> <!--<button @click="btn2Click(123)">按钮2</button>--> <!--<button @click="btn2Click()">按钮2</button>--> <button @click="btn2Click">按钮2</button> <!--3.方法定义时, 我们需要event对象, 同时又需要其他参数--> <!-- 在调用方式, 如何手动的获取到浏览器参数的event对象: $event--> <button @click="btn3Click(abc, $event)">按钮3</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', abc: 123 }, methods: { btn1Click() { console.log("btn1Click"); }, btn2Click(event) { console.log('--------', event); }, btn3Click(abc, event) { console.log('++++++++', abc, event); } } }) // 如果函数需要参数,但是没有传入, 那么函数的形参为undefined // function abc(name) { // console.log(name); // } // // abc() </script>
-
v-on
修饰符@click.stop
阻止冒泡@click.prevent
把一些浏览器自动处理时修改为手动处理(例如form表单)@click.enter
监听某个键盘的键帽@click.once
只监听一次<div id="app"> <!--1. .stop修饰符的使用--> <div @click="divClick"> aaaaaaa <button @click.stop="btnClick">按钮</button> </div> <!--2. .prevent修饰符的使用--> <br> <form action="baidu"> <input type="submit" value="提交" @click.prevent="submitClick"> </form> <!--3. .监听某个键盘的键帽--> <input type="text" @keyup.enter="keyUp"> <!--4. .once修饰符的使用--> <button @click.once="btn2Click">按钮2</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, methods: { btnClick() { console.log("btnClick"); }, divClick() { console.log("divClick"); }, submitClick() { console.log('submitClick'); }, keyUp() { console.log('keyUp'); }, btn2Click() { console.log('btn2Click'); } } }) </script>
-
-
v-show
v-show
与v-if
的区别` v-show` 当条件为false时, v-show只是给我们的元素添加一个行内样式: display: none `v-if `当条件为false时, 包含v-if指令的元素, 根本就不会存在dom中
<div id="app"> <!--v-if: 当条件为false时, 包含v-if指令的元素, 根本就不会存在dom中--> <h2 v-if="isShow" id="aaa">{{message}}</h2> <!--v-show: 当条件为false时, v-show只是给我们的元素添加一个行内样式: display: none--> <h2 v-show="isShow" id="bbb">{{message}}</h2> </div> data: { message: '你好啊', isShow: true }
3. 条件循环动态绑定操作
-
v-if / v-else / v-else-if
-
v-if
判断是否显示,传入布尔值<h2 v-if="isShow"> data: { isShow: true }
-
v-if / v-else
<h2 v-if="isShow"> {{message}} </h2> <h1 v-else>isShow为false时, 显示我</h1> data: { message: 'isShow为true时, 显示我', isShow: true }
-
v-if / v-else-if / v-else
在第一个
if
条件失败,会执行了第二个else-if
,所有条件判断完了就会执行else
<div id="app"> // 方式一 通过操作使用 v-if / v-else-if / v-else 可读写不高 <h2 v-if="score>=90">优秀</h2> <h2 v-else-if="score>=80">良好</h2> <h2 v-else-if="score>=60">及格</h2> <h2 v-else>不及格</h2> // 方式二(推荐)采用了计算属性来显示文字内容 <h1>{{result}}</h1> </div> <script> const app = new Vue({ el: '#app', data: { score: 99 }, // 方式二采用了计算属性来显示文字内容 computed: { result() { let showMessage = ''; if (this.score >= 90) { showMessage = '优秀' } else if (this.score >= 80) { showMessage = '良好' } // ... return showMessage } } }) </script>
-
-
v-for
-
遍历数组
<!--1.在遍历的过程中,没有使用索引值(下标值)--> <ul> <li v-for="item in names">{{item}}</li> </ul> <!--2.在遍历的过程中, 获取索引值--> <li v-for="(item, index) in names"> {{index+1}}.{{item}} </li>
-
遍历对象
<!--1.在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value--> <ul> <li v-for="item in info">{{item}}</li> </ul> <!--2.获取key和value 格式: (value, key) --> <ul> <li v-for="(value, key) in info">{{value}}-{{key}}</li> </ul> <!--3.获取key和value和index 格式: (value, key, index) --> <ul> <li v-for="(value, key, index) in info">{{value}}-{{key}}- {{index}}</li> </ul> data: { info: { name: 'why', age: 18, height: 1.88 } }
-
v-for 添加 key属性
在没有添加key时,如果在中间插入元素,则dom会把下标的值修改为插入的值,后续的下标的值依次往后移一个位置。例如在C处添加F, 则DOM会把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
总结:key的作用主要是为了高效的更新虚拟DOM
-
4. v-model
-
基本使用
使用在
input
或textare
等可以表单类型的标签中v-model
指令来实现表单元素和数据的双向绑定<div id="app"> <input type="text" v-model="message"> {{message}} </div> data:{ message:'' }
-
v-model 结合radio类型
-
不使用vue实现单选按钮
<div> <label for="male"> <input type="radio" value="male" name="sex" id="male" checked>男 </label> <label for="female"> <input type="radio" value="female" name="sex" id="female">女 </label> </div>
-
使用vue实现单选按钮
<div id="app"> <label for="male"> <input type="radio" id="male" value="男" v-model="sex" checked>男 </label> <label for="female"> <input type="radio" id="female" value="女" v-model="sex">女 </label> <h2>您选择的性别是: {{sex}}</h2> </div> data: { sex: '男' }
-
-
v-model 结合checkbox类型
-
html式checkbox框
<label for="agree"> <input type="checkbox" id="agree" name="agree" >同意协议 </label>
-
Vue式checkbox实现单选框
<label for="agree">--> <input type="checkbox" id="agree" v-model="isAgree">同意协议 </label> <h2>您选择的是: {{isAgree}}</h2> // 如果isAgree为true,那么disable就会生效,下一步将无法选择,注意前面同意为true,下一步这里取反了 <button :disabled="!isAgree">下一步</button> data:{ isAgree: false, // 单选框 }
-
Vue式checkbox实现多选框
// 方式一 <input type="checkbox" value="篮球" v-model="hobbies">篮球 <input type="checkbox" value="足球" v-model="hobbies">足球 <input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球 <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球 <h2>您的爱好是: {{hobbies}}</h2> // 方式二(推荐) <label v-for="item in originHobbies" :for="item"> <input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}} </label> </div> data: { hobbies: [], // 多选框, originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球'] } })
-
-
v-model 结合select类型
-
选择一个
<select name="abc" v-model="fruit"> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="榴莲">榴莲</option> <option value="葡萄">葡萄</option> </select> <h2>您选择的水果是: {{fruit}}</h2> data:{ fruit:'' }
-
选择多个
<select name="abc" v-model="fruits" multiple> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="榴莲">榴莲</option> <option value="葡萄">葡萄</option> </select> <h2>您选择的水果是: {{fruits}}</h2> data:{ fruits:[] }
-
-
v-model 原理
v-model
其实是一个语法糖,它的背后本质上是包含两个操作:-
v-bind
绑定一个value
属性 -
v-on
指令给当前元素绑定input
事件 -
流程解析
- 因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message发生改变
- 当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变。
- 所以,通过v-model实现了双向的绑定
🍺不适用
v-model
实现数据双向绑定<input type="text" v-model="message"> <input type="text" :value="message" @input="valueChange"> data:{ message:'' }, methods:{ // 这里传参的event,在标签如果不写的话,默认会把该input事件传入进来 valueChange(event){ this.message = this.event.target.value } }
🍺简洁写法实现
v-model效果
<input type="text" :value="message" @input="message = $event.target.value"> data:{ message:'' },
-
-
v-model
修饰符-
.lazy
-
v-model
默认是在input事件中同步输入框的数据的。 -
也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变
-
lazy
修饰符可以让数据在失去焦点或者回车时才会更新<!--1.修饰符: lazy--> <input type="text" v-model.lazy="message"> <h2>{{message}}</h2>
-
-
.number
-
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
-
但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
-
number修饰符可以让在输入框中输入的内容自动转成数字类型
<!--2.修饰符: number--> <input type="number" v-model.number="age"> <h2>{{age}}-{{typeof age}}</h2>
-
-
tirm
-
如果输入的内容首尾有很多空格,通常我们希望将其去除
-
trim修饰符可以过滤内容左右两边的空格
<!--3.修饰符: trim--> <input type="text" v-model.trim="name"> <h2>您输入的名字:{{name}}</h2>
-
-
5. Vue检测数组更新
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
/ 1.push方法
// this.letters.push('aaa')
// this.letters.push('aaaa', 'bbbb', 'cccc')
// 2.pop(): 删除数组中的最后一个元素
// this.letters.pop();
// 3.shift(): 删除数组中的第一个元素
// this.letters.shift();
// 4.unshift(): 在数组最前面添加元素
// this.letters.unshift()
// this.letters.unshift('aaa', 'bbb', 'ccc')
// 5.splice作用: 删除元素/插入元素/替换元素
// 删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素)
// 替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素
// 插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素
// splice(start)
// splice(start):
this.letters.splice(1, 3, 'm', 'n', 'l', 'x')
// this.letters.splice(1, 0, 'x', 'y', 'z')
// 5.sort()
// this.letters.sort()
// 6.reverse()
// this.letters.reverse()
// 注意: 通过索引值修改数组中的元素不会使Vue的值更新
// this.letters[0] = 'bbbbbb';
// this.letters.splice(0, 1, 'bbbbbb')
// set(要修改的对象, 索引值, 修改后的值)
// Vue.set(this.letters, 0, 'bbbbbb')
3.4 计算属性
-
基本使用
下面代码实现了四种显示英文姓和名的写法,使用计算属性来书写使得代码可读性更好。
<div id="app"> // 字符串拼接方式 <h2>{{firstName + ' ' + lastName}}</h2> // 模板变量组合方式 <h2>{{firstName}} {{lastName}}</h2> // 使用 methods方法 <h2>{{getFullName()}}</h2> // 使用computed计算属性 <h2>{{fullName}}</h2> </div> <script> const app = new Vue({ el: '#app', data: { firstName: 'Lebron', lastName: 'James' }, // computed: 计算属性() computed: { fullName: function () { return this.firstName + ' ' + this.lastName } }, methods: { getFullName() { return this.firstName + ' ' + this.lastName } } }) </script>
-
computed 实现多本书籍价格统计
<h2>总价格: {{totalPrice}}</h2> <br> <h2>总价格: {{getTotalPrice()}}</h2> <script> const app = new Vue({ el: '#app', data: { books: [ {id: 110, name: 'Unix编程艺术', price: 119}, {id: 111, name: '代码大全', price: 105}, {id: 112, name: '深入理解计算机原理', price: 98}, {id: 113, name: '现代操作系统', price: 87}, ] }, methods: { // 使用方法() getTotalPrice: function () { let result = 0 for (let i=0; i < this.books.length; i++) { result += this.books[i].price } return result } }, // 使用计算属性() computed: { totalPrice: function () { let result = 0 for (let i=0; i < this.books.length; i++) { result += this.books[i].price } return result // for (let i in this.books) { // this.books[i] // ..... // } // // for (let book of this.books) { // // } } } }) </script>
-
计算属性深入理解
computed 包含 set 函数和get函数
set方法主要对数据是否变化进行监听,如果数据发生变化则会触发set方法
get方法主要用于数据值的返回,并被Vue接收渲染
注意:计算属性一般没有set方法,只有属性
# 基本结构 computed: { fullName: { set: function(*arg) { pass }, get: function () { pass } }
由于set方法一般不会进行书写,所以computed进行了简写
# fullname属性取代了get方法 computed: { fullName: function () { console.log('fullName'); return this.firstName + ' ' + this.lastName } }
-
methods 与 computed 的对比
method 在每次调用时都会去进行一次,没有缓存
computed 则是对值进行检测,如果值没有发生改变,则每次调用都只会加载一次,computed 数据会存储在缓存中。
<div id="app"> <!--1.直接拼接: 语法过于繁琐--> <h2>{{firstName}} {{lastName}}</h2> <!--2.通过定义methods--> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <!--3.通过computed--> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> </div> ... <script> const app = new Vue({ el: '#app', data: { firstName: 'Kobe', lastName: 'Bryant' }, methods: { getFullName: function () { console.log('methods getFullName'); return this.firstName + ' ' + this.lastName } }, computed: { fullName: function () { console.log('computed fullName'); return this.firstName + ' ' + this.lastName } } }) </script>
通过对比发现,methods在console中加载了四次,而computed只加载了一次。
3.5 组件
1. 组件的创建流程
✒️ 这是原始的书写方式,后面会有更加便捷的写法(语法糖)
-
创建组件构造器
Vue.extend() - 调用Vue.extend()创建的是一个组件构造器。 - 通常在创建组件构造器时,传入template代表我们自定义组件的模板。 - 该模板就是在使用到组件的地方,要显示的HTML代码。 - 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
-
注册组件
Vue.component() - 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。 所以需要传递两个参数: 1、注册组件的标签名 2、组件构造器
-
使用组件
组件必须挂载在某个Vue实例下,否则它不会生效。 我们来看下面我使用了三次<my-cpn></my-cpn>
-
一个完整的组件demo
<div id="app"> // 3.使用组件 <my-cpn></my-cpn> </div> // 1. 创建组件 const cpn = Vue.extend({ // 注意extend没有s template:` // 注意template没有s <div> <h3>这是组件的标题</h3> <span>这是组件内容</span> </div> ` }) // 2. 注册组件 Vue.component('my-cpn',cpn)
2. 全局组件和局部组件
-
全局组件
当我们通过调用**Vue.component()**注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。
当我们使用Vue 创建多个实例时,全局组件可以在任何一个实例中使用
// my-cpn 可以在app1和app2中使用 Vue.component('my-cpn',cpn) const app1 = new Vue({ ... }) const app1 = new Vue({ ... })
-
局部组件
如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件
<div id="app"> <cpn2_tag></cpn2_tag> </div> const cpn2 = Vue.extend({ template:` <div> <h3>这是局部组件的标题</h3> <span>这是局部组件内容</span> </div> ` }) const app = new Vue({ el: '#app', data: {}, methods: {}, components:{ // 此处有s // cpn2_tag 表示标签名 cpn2_tag:cpn2 } });
3. 父组件与子组件
- 在父组件需要根实例中注册
- 子组件只能在父组件中被识别的。
// 1.创建第一个组件构造器(子组件) const cpnC1 = Vue.extend({ template: ` <div> <h2>我是标题1</h2> <p>我是内容, 哈哈哈哈</p> </div> ` }) // 2.创建第二个组件构造器(父组件) const cpnC2 = Vue.extend({ template: ` <div> <h2>我是标题2</h2> <p>我是内容, 呵呵呵呵</p> <cpn1></cpn1> </div> `, components: { cpn1: cpnC1 } }) // root组件 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn2: cpnC2 } })
4. 组件的语法糖
-
方式一:全局组件注册的语法糖
这种方式书写的代码过于臃肿,不够简洁美观
// 全局注册组件 Vue.component('cpn1', { template: ` <div> <h2>我是标题1</h2> <p>我是内容, 哈哈哈哈</p> </div> ` }) // 第二个参数的对象,其实就是调用Vue.extend返回的对象被component接收了 // 局部注册组件语法糖 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { 'cpn2': { template: ` <div> <h2>我是标题2</h2> <p>我是内容, 呵呵呵</p> </div> ` } } })
-
⭐️方式二:组件模板分离(推荐)
分工更加明确,可读写更高。
// 1. 书写模板标签,指定id用以被template获取 <template id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,呵呵呵</p> </div> </template> // 2. 注册组件, Vue.component('cpn', { template: '#cpn' // 指定template标签的id })
5. 组件数据获取
-
使用
data()
函数来获取数据,注意组件中使用变量来获取数据时,根实例的数据是不能引用的。组件对象也有一个data属性(也可以有methods等属性)
只是这个data属性必须是一个函数
格式:data(){ return {} } -> 函数再返回一个对象,在对象中定义数据
<template id="cpn"> <div> <h2>{{title}}</h2> <p>我是内容,呵呵呵</p> </div> </template> Vue.component('cpn', { template: '#cpn', data() { return { title: 'abc' } } }) const app = new Vue({ el: '#app', data: { message: '你好啊', title: '我是标题' // 不会引用该变量 } })
-
为什么data用函数来书写,而不是直接用对象来书写
仔细对比发现,在组件中data是通过函数返回对象,并在对象中定义数据
而在根实例中data是直接使用对象来定义数据的。
为什么会这么定义呢?
-
解析
- 如果不是一个函数,Vue直接就会报错
- 如果使用对象直接返回,则每一个组件都是调用的同一个内存地址,会导致一处地方的数据修改,其他的地方相同的组件中的数据也会修改
- 采用函数返回对象,则每次调用组件时,都会产生新的对象,使每个组件中的数据相互独立,互不干扰。
-
组件同一对象调用伪代码
const obj = { name: 'why', age: 18 } Vue.component('cpn', { template: '#cpn', data() { return obj // 以后每一个生成组件,数据都是引用同一个地址 },
-
组件不同对象调用
const obj = Vue.component('cpn', { template: '#cpn', data() { return { // 以后每一个生成组件,数据都是引用不同的地址 name: 'why', age: 18 } },
-
-
封装组件demo
<template id="cpn"> <div> <h2>当前计数: {{counter}}</h2> <button @click="increment">+</button> <button @click="decrement">-</button> </div> </template> <script> Vue.component('cpn', { template: '#cpn', data() { return { counter: 0 } }, methods: { increment() { this.counter++ }, decrement() { this.counter-- } } }) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script>
6.组件通信
在开发中,往往一些数据确实需要从上层传递到下层,
- 比如在一个页面中,我们从服务器请求到了很多的数据。
- 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
- 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
-
父组件向子组件传递 props
注意驼峰命名,在脚手架中可以使用,在原生Vue中需要使用`-`来区分来,或者全部小写
-
方式一
:字符串数组,数组中的字符串就是传递时的名称// cmovies 要不全小写命名,要不写成c-movies <cpn v-bind:cmovies="movies"></cpn> // <cpn v-bind:cmovies=['海王', '海贼王', '海尔兄弟']></cpn> 上面代码就等同这种格式 const cpn = { template: '#cpn', props: ['cmovies', 'cmessage'], } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, components: { cpn } })
- :star:`方式二`:对象,对象可以设置传递时的类型,也可以设置默认值等。
const cpn = { template: '#cpn', props: { // 1.类型限制 // cmovies: Array, // cmessage: String, // 2.提供一些默认值, 以及必传值 cmessage: { type: String, default: 'aaaaaaaa', required: true }, // 类型是对象或者数组时, 默认值必须是一个函数 cmovies: { type: Array, default() { return [] } } } } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, components: { cpn } })
-
-
props 数据验证
如上代码,当需要对**props**进行类型等验证时,就需要对象写法了。
-
子组件向父组件传递 $emit()
使用自定义事件来完成子组件向父组件传递
-
在子组件中,通过$emit()来触发事件。
格式:.$emit('EventName',data) (事件名称,数据)
-
在父组件中,通过v-on来监听子组件事件。
<!-- 父组件 --> <div id="app"> // cat-click就是子组件定义事件的名称,catClick是父组件实现的方法,可以简单的理解为cat-click就是一个点击事件,后者是实现的方法, // 注意在父子传递的过程中,事件名称不可以使用【驼峰命名】 <cpn @cat-click="catClick"></cpn> </div> <!-- 子组件模板 --> <template id="cpn"> <div> <ul> <li v-for="item in categories"> // 子组件调用了catClick()方法 <button @click="catClick(item)">{{item.name}}</button> </li> </ul> <h6> {{desc}} </h6> </div> </template> const cpn = { // 这里被根实例的components调用了,就变成了子组件 template:'#cpn', data(){ return{ categories:[ { 'id':0001, 'name':'数码', }, { 'id':0002, 'name':'图书', }, { 'id':0003, 'name':'家电', }, { 'id':0004, 'name':'服饰', }, ], desc:'每天24开始更新数据' } }, methods:{ catClick(item){ // (自定义的事件方法名,传递的数据) this.$emit('cat-click',item) // 发射事件:自定义事件 } } } const app = new Vue({ el: '#app', data: {}, methods: {}, components:{ cpn // 增强写法就等于 cpn:cpn -》(标签名:对象) }, methods:{ catClick(item){ // 此处接收的item就是子组件中的item数据 console.log(item) } } });
-
-
父子组件访问方式
-
父组件→子组件,使用
$children
或$refs
-
$children
使用- 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
- 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
const app = new Vue({ el: '#app', data: { message: '你好啊' }, methods: { btnClick() { // 1.$children console.log(this.$children); for (let c of this.$children) { console.log(c.name); } } }, components: { // 子组件 cpn: { template: '#cpn', data() { return { name: ['Jack','Rion','Lucy'] } },
-
⭐️
$refs
使用- $refs和ref指令通常是一起使用的。
- 我们通过ref给某一个子组件绑定一个特定的ID
- 通过this.$refs.ID就可以访问到该组件了
<div id="app"> <cpn ref="aaa"></cpn> // 绑定一个特定的ID <button @click="btnClick">按钮</button> </div> methods: { btnClick() { // 2.$refs => 对象类型, 默认是一个空的对象 ref='bbb' console.log(this.$refs.aaa.name) // 使用$refs.ID来获取组件中的数据 } }, components: { cpn: { template: '#cpn', data() { return { name: ['Jack','Rion','Lucy'] } },
-
-
🛑子组件→父组件,使用
$parent
或$root
-
注意:
1.尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做 2.子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。 3.如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。 4.另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
data() { return { name: '我是cpn组件的name' } }, components: { cpn: { template: '#cpn', methods: { btnClick() { // 1.访问父组件$parent console.log(this.$parent); console.log(this.$parent.name); // 2.访问根组件$root console.log(this.$root); console.log(this.$root.message); }
-
-
3.6 插槽 slot
插槽(slot)是什么?
- 插槽的目的是让我们原来的设备具备更多的扩展性
- 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
组件的插槽有什么好处
- 组件的插槽也是为了让我们封装的组件更加具有扩展性。
- 让使用者可以决定组件内部的一些内在这里插入图片描述
容到底展示什么。
🌸 栗子:移动网站中的导航栏。
移动开发中,几乎每个页面都有导航栏。显示内容也不一致
导航栏我们必然会封装成一个插件,比如nav-bar组件。
一旦有了这个组件,我们就可以在多个页面中复用了。
1. 普通插槽
-
基本使用
1.插槽的基本使用 <slot></slot> 2.插槽的默认值 <slot>button</slot> 3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
<div id="app"> <!-- 第一个cpn --> <cpn><button>左侧</button></button></cpn> //此处内容会替换slot标签的内容 <!-- 第二个cpn --> <cpn></cpn> // 若为空则会显示slot标签中的内容 <!-- 第三个cpn --> <cpn> // 组件中的全部内容一起替换slot标签中的内容 <span>第一段内容</span> <span>第一段内容</span> <span>第一段内容</span> </cpn> </div> <template id="cpn"> <div> <h2>我是组件,这是共同部分</h2> <slot><h4>这时插槽中的默认值</h4></slot> </div> </template>
2. 具名插槽
- 当子组件的功能复杂时,子组件的插槽可能有多个时。
- 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
- 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
- 非常简单,只要给
slot
元素一个name
属性即可
// slot属性用来指定加载到模板中哪个slot标签,没有被指定name的slot标签就不会加载
<div id="app">
<cpn><span slot="center">标题</span></cpn>
<cpn><button slot="left">返回</button></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
3. 编译作用域
父组件模板的所有东西都会在父级作用域内编译;
子组件模板的所有东西都会在子级作用域内编译。
4. 插槽获取数据
当我们在使用插槽时,想要同一组数据不同方式展示,如何将数据传递给slot中呢
我们通过<template slot-scope="slotProps">
获取到slotProps属性
通过slotProps.data
就可以获取到刚才我们传入的data了
slot
标签中可以指定数据对象- 流程
- 书写模板,定义slot标签,并指定变量引用的数据
:DateName="pLanguages"
- 实例app中使用组件,若为空则显示模板中slot的默认样式
- 若不为空,则使用自定义标签➕属性
slot-scope="SlotName"
来获取数据 - 数据通过
SlotName.DateName
来使用数据
- 书写模板,定义slot标签,并指定变量引用的数据
<div id="app">
<cpn></cpn> // 默认显示方式,即 ul>li 的方式来显示
<cpn>
<!--目的是获取子组件中的pLanguages-->
<template slot-scope="slot"> // template 名字是可以修改的,slot-scope获取了 模板slot标签的属性,即获取了数据 slot.data = pLanguages
<span v-for="item in slot.data"> {{item}} | </span>
</template>
</cpn>
<cpn>
<!--目的是获取子组件中的pLanguages-->
<template slot-scope="slot">
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
<!--<cpn></cpn>-->
</div>
<template id="cpn">
<div>
<slot :data="pLanguages"> //:data data名字可以随意修改,后续调用通过改名字获取数据
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
// 子组件中定义了pLanguages数组
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
}
}
}
}
网页效果
四、Vue CLI
CLI
是Command-Line Interface
,翻译为命令行界面, 但是俗称脚手架,使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置。
- Vue程序运行过程
4.1 安装node
-
下载
http://nodejs.cn/download/
-
安装检测
4.2 NPM
- NPM的全称是Node Package Manager
- 是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。
-
配置下载源
// 如出现下载缓慢请配置 npm 淘宝镜像 npm config set registry https://registry.npm.taobao.org
-
npm 使用
在使用npm之前,需要先下载node安装包。
npm(node package Manager)
-
node 环境配置
npm init
-
安装包
// npm安装 npm install vue npm install --save npm install --save-dev npm install -g npm search // 搜索 npm update // 更新 npm list -g --depth 0 // 查看全局包,–depth 0:列表搜索深度0级 不加这参数 会默认所有 npm ls vue/cli -g // 查询单独一个包文件是否安装 npm install -g cnpm --registry npm uninstall -g 包名称 // 卸载
-
-
npm run build调用流程
-
npm run dev 运行流程
4.3 Vue CLI 使用
-
安装Vue CLI
npm install -g @vue/cli
-
创建 vue 项目
vue crate projectname
-
启动 vue 项目
npm run serve
五、Vue-Router
5.1 url中的hash和history
一个是带有#/
一个不带
-
hash
URL的hash也就是锚点(#), 本质上是改变window.location的href属性
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
-
history
history接口是HTML5新增的, 它有五种模式改变URL而不刷新页面.
-
history.pushState()
-
history.replaceState()
-
history.go()
因为 history.back() 等价于 history.go(-1)
history.forward() 则等价于 history.go(1)
-
5.2 Vue-Router 使用
1. 基本使用
-
安装
npm install vue-router --save
-
vue-router的简单使用
第一步: 创建路由组件 第二步: 配置路由映射: 组件和路径映射关系 第三步: 使用路由: 通过<router-link>和<router-view>
-
实例
-
创建router实例
//src/router/index.js
import VueRouter from "vue-router"; import Vue from "vue"; import Home from '../components/home' import Order from '../components/order' // 第一步: 创建路由组件 Vue.use(VueRouter) // 第二步: 配置路由映射: 组件和路径映射关系 const routes = [ { path:"/home", component:Home }, { path:"/order", component:Order }, ] const router = new VueRouter({ routes, mode:"history" }) // 第三步:将router对象传入到Vue实例
export default router
```-
挂在到
main.js
中import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App),
}).$mount(’#app’)
```-
实现component中的组件
// home.vue <template> <div> <h2>Home</h2> <span>这是Home页面</span> </div> </template> <script> export default { name:'Home' } </script> // order.vue <template> <div> <h2>Home</h2> <span>这是Home页面</span> </div> </template> <script> export default { name:'Order' } </script>
-
5.2 router-link属性
1. router-link属性
-
to属性
实现组件之间的切换,即各组件之间的跳转
-
tag属性
tag可以指定之后渲染成什么组件, 比如上面的代码会被渲染成一个
- 元素, 而不是 。默认是渲染成a标签
-
<router-link to="home" tag="h3">Home</router-link>
-
replace属性
replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中
<router-link to="home" tag="h3" replace>Home</router-link>
-
active-class
当对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称.
-
方式一
:加载标签上<router-link to="home" active-class="activeName">Home</router-link>
-
方式一
:加载VueRouter实例中// /src/router/index.js const router = new VueRouter({ routes, linkActiveClass:'activeName', mode:'history' })
设置类样式,实现点击字体变绿
// src/App.vue <style> .activeName{ color: yellowgreen; } </style>
实现效果
-
2. $router 实现路由跳转
标签通过点击事件来实现路由跳转,在实现的methods中,调用$router
, $router
这是一个系统自带的对象,属于全局的vuerouter实例。
-
使用push方法,则网页可以进行前进和后退
-
使用repalce方法,则不允许网页进行前进或者后退
<button @click="clickHome">Home</button> <button @click="clickOrder">Order</button> methods:{ clickHome(){ // return this.$router.push('/home') return this.$router.replace('/home') }, clickOrder(){ // return this.$router.push('/order') return this.$router.replace('/order') } }
5.3 动态路由
-
设置接收参数
// router/index.js { path: '/user/:id', component: User, }
-
组件解析id
<template> <div> <h2>我是用户界面</h2> <h2>{{userId}}</h2> </div> </template>
-
视图传值
<router-link :to="'/user/'+userId">用户</router-link>
5.4 路由懒加载
- 当打包构建应用时,Javascript 包会变得非常大,影响页面加载。
- 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
- 路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块.
- 只有在这个路由被访问到的时候, 才加载对应的组件
- 未使用懒加载
- ⭐️使用懒加载
-
其他方式使用懒加载
方式一: 结合Vue的异步组件和Webpack的代码分析. const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })}; 方式二: AMD写法 const About = resolve => require(['../components/About.vue'], resolve); 方式三(⭐): 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割. const Home = () => import('../components/Home.vue')
5.5 路由嵌套
- 比如在home页面中, 我们希望通过/home/news和/home/message访问一些内容
- 一个路径映射一个组件, 访问这两个路径也会分别渲染两个组件.
-
实现嵌套路由有两个步骤
- 创建对应的子组件(), 并且在路由映射中配置对应的子路由.
- 在组件内部使用标签.
// 1. 创建对应的HomeNews.vue 和 HomeMessage.vue 组件 { path: '/home', component: Home, meta: { title: '首页' }, // 2. 在home页面创建对应的子组件(children) children: [ // { 设置子组件的默认显示 // path: '', // redirect: 'news' // }, { path: 'news', component: HomeNews }, { path: 'message', component: HomeMessage } ] }, // 3. 路由视图书写 <router-link to="/home/news">关于</router-link> <router-link to="/home/message">关于</router-link>
5.6 参数传递
在网页URL后面传递query,如:http://www.baidu.com/?type=1&name=rion
获取参数通过$route
对象获取的
在使用了 vue-router 的应用中,路由对象会被注入每个组件中,赋值为 this.$route
,并且当路由切换时,路由对象会被更新。
-
router-link 传递
// App.vue <router-link :to="{path: '/profile', query: {name: 'why', age: 18, height: 1.88}}"> 档案</router-link> // profile.vue <template> <div> <h2>我是Profile组件</h2> <h2>{{$route.query.name}}</h2> <h2>{{$route.query.age}}</h2> <h2>{{$route.query.height}}</h2> </div> </template>
-
methods方法传递
// App.vue <button @click="profileClick">档案</button> methods: { profileClick() { this.$router.push({ path: '/profile', query: { name: 'kobe', age: 19, height: 1.87 } }) } } // profile.vue <template> <div> <h2>我是Profile组件</h2> <h2>{{$route.query.name}}</h2> <h2>{{$route.query.age}}</h2> <h2>{{$route.query.height}}</h2> </div> </template>
-
router & route 的区别
$router
r为VueRouter
实例,想要导航到不同URL,则使用$router.push
方法$route
为当前router
跳转对象里面可以获取name、path、query、params
等
5.7 导航守卫
vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
可以在路由发生改变时执行一些操作,比如修改网页的title等
-
beforeEach
格式:beforeEach(to,from,next) => {} to: 即将要进入的目标的路由对象. from: 当前导航即将要离开的路由对象. next: 调用该方法后, 才能进入下一个钩子
首先, 我们可以在钩子当中定义一些标题, 可以利用meta来定义
其次, 利用导航守卫,修改我们的标题.
// 子组件中获取元数据的title的方式
document.title = to.matched[0].meta.title
```
**补充**
```
如果是后置钩子, 也就是afterEach, 不需要主动调用next()函数.
补充二: 上面我们使用的导航守卫, 被称之为全局守卫.
路由独享的守卫.
组件内的守卫.
```
5.8 keep-alive
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
它们有两个非常重要的属性:
-
include - 字符串或正则表达,只有匹配的组件会被缓存
-
exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存.
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
🌴实现切换标签保持组件的alive状态
-
注意,以下都是基于keep-alive状态下才能实现
-
定义path路径
-
使用生命周期create函数,在创建组件是保存path的路径
-
在切换组件时,使用组件守卫来保持path路径,实现保存保持