1.Vue简介
- JavaScript框架
- 简化DOM操作
- 响应式数据驱动
在es6中,用let
定义变量,const
定义常量
Vue实例的作用范围:管理el选项命中元素及其内部的后代元素
可以使用其他的选择器,但是建议使用ID选择器
可以使用其他的双标签,不能使用HTML和BODY
<div id="app">
{{ message }}
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"> --></script>
<script src="./vue.js"></script>
<script>
// 编程范式:声明式编程
var app = new Vue({
el: "#app", // 用于挂载要管理的元素(建议使用id选择器)
data: { // 定义数据
message: "Hello Vue!"
}
})
</script>
计时器
methods
属性用于在Vue对象中定义方法
@click
:该指令用于监听某个元素的点击事件,并且需要指定发生点击时,执行的方法
<div id="app">
<h2>当前计数:{{counter}}</h2>
<!--<button v-on:click="counter++">+</button> -->
<!--<button v-on:click="counter--">-</button> -->
<button v-on:click="add">+</button>
<button @click="sub">-</button>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
counter:0
},
methods: {
add: function(){
this.counter++;
},
sub: function(){
this.counter--;
}
}
})
</script>
MVVM
View层
- 视图层
- 在前端开发中,通常就是DOM层
- 主要的作用是给用户展示各种信息
Model层
- 数据层
- 数据可能是固定的死数据,更多的是来自服务器从网络上请求下来的数据
VueModel层
- 视图模型层
- 视图模型层是View和Model沟通的桥梁
- 一方面它实现了数据绑定,将Model的改变实时的反应到View中
- 另一方面它实现了DOM监听,当DOM发生一些事件(点击、滚动、touch)时,可以监听到,并在需要的情况下改变对应的data
创建Vue实例的时候,传入了一个对象options
el
:
类型:String | HTMLElement
作用:决定之后Vue实例会管理哪一个DOM
data
:
类型:Object | Function(组件当中data必须是一个函数)
作用:Vue实例对应的数据对象,可以写复杂类型的数据(渲染时,遵循js 的语法即可)
methods
:
类型:{ [key: string]: Function }
作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用
Mustache语法
<div id="app">
<h2>{{message}}</h2>
<h2>{{message}},李银河</h2>
<!-- mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式 -->
<h2>{{firstName + lastName}}</h2>
<h2>{{firstName + '' + lastName}}</h2> // kobe bryant
<h2>{{firstName}} {{lastName}}</h2> // kobe bryant
<h2>{{counter * 2}}</h2> // 200
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好啊",
firstName: "kobe",
lastName: "bryant",
counter: 100
}
})
</script>
2.Vue指令
1.v-text指令
作用与Mustache比较相似:都是用于将数据显示在界面中(设置标签的内容),通常情况下,v-text接受一个string类型。默认写法会替换全部内容,使用插值表达式{{}}可以替换指定的内容;内部支持写表达式
2.v-html指令
该指令后面往往会跟上一个string类型,设置元素的innerHTML
会将string的html解析出来并且进行渲染,而v-text指令无论内容是什么,只会解析为文本
<div id="app">
<h2 v-html="url"></h2> // 百度一下
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好",
url: "<a href="http://wwww.baidu.com">百度一下</a>"
}
})
</script>
3.v-on指令
为元素绑定事件
绑定的方法定义在methods
属性中,方法内部通过this
关键字访问定义在data
中的数据;事件绑定的方法写成函数调用的形式,可以传入自定义参数;定义方法时需要定义形参来接收传入的实参;事件的后面跟上 .修饰符 可以对事件进行限制;.enter
可以限制触发的按键为回车;事件修饰符有多种(.stop
,完成响应之后,不再进行其他响应,阻止冒泡、.prevent
阻止默认事件、.once
只触发一次回调)
手动获取浏览器参数的event对象:$event
<div id="app">
<input type="button" value="事件绑定" v-on:click="方法">
<input type="button" value="事件绑定" v-on:mouseenter="方法">
<input type="button" value="事件绑定" v-on:dbclick="方法">
<input type="button" value="事件绑定" @dbclick="方法">
<input type="button" value="点击" @click="doIt(666,'老铁')">
<input type="button" @keyup.enter="sayHi">
</div>
<script>
const app = new Vue({
el: "#app",
methods:{
doIt:function(p1,p2){
console.log(p1);
console.log(p2);
},
sayHi:function(){
alert("吃了没");
}
}
})
</script>
4.v-show指令
根据表达值的真假,切换元素的显示状态;原理是修改元素的display
,实现显示隐藏;指令后面的内容,最终都会解析为布尔值;值为true元素显示,false隐藏;数据改变后,对应元素的显示状态会同步更新
<div id="app">
<img src="地址" v-show="true">
<img src="./img/monkey.gif" v-show="isShow">
<input type="button" value="切换显示状态" @click="changeIsShow">
<input type="button" value="累加年龄" @click="addAge">
<img src="./img/monkey.gif" v-show="age>=18">
</div>
<script>
const app = new Vue({
el: "#app",
data: {
isShow: false,
age: 16
},
methods: {
changeIsShow:function(){
this.isShow != this.isShow;
},
addAge:function(){
this.age++;
}
}
})
</script>
5.v-if指令
根据表达值的真假,切换元素的显示和隐藏(操作dom元素);表达式的值为true,元素存在于dom树中,为false,从dom树中移除;频繁的切换使用v-show,反之使用v-if,前者的切换消耗小
6.v-bind指令
动态设置元素的属性(如src、class、title)
完整写法是 v-bind:属性名
简写可以直接省略v-bind,只保留:属性名
需要动态的增删class
,建议使用对象的方式:class="{类名:布尔值}"
:class="{active:isActive}"
(active是否生效,取决于isActive的值)
<style>
.active {
color:red;
}
</style>
<div id="app">
<h2 :class="{key1:value1,key2:value2}"></h2>
<h2 :class="{类名:布尔值}"></h2>
<h2 :class="{active:isActive,line:isLine}"></h2>
</div>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好",
isActive:true,
isLine:true
}
})
</script>
动态绑定style
(1)对象语法:对象的value可以是具体的值,也可以是来自data中的属性
<h2 :style="{key(css的属性名):value(属性值)}"></h2>
<!-- '50px'必须加单引号,否则会当作一个变量去解析-->
<h2 :style="{fontSize:'50px'}">{{message}}</h2>
<!-- finalSize 当成一个变量使用-->
<h2 :style="{fontSize: finalSize}">{{message}}</h2>
<!-- finalSize + 'px'作为一个表达式,进行字符串拼接-->
<h2 :style="{fontSize: finalSize + 'px'}">{{message}}</h2>
<h2 :style="{fontSize: finalSize + 'px', color:finalColor}">{{message}}</h2>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好",
finalSize: '100px',
finalSize:100,
finalColor:'red'
}
})
</script>
(2)数组语法
<div :style="[baseStyle,baseSytle1]"></div>
7.v-for指令
根据数据生成列表结构;数组经常和v-for结合使用;语法是{item,index} in 数据
变量名 in 列表 item和index可以结合其他指令一起使用;数组长度的更新会同步到页面上,是响应式的
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key
属性(绑定)
<div id="app">
<ul>
<li v-for="(item,index) in movies" :key="item">{{index}}.{{item}}</li>
</ul>
<!-- 遍历对象-->
<h2 v-for="items in foods">{{items.name}}</h2>
<ul>
<li v-for="(value,key) in info">{{value}}--{{key}}</li>
</ul>
<ul>
<li v-for="(value,key,index) in info">{{value}}--{{key}}--{{index}}</li>
</ul>
<input type="button" value="添加数据" @click="add">
<input type="button" value="移除数据" @click="remove">
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
movies: ["夏洛特烦恼", "速度与激情", "你好李焕英"],
info:{
name:why,
age:18,
height:1.88
}
},
foods:[
{ name: "西红柿炒蛋" },
{ name: "西红柿炒蛋" }
],
methods:{
add:function(){
this.foods.push({ name: "豆干" });
},
remove:function(){
this.foods.shift();
}
}
})
</script>
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新:
push()
:在最后添加元素,可以传多个值
pop()
:删除数组中的最后一个元素
shift()
:删除数组中的第一个元素
unshift()
:在数组最前面添加元素,可以传多个值
splice()
:第一个参数 start 起始位置,删除(第二个参数传入要删除几个元素,如果没有传则删除后面所有的元素)、插入(第二个参数传入0,并且后面跟上要插入的元素)、替换元素(第二个参数表示要替换几个元素,后面是用于替换前面的元素)
sort()
:排序
reverse()
:翻转
Vue.set(要修改的对象,索引值,修改后的值)
toFixed(num)
方法可把 Number 四舍五入为指定小数位数的数字
通过索引值修改元素值不是响应式的
8.v-model指令
获取和设置表单元素的值(双向数据绑定);绑定的数据会和表单元素值相关联;绑定的数据⬅➡表单元素的值
它的背后本质上包含两个操作:
①v-bind绑定一个value属性
②v-on指令给当前元素绑定input事件
<div id="app">
<input type="text" v-model="message">
<input type="text" :value="message" @input="valueChange">
<input type="text" :value="message" @input="message = $event.target.value">
<input>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: '你好啊'
},
methods:{
valueChange(event){
this.message = event.target.value;
}
}
})
</script>
结合radio(没有v-model时,必须有name属性才能实现互斥,选一个)
<label for="male">
<input type="radio" name="sex" id="male" value="男" v-model="sex"> 男
</label>
<label for="female">
<input type="radio" name="sex" id="female" value="女" v-model="sex"> 女
</label>
<h3>您选择的性别是:{{sex}}</h3>
<script>
const app = new Vue({
el: "#app",
data: {
sex: '男'
}
})
</script>
v-model:checkbox
<div id="app">
<!-- checkbox单选框 布尔类型 -->
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree"> 同意协议
</label>
<h2>您选择的是:{{ isAgree }}</h2>
<button :disabled="!isAgree">下一步</button>
<br>
<!-- checkbook多选框 数组类型 -->
<label><input type="checkbox" name="" id="" value="蓝球" v-model="hobbies">蓝球</label>
<label><input type="checkbox" name="" id="" value="足球" v-model="hobbies">足球</label>
<label><input type="checkbox" name="" id="" value="乒乓球" v-model="hobbies">乒乓球</label>
<label><input type="checkbox" name="" id="" value="羽毛球" v-model="hobbies">羽毛球</label>
<h2>您的爱好是:{{hobbies}}</h2>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好啊",
isAgree: false, // 单选框
hobbies: [] // 多选框
}
})
</script>
v-model:select
<!-- select选择一个 字符串类型-->
<select name="abc" id="" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="葡萄">葡萄</option>
<option value="西瓜">西瓜</option>
<option value="草莓">草莓</option>
</select>
<h2>您选择的水果是:{{fruit}}</h2>
<!-- select选择多个 数组类型-->
<select name="abc" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="葡萄">葡萄</option>
<option value="西瓜">西瓜</option>
<option value="草莓">草莓</option>
</select>
<h2>您选择的水果是:{{fruits}}</h2>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好啊",
fruit: "香蕉",
fruits: []
}
})
</script>
值绑定 :value
修饰符
(1)lazy
修饰符:默认情况下,在input事件中同步输入框的数据,即一旦有数据发生变化,则对应的data中的数据就会自动发生改变,lazy修饰符可以让数据在失去焦点或者回车时才会更新
<label><input type="text" v-model.lazy="message"></label>
<h2>{{message}}</h2>
(2)number
修饰符:默认情况下,在输入框中无论输入字母还是数字,都会被当做字符串类型进行处理,number修饰符可以让在输入框中输入的内容自动转成数字类型
<label><input type="text" v-model.number="age"></label>
<h2>{{age}}</h2>
(3)trim
修饰符:过滤内容左右两边的空格
<label><input type="text" v-model.trim="name"></label>
<h2>{{name}}</h2>
9.v-once指令
该指令后面不需要跟任何表达式
该指令表示元素和组件只渲染一次,不会随着数据的改变而改变
10.v-pre指令
跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法
<p v-pre>{{message}}</p> // {{message}}
11.v-cloak指令
在某些情况下,浏览器可能会直接显示出Mustache标签,v-cloak就可以实现类似隐藏Mustache标签的作用,vue解析之后,div中的v-cloak属性就没了
<style>
[v-cloak] {
display: none;
}
</style>
<div id = "app" v-cloak>
<h2 v-cloak>{{message}}</h2> // {{message}}
</div>
计算属性
计算属性会进行缓存,如果多次使用时,计算属性只会调用一次;
一般是没有set方法的,是一个只读属性
<div id="app">
<!--1.直接拼接:语法过于繁琐-->
<h2>{{firstName + '' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<!--2.通过定义methods-->
<h2>{{getFullName()}}</h2>
<!--3.计算属性不需要加小括号,命名 名词-->
<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:function(){
return this.firstName + '' + this.lastName;
}
}
})
</script>
ES5之前的var是没有块级作用域的(if/for);
ES6中的let是有块级作用域的(if/for)
const的使用
当修饰的标识符不会被再次赋值时,就可以使用const来保证数据的安全性;再开发中,优先使用const,只有需要改变某一个标识符时使用let(ES6中)
注意:
- 一旦给const修饰的标识符被赋值后,不能修改
- 在使用const定义标识符,必须进行赋值
- 常量的含义是指向的对象不能修改,但可以改变对象内部的属性
<script>
const name = "why",
const age = 18,
const height = 1.88
// ES5的写法
const obj = {
name:name,
age:age,
height:height,
run:function(){},
eat:function(){}
}
//ES6 的写法
const obj = {
name,
age,
height,
run(){},
eat(){}
}
</script>
Vue在进行DOM 渲染时,出于性能考虑,会尽可能的复用以及存在的元素,而不是重新创建新的元素。若不希望Vue出现重复利用的问题,可以给对应的input添加key
,并且保证key的不同
编程范式:命令式编程、声明式编程
面向对象编程(第一公民:对象)、函数式编程(第一公民:函数)
<script>
let nums = [10,20,222,111,444,50,60]
let newNums = nums.filter(function(n){
return n < 100
})
console.log(newNums)
let new2Nums = newNums.map(function(n) {
return n*2
})
console.log(new2Nums)
let total = new2Nums.reduce(function(prevValue,n){
return prevValue + n
},0)
console.log(total)
-----------------------------------------------------------------
// 函数式编程
let total = nums.filter(function(n){
return n < 100
}).map(function(n){
return n * 2
}).reduce(function(prevValue, n){
return prevValue + n
},0)
-----------------------------------------------------------------
// 箭头函数
let total = nums.filter(n => n < 100).map(n => n * 2).reduce((pre, n) => pre + n)
</script>
filter()
过滤器回调函数要求:必须返回一个boolean值:true or false
当返回true时,函数内部会自动将这次回调的值n加入到新的数组中,
返回false时,函数内部会过滤掉这次的n
map()
映射,返回的是一个数组
reduce()
:对数组中所有的内容进行汇总
3.组件化
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造应用,任何应用都会被抽象成一颗组件树。
3.1 组件化的步骤
(1)创建组件构造器 Vue.extend()
传入template
代表自定义组件的模板(在使用到组件的地方,要显示的HTML代码)
(2)注册组件 Vue.component()
(3)使用组件
<div id="app">
<!-- 3.使用组件 -->
<cpn></cpn>
</div>
<script src="../vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈</p>
<p>我是内容,呵呵呵呵</p>
</div>`
})
// 2.注册组件 Vue.component('组件标签名', 组件构造器) 全局组件
// Vue.component('cpn', cpnC)
const app = new Vue({
el: "#app",
data: {
message: "你好啊",
},
// cpn为使用组件时的标签名:组件构造器(局部组件,挂载在某个vue实例中)
components: {
cpn: cpnC
}
})
</script>
3.2 子组件与父组件
子组件是只能在父组件中识别的
<div id="app">
<cpn2></cpn2>
<!--报错-->
<cpn1></cpn1>
</div>
<script>
// 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
}
})
const app = new Vue({
el: "#app",
components: {
cpn2: cpnC2
}
})
</script>
语法糖:主要是省去了Vue.extend()的步骤,直接使用一个对象来代替
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script>
// const cpnC = Vue.extend() 创建组件构造器
// 全局组件注册
Vue.component('cpn1',{
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容,哈哈哈哈</p>
</div>`
})
// 注册局部组件
const app = new Vue({
el: "#app",
data:{
message:'hello'
},
components: {
'cpn2': {
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容,哈哈哈哈</p>
</div>`
}
}
})
</script>
3.3 组件模板分离
<div id="app">
<cpn></cpn>
</div>
<!-- 1.script标签,注意:类型必须是text/x-template -->
<!-- <script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈</p>
</div>
</script> -->
<!-- 2.template标签 -->
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈呵呵呵呵</p>
</div>
</template>
<script src="../vue.js"></script>
<script>
// 注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
</script>
组件内部不能访问vue实例里面的数据。
组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也有属于自己的数据 data属性:必须是一个函数,且函数返回一个对象,对象内部保存着数据
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
<h2>{{title}}</h2>
</div>
</template>
<script>
Vue.component('cpn', {
template: '#cpn',
data: function () {
return {
title: "abc"
}
}
})
</script>
3.4 父子组件的通信
子组件不能引用父组件或者Vue实例的数据,但在开发中,往往一些数据需要从上层传递到下层:比如在一个页面中,我们从服务器请求到了很多数据,其中一部分数据并非是整个页面的大组件来展示的,而是需要下面的子组件进行展示,这时,并不会让子组件再次发送一个网络请求,而是直接让父组件(大组件)将数据传递给子组件(小组件)
(1) 通过props
向子组件传递数据
使用props
来声明需要从父级接收到的数据
props的值有两种方式:
① 字符串数组,数组中的字符串就是传递时的名称
② 对象:对象可以设置传递时的类型,也可以设置默认值
<div id="app">
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<!--<p>{{cmovies}}</p>-->
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script>
const cpn = {
template: "#cpn",
props:['cmovies','cmessage']
}
const app = new Vue({
el:"#app",
data:{
message:"hello",
movies:['海贼王', '哈姆雷特', '海王']
},
components:{
'cpn':cpn
}
})
</script>
props数据验证
当需要对props进行类型等验证时,需要对象写法
验证支持的数据类型:
String、Number、Boolean、Array、Object、Date、Function、Symbol
当有自定义构造函数时,验证也支持自定义的类型
<div id="app">
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<!--<p>{{cmovies}}</p>-->
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script>
// 父传子:cpn
const cpn = {
template: "#cpn",
props:{
// 1.类型限制
cmovies:Array,
cmessage:String,
// 2.提供一些默认值、必传值
cmessage: {
type:String,
default:"aaaaaaa",
required:true
},
// 类型是对象或者数组时,默认值必须是一个函数
cmovies:{
type:Array,
default:function(){
return []
}
}
},
data(){
return {}
}
}
const app = new Vue({
el:"#app",
data:{
message:"hello",
movies:['海贼王', '哈姆雷特', '海王']
},
components:{
cpn // 增强写法(等同于'cpn':cpn cpn:cpn)
}
})
</script>
若props是驼峰命名的,则在使用时用“-”分割
<div id="app">
<cpn :cinfo="info" :child-my-message="message"></cpn>
</div>
<script>
const cpn = {
template: '#cpn',
props: {
cinfo: {
type: Object,
default() {
return {}
}
},
childMyMessage: {
type: String,
default: ''
}
}
}
</script>
(2) 通过事件向父组件发送消息
子组件向父组件传递—>自定义事件来完成
v-on不仅可以用于监听DOM事件,还可以用于组件间的自定义事件
自定义事件流程:
① 在子组件中,通过$emit()
来触发事件
② 在父组件,通过v-on
来监听子组件事件
<!-- 父组件模板 -->
<div id="app">
<!--父组件监听子组件传过来的事件-->
<cpn @itemclick="cpnClick"></cpn>
</div>
<!-- 子组件模板 -->
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script>
// 1.子组件
const cpn = {
template: "#cpn",
data() {
return {
categories: [
{ id: 'aaa', name: '热门推荐' },
{ id: 'bbb', name: '手机数码' },
{ id: 'ccc', name: '家用电器' },
{ id: 'ddd', name: '电脑办公' },
]
}
},
methods: {
btnClick(item) {
// 发射事件(自定义事件--'事件名称')
this.$emit('itemclick', item)
}
}
}
// 2.父组件
const app = new Vue({
el: "#app",
data: {
message: 'Hello'
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
</script>
3.5 父子组件的访问方式
(1)父组件访问子组件:$children
、$refs
(reference引用 重要)
(2)子组件访问父组件:$parent
、$root
3.6 slot
组件的插槽是为了让组件更加具有扩展性
最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽,一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容:搜索框、文字、菜单…,由调用者自己决定
<!--
1.插槽的基本使用 <slot></slot>
2.插槽的默认值 <slot><button>按钮</button></slot>
3.如果有多个值同时放入到组件进行替换时,一起作为替换元素
-->
<div id="app">
<cpn><button>按钮</button></cpn>
<cpn><span>呵呵呵呵</span></cpn>
<cpn>
<i>啦啦啦</i>
<div>我是div元素</div>
<p>我是p元素</p>
</cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>哈哈哈哈哈哈哈</p>
<slot><button>按钮</button></slot>
</div>
</template>
<script src="../vue.js"></script>
<script>
const cpn = {
template: "#cpn"
}
const app = new Vue({
el: "#app",
data: {
message: 'Hello'
},
components: {
cpn
}
})
</script>
具名插槽
<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>
编译作用域
准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在自己作用域内编译
作用域插槽
父组件替换插槽的标签,但是内容本身由子组件提供
<div id="app">
<cpn></cpn>
<cpn>
<!-- 目的:获取子组件中的pLanguages -->
<template slot-scope="slot">
<span v-for="item in slot.data">{{item}} -</span>
</template>
</cpn>
<cpn>
<template slot-scope="slot">
<!-- <span v-for="item in slot.data">{{item}} *</span> -->
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: 'Hello'
},
components: {
cpn: {
template: "#cpn",
data() {
return {
pLanguages: ['JavaScript', 'Java', 'C++', 'C#', 'Python']
}
}
}
}
})
</script>
4.模块化开发
在网页开发早期,js制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那时代码还是很少的。随着ajax异步请求的出现,慢慢形成了前后端的分离,客户端需要完成的事情越来越多,代码量也与日俱增,为了应对代码量的剧增,通常会将代码组织在多个js文件中进行维护,但是这种维护方式,依然不能避免一些灾难性的问题:
① 全局变量同名问题
② 对js文件的依赖顺序几乎是强制性的
我们可以使用将需要暴露到外面的变量,使用一个模块作为出口:
在匿名函数内部,定义一个对象;给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可);最后将这个对象返回,并且外面使用一个MoudleA接收
常见的模块化规范:CommonJS、AMD、CMD、ES6的Modules
export(导出)基本使用
// info.js
export let name = 'why'
export let age = 18
export let height = 1.88
let name = 'why'
let age = 18
let height = 1.88
export {name, age, height}
// 导出函数/类
export function mul(num1,num2) {
return num1 + num2
}
export class Person {
run() {}
}
export {mul, Person}
<script src="info.js" type="module"></script>
import {name,age} form "info.js" // 导入
某些情况下,一个模块中包含的某个功能,并不希望给这个功能命名,而且让导入者可以自己来命名,可以使用export default
(在同一个模块中,不允许同时存在多个)
// info.js
export default function(){
console.log('default function');
}
// main.js
import myFun from './info.js' //myFun是自己命名的
myFun()
若我们希望某个模块中所有的信息都导入,可以通过导入模块中所有的export变量,通常情况下,给起一个别名
import * as aaa from './info.js'
console.log(aaa.name)
5.Webpack
现代的JavaScript应用的静态模块打包工具
Webpack其中一个核心是让我们可以进行模块化开发,且处理模块间的依赖关系,不仅仅是JavaScript文件,CSS、图片、json文件等都可以被当做模块来使用
地址:官网https://www.webpack.js.org
5.1 webpack的打包方式
mathUtils.js
function add(num1,num2) {
return num1 + num2
}
function mul(num1,num2) {
return num1 * num2
}
module.exports = {
add,
mul
}
main.js
// 1.使用commonjs的模块化规范
const {add,mul} = require('./mathUtils.js')
console.log(add(20,30))
console.log(mul(10,2))
// 2.使用ES6的模块化规范
import {name,age,height} from "./info"
console.log(name);
console.log(age);
console.log(height);
info.js
export const name="why";
export const age = 18;
export const height = 1.88;
打包成bundle.js文件
直接在index.html文件中引用
<script src="./dist/bundle.js" type=""></script>
打包方式二:
webpack.config.js配置文件
// 动态获取路径
const path = require('path')
module.exports = {
// 入口
entry: './src/main.js',
// 出口
output: {
path: path.resolve(__dirname,'dist'), // 绝对路径
filename: 'bundle.js'
},
}
package.json
{
"name": "meetwebpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
打包方式三:
package.json
{
"name": "meetwebpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"author": "",
"license": "ISC"
}
局部安装webpack
npm install webpack@3.6.0 --save-dev
{
"name": "meetwebpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies:" {
"webpack": "^3.6.0"
}
}
通过node_modules/.bin/webpack
启动webpack打包
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置:
首先,会寻找本地的node_modules/.bin路径中对应的命令
如果没找到,会去全局的环境变量中寻找
执行build指令:npm run build
5.2 loader
webpack主要用来处理js代码,且webpack会自动处理js之间相关的依赖。但在开发中,也需要加载css、图片、高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue转成js文件等。webpack扩展对应的loader就支持这些转化
5.2.1 loader使用过程
一、通过npm安装需要使用的loader
npm install --save-dev css-loader@2.0.2
npm install --save-dev style-loader@0.23.1
二、在webpack.config.js中的module关键字下进行配置
// 动态获取路径
const path = require('path')
module.exports = {
// 入口
entry: './src/main.js',
// 出口
output: {
path: path.resolve(__dirname,'dist'), // 绝对路径
filename: 'bundle.js'
},
module:{
rules:[
{
test:/\.css$/,
// css-loader只负责将css文件进行加载
// style-loader负责将样式添加到DOM中
// 使用多个loader时,从右向左
use:['style-loader','css-loader']
}
]
}
}
5.2.2 less文件
special.less
@fontSize: 50px;
@fontColor: orange;
body {
font-size: @fontSize;
color: @fontColor;
}
npm install --save-dev less-loader@4.1.0 less@3.9.0
webpack.config.js
// 动态获取路径
const path = require('path')
module.exports = {
// 入口
entry: './src/main.js',
// 出口
output: {
path: path.resolve(__dirname,'dist'), // 绝对路径
filename: 'bundle.js'
},
module:{
rules:[
{
test:/\.css$/,
// css-loader只负责将css文件进行加载
// style-loader负责将样式添加到DOM中
// 使用多个loader时,从右向左
use:['style-loader','css-loader']
},
{
test: /\.less$/,
use: [{
loader: "style-loader"
},{
loader: "css-loader"
},{
loader: "less-loader"
}]
}
]
}
}
5.2.3 图片文件处理url-loader
npm install --save-dev url-loader (图片小于8kb)
npm install --save-dev file-loader (图片大于8kb)
配置webpack.config.js
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片小于limit时,会将图片编译成base64字符串形式
// 当加载的图片大于limit时,需要使用file-loader模块进行加载
limit: 8196,
},
},
],
},
打包后,会发现dist文件夹下多了一个图片文件(名:哈希hash值32位,防止重名)
在真实开发中,可能对打包的图片名字有一定的要求,比如:将所有图片放在同一个文件夹中,跟上图片原来的名字,同时也要防止重复 img/name.hash:8.ext(截取8位)
然后打包 npm run build
5.2.4 ES6语法处理
webpack打包的js文件里面写的ES6语法并没有转成ES5,意味着可能一些对ES6还不支持的浏览器没办法很好的运行代码。
若将ES6语法转成ES5,需要babel
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
webpack.config.js
{
test:/\.js$/,
exclude:/(node_modules|bower_components)/,
use:{
loader:'babel-loader',
options:{
presets:['es2015']
}
}
},
5.2.5 webpack-使用vue的配置过程
1.安装:npm install --save vue
2.导入 main.js
3.挂载vue实例
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word!',
},
});
<div id="app">
<h2>{{message}}</h2>
</div>
4.打包:npm run build
在webpack.config.js中配置
版本
1.runtime-only:代码中,不可以有任何的template
2.runtime-compiler:代码中,可以有template,因为compiler可以用于编译template
5.2.6 el和template区别
正常运行之后,若我们希望将data中的数据显示在界面中,就必须修改index.html,如果后面自定义了组件,也必须修改index.html来使用组件,但是html模板在之后的开发中,不希望手动频繁修改,怎么做?
定义template属性:在Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容。我们可以将div元素值中的{{message}}内容删掉,只保留一个基本的id为div的元素,若依然希望在其中显示{{message}}的内容,我们可以再定义一个template属性
new Vue({
el: '#app',
template:`
<div>
<h2>{{message}}</h2>
<button @click="btnClick"></button>
<h2>{{name}}</h2>
</div>
`
,
data: {
message: 'Hello Word!',
name:'codewhy'
},
methods:{
btnClick(){}
}
});
npm install -D vue-loader vue-template-compiler
const {VueLoaderPlugin} = require('vue-loader')
module: {
{
test:/\.vue$/,
use:['vue-loader']
}
},
plugins:[
new VueLoaderPlugin()
],
5.3 plugin插件
通常用于对某个现有的架构进行扩展。webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化、文件压缩等
loader和plugin的区别
- loader主要用于转换某些类型的模块,它是一个转换器
- plugin是插件,它是对webpack本身的扩展,是一个扩展器
plugin的使用过程
- 通过npm安装需要使用的plugins
- 在webpack.config.js中配置插件
5.3.1 打包html的plugin
在真实发布项目时,发布的是dist文件夹中的内容,若dist文件夹中没有index.html
,那么打包的js等文件也就没有意义了。我们需要将index.html文件打包到dist文件夹中,这时就可以使用HtmlWebpackPlugin插件–自动生成一个index.html文件(可以指定模板来生成);将打包的js文件,自动通过script标签插入到body中
npm install html-webpack-plugin@3.2.0 --save-dev
使用插件,修改webpcak.config.js文件中plugin部分的内容:
template表示根据什么模板来生成index.html
删除之前在output中添加的publicPath属性,否则插入的script标签中的src可能会有问题
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins:[
new webpack.BannerPlugin('最终版权归...所有'),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template:'index.html'
})
],
5.3.2 js压缩的plugin
在项目发布之前,我们必须对js等文件进行压缩处理
npm install uglifyjs-webpack'-plugin@1.1.1 --save-dev
在webpack.config.js中进行配置
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
plugins:[
// new webpack.BannerPlugin('最终版权归...所有'),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template:'index.html'
}),
new UglifyjsWebpackPlugin()
],
5.3.3 搭建本地服务器
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的浏览器自动刷新显示我们修改后的结果
npm install webpack-dev-server@2.9.1 --save-dev
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
- contentBase:为哪一个文件提供本地服务,默认是根文件夹,这里要填写./dist
- port:端口号
- inline:页面实时刷新
- historyApiFallback:在SPA页面中,依赖HTML5的history模式
webpack.config.js文件配置修改如下:
devServer:{
contentBase:'./dist',
inline:true
}
在package.json中我们还可以再配置另外一个scripts:–open参数表示直接打开浏览器
"dev":"webpack-dev-server --open"
运行 npm run dev
打开网页
ctrl+c终止
5.3.4 webpack配置分离
将webpack.config.js分离成三个文件:base.config.js
、dev.config.js
、prod.config.js
合并
npm install webpack-merge@4.1.5 --save-dev
prod.config.js(生产环境)
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
plugins:[
new UglifyjsWebpackPlugin()
]
})
dev.config.js(开发环境)
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
devServer:{
contentBase:'./dist',
inline:true
}
})
修改脚本文件package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
}
npm run build打包到dist
base.config.js修改路径
output: {
path: path.resolve(__dirname, '../dist'), // 绝对路径
filename: 'bundle.js',
},
6.Vue CLI脚手架
使用前提–Node、Webpack
安装:npm install -g @vue/cli
拉取 2.x模板
npm install -g @vue/cli-init
在终端建立项目(下载模板)
6.1 Vue CLI2
1、创建项目
npx vue init webpack 项目名
2、创建过程
项目结构
3、运行
npm run dev
4、runtime-only和runtime-compiler区别
Vue程序的运行过程
runtime-compiler:
template 解析–> ast(抽象语法树) 编译–> render --> virtual dom -->UI(真实DOM)
runtime-only:
render --> virtual dom -->UI
所以,runtime-only
性能更高,代码量更少
const cpn = {
template:`<div>{{message}}</div>`,
data() {
return {
message: '我是组件message'
}
}
}
new Vue({
el: '#app',
render = function(createElement) {
// 1.普通用法:createElement('标签',{标签的属性},[])
return createElement('h2',
{class: 'box'},
['Hello World'],createElement('button',['按钮']))
// 2.传入组件对象
return createElement(cpn)
}
})
参数1:标签名 创建的h2标签会替换掉挂载的app <h2></h2>
参数2:对象 标签属性 <h2 class="box"></h2>
参数3:数组 可以跟标签的内容 <h2 class="box">内容</h2>
6.2 Vue CLI3
1、创建项目
npx vue create 项目名
2、创建过程
①手动选择
②用空格键选中
③选择配置文件
④保存 作为将来创建项目时的预置
项目结构
3、运行
npm run serve
C:\Users\dell.vuerc (可以删除)
配置可视化(图形化界面)
npx vue ui
6.3 箭头函数
<script>
// 定义函数的方式:
// 1.function
const aaa = function() {}
// 2.对象字面量中定义函数
const obj = {
bbb: function() {},
bbb() {}
}
// 3.ES6中的箭头函数
const ccc = (参数列表) => {}
// 2.参数问题
// 2.1 放入两个参数
const sum = (num1, num2) => {
return num1 + num2
}
// 2.2 放入一个参数,可以省略括号()
const power = num => {
return num * num
}
// 3.返回值问题
// 3.1 函数体中有多行语句
const test = () => {
console.log('Hello World');
console.log('Hello Vue.js')
}
// 3.2 函数代码块中只有一行代码
// const mul = (num1,num2) => {
// return num1 * num2
// }
const mul = (num1, num2) => num1 * num2
console.log(mul(20, 30));
// const demo = () => {
// console.log('Hello Demo')
// }
const demo = () => console.log('Hello Demo')
console.log(demo());
</script>
箭头函数的使用:当把一个函数作为参数传入另一个函数
this的使用
箭头函数中的this如何查找?
向外层作用域中,一层层查找this,直到有this的定义
以函数的形式调用时,this永远都是window;
以方法的形式调用时,this就是调用方法的那个对象
<script>
const obj = {
aaa() {
console.log(this); // obj对象{aaa: f}
setTimeout(function () {
console.log(this); // Window
})
setTimeout(() => {
console.log(this); // obj对象{aaa: f}
})
}
}
obj.aaa()
</script>
6.4 Vue-Router 路由
路由就是通过互联的网络把信息从源地址传输到目的地址的活动。
路由器提供了两种机制:路由和转送
路由决定把数据包从来源到目的地的路径
转送将输入端的数据转移到合适的输出端
后端路由阶段
早期的网站开发整个HTML页面都是由服务器来渲染的,服务器直接生产渲染好对于的HTML页面,返回给客户端进行展示。
一个网站,那么多页面服务器如何处理?
- 一个页面有自己对应的网址,即URL
- URL会发送到服务器,服务器会通过正则对该URL进行匹配,并且最后交给一个Controller进行处理
- Controller进行各种处理,最终生成HTML或者数据,返回给前端
- 这就完成了一个IO操作
当我们页面需要请求不同的路径内容时,交给服务器进行处理,服务器渲染好整个页面,并且将页面返回给客户端。这种情况下渲染好的页面,不需要单独加载任何的js和css,可以直接交给浏览器展示,这样也有利于SEO的优化。
缺点: - 整个页面的模块由后端人员来编写和维护
- 前端开发人员如果要开发页面,需要通过PHP和Java等语言来编写页面代码
- HTML代码和数据以及对应的逻辑会混在一起,编写和维护非常糟糕
前后端分离阶段
- 随着ajax的出现,有了前后端分离的开发模式
- 后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中
- 优点-前后端责任的清晰,后端专注于数据,前端专注于交互和可视化
- 当移动端出现后,后端不需要及逆行任何处理,依然使用之前的一套API即可
单页富用应用阶段
SPA-在前后端分离的基础上加了一层前端路由,即由前端来维护一套路由规则
前端路由的核心:改变url,但是页面不进行整体的刷新
URL的hash
URL的hash也就是锚点(#),本质上是改变window.location的href属性
可以通过直接赋值location.hash
来改变href,但是页面不发生刷新
例:location.hash='aaa'
HTML5的history模式
① pushState:相当于入栈
> location.href
< "http://192.168.1.101:8080/examples/urlChange"
> history.pushState({},'','/foo')
< undefined
> location.href
< "http://192.168.1.101:8080/foo"
> history.pushState({},'','/')
< undefined
> location.href
< "http://192.168.1.101:8080/"
②replaceState:不可以返回
> location.href
< "http://192.168.1.101:8080/"
> history.replaceState({},'','/foo')
< undefined
> location.href
< "http://192.168.1.101:8080/foo"
> history.replaceState({},'','/foo/bar')
< undefined
> location.href
< "http://192.168.1.101:8080/foo/bar"
③ go
> location.href
< "http://192.168.1.101:8080/"
> history.go(-1)
< undefined
> location.href
< "http://192.168.1.101:8080/foo"
> history.go(-1)
< undefined
> location.href
< "http://192.168.1.101:8080/examples/urlChange"
> history.go(1)
< undefined
> location.href
< "http://192.168.1.101:8080/foo"
history.back() 等价于 history.go(-1)
history.forward() 等价于 history.go(1)
这三个接口等同于浏览器界面的前进后退
6.4.1 vue-router基本使用
步骤一:安装
npm install vue-router --save
步骤二:在模块化工程中使用(因为它是一个插件,所以可以通过Vue.use()来安装路由功能)
- 导入路由对象,并且调用Vue.use(VueRouter)
- 创建路由实例,并且传入路由映射配置
- 在Vue实例中挂载创建的路由实例
router/index.js
// 配置路由相关信息
import VueRouter from 'vue-router'
import Vue from 'vue'
// 1.通过Vue.use(插件),安装插件
Vue.use(VueRouter)
// 2.创建VueRouter路由对象
const routes = [
]
const router = new VueRouter({
// 配置路由和组件之间的映射关系
routes
})
// 3.将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");
使用vue-router的步骤
- 创建路由组件
- 配置路由映射:组件和路径映射关系
- 使用路由:通过
<router-link>
和<router-view>
<router-link>
:该标签是一个vue-router中已经内置的组件,它会被渲染成一个<a>
标签,属性如下:
- to:用于指定跳转的路径
- tag:指定
<router-link>
之后渲染成什么组件,比如<router-link to='/home'> tag="button"
会被渲染成button按钮 - replace:不会留下history记录,所以指定replace的情况下,后退键返回不能返回到上一个页面中
- active-class:当
<router-link>
对应的路由匹配成功时,会自动给当前元素设置一个router-link-active的class,设置active-class可以修改默认的名称。但是通常不会修改类的属性,直接使用默认的router-link-active即可
<router-view>
:该标签会根据当前的路径,动态渲染出不同的组件。网页的其它内容,比如顶部的标题、导航,或底部的一些版权信息等会和<router-view>
处于同一个级别;在路由切换时,切换的是<router-view>
挂载的组件,其它内容不会发生改变
Home.vue
<template>
<div>
<h2>我是首页</h2>
<p>我是首页内容,哈哈哈</p>
</div>
</template>
<script>
export default {
name:"Home",
data () {
return {
};
},
components: {},
computed: {},
mounted: {},
methods: {}
}
</script>
<style lang='scss' scoped>
</style>
About.vue
<template>
<div>
<h2>我是关于</h2>
<p>我是关于内容,呵呵呵</p>
</div>
</template>
<script>
export default {
name:"About",
data () {
return {
};
},
components: {},
computed: {},
mounted: {},
methods: {}
}
</script>
<style lang='scss' scoped>
</style>
index.js
// 配置路由相关信息
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
// 1.通过Vue.use(插件),安装插件
Vue.use(VueRouter)
// 2.创建VueRouter路由对象
const routes = [
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]
const router = new VueRouter({
// 配置路由和组件之间的映射关系:一个映射关系就是一个对象
routes
})
// 3.将router对象传入到vue实例
export default router
APP.vue
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
</style>
路由的默认路径
默认情况下,进入网站的首页,我们希望<router-view>
渲染首页的内容,让路径默认跳转到首页,并且<router-view>
渲染首页组件,只需多配置一个映射即可:path配置的是根路径,redirect是重定向,即将根路径重定向到/home路径下
{
const routers = [
path:'/',
// redirect重定向
redirect:'/home'
},
默认情况下,路径的改变使用的是URL的hash
如果希望使用HTML5的history模式,进行如下配置即可:
// 2.创建VueRouter路由对象
const router = new VueRouter({
// 配置路由和组件之间的映射关系:一个映射关系就是一个对象
routes,
mode:'history'
})
动态路由
在某些情况下,一个页面的path路径可能是不确定的,比如进入用户界面时,希望是/user/aaa或/user/bbb,除了有前面的/user之外,后面还跟上了用户的ID,这种path和component的匹配关系,称之为动态路由
①新建 User.vue
<template>
<div>
<h2>我是用户界面</h2>
<p>我是用户的相关信息,嘿嘿嘿</p>
<h2>{{userId}}</h2>
<h2>{{$route.params.userId}}</h2>
</div>
</template>
export default {
name:"User",
computed:{
userId() {
return this.$route.params.userId
}
},
②在index.js中添加一个路由对象
{
path:'/user/:userId',
component:User
}
③在App.vue中动态绑定
<router-link :to="'/user/' + userId">用户</router-link>
<script>
export default {
name: 'App',
data(){
return {
userId:'lisi'
}
},
}
</script>
路由的懒加载
官方解释:当打包构建应用时,JavaScript包会变得非常大,影响页面加载。若我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。
白话:首先,我们知道路由中通常会定义很多不同的页面,这个页面最后被打包到一个js文件中,但是,页面这么多都放在一个js文件中,必然会造成这个页面非常大。如果我们一次性从服务器请求下来这个页面,可能需要花费一定的时间,甚至用户的电脑上还出现了短暂空白的情况。如何避免这种情况呢?—>路由懒加载
路由懒加载做了什么?
主要作用就是将路由对应的组件打包成一个个的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');
6.4.2 vue-router嵌套路由
比如在home页面中,我们希望通过/home/news和/home/message访问一些内容。一个路径映射一个组件,访问这两个路径也会分别渲染两个组件。路径和组件的关系如下:
实现路由嵌套有两个步骤:
①创建对应的子组件,并且在路由映射中配置对应的子路由
②在组件内部使用<router-view>
标签
1、创建news和message子组件
2、在index.js中加载路由、创建路由对象、配置路径和路由映射子组件
3、在Home.vue中显示
6.4.3 vue-router参数传递
传递参数主要有两种类型:params
和query
params的类型:
- 配置路由格式:/router/:id
- 传递的方式:在path后面跟上对应的值
- 传递后形成的路径:/router/123,/router/abc
query的类型
- 配置路由格式:/router,也就是普通配置
- 传递的方式:对象中使用query的key作为传递方式
- 传递后形成的路径:/router/?id=123,/router/?id=abc
新建一个Profile.vue组件;
在index.js中配置一个路由对象;
const Profile = () => import('../components/Profile.vue')
const routes=[
{
path:'/profile',
component:Profile
}
]
在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}}</h2>
<h2>{{$route.query.name}}</h2>
<h2>{{$route.query.age}}</h2>
<h2>{{$route.query.height}}</h2>
</div>
</template>
在App.vue中
<template>
<button @click="userClick">用户</button>
<button @click="profileClick">档案</button>
</template>
methods:{
userClick(){
this.$router.push('/user/' + this.userId)
},
profileClick(){
this.$router.push({
path:'/profile',
query:{
name:'kobe',
age:18,
height:2.10
}
})
}
}
6.4.4 vue-router导航守卫
vue-router提供的导航守卫主要用来监听路由的进入和离开,提供了beforeEach
和afterEach
的钩子函数,它们会在路由即将改变前和改变后触发
我们可以利用beforeEach
来完成标题的修改:
首先,在钩子中定义一些标题,用meta来定义;
其次,利用导航守卫,修改标题
导航钩子的三个参数
- to:即将要进入的目标的路由对象
- from:当前导航即将要离开的路由对象
- next:调用该方法后,才能进入下一个钩子
在index.js中
{
path:'/home',
component:Home,
meta:{
title:'首页'
},
},
{
path:'/about',
component:About,
meta:{
title:'关于'
},
},
{
path:'/user/:userId',
component:User,
meta:{
title:'用户'
},
},
// 前置守卫(guard)
router.beforeEach((to,from,next)=>{
// 从from跳转到to
document.title = to.matched[0].meta.title
next()
})
// 后置钩子(hook)
router.afterEach((to,from) => {
})
keep-alive
是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
属性
- include:字符串或正则表达式,只有匹配的组件会被缓存
- exclude:字符串或正则表达式,任何匹配的组件都不会被缓存
router-view也是一个组件,如果直接被包含在keep-alive里面,所有路径匹配到的视图组件都会被缓存