基础知识一
框架和库的区别
- 概念:小而巧的是库,大而全的是框架
- 框架:是一套完整的解决方案,对项目的侵入性很大,项目如果需要更换框架,则需要重新架构整个项目
- 库(插件):提供某一个小功能,对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易的切换到其他库实现需求
MVC和MVVM思想
-
MVC 主要是后端的分层开发思想;把 一个完整的后端项目,分成了三个部分:
- Model:(数据层)主要负责 数据库的操作;
- View:(视图层)所有前端页面,统称为 View 层
- Controller:(业务逻辑层)主要处理对应的业务逻辑;(对于后台来说,这是开发的重点)
- MVVM是前端页面的分层开发思想,主要关注于 视图层 分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View, ViewModel
- Model 是 页面中,需要用到的数据
- View 是页面中的HTML结构;
- ViewModel 是 一个 中间的调度者,提供了双向数据绑定的概念;
- 为什么有了MVC还要有MVVM
- 因为 MVC是后端的开发思想,并没有明确定义前端的页面该如何开发;
- MVVM 是前端的页面的开发思想,把每个页面,分成了三个部分,同时 VM 作为 MVVM 的核心,提供了双向数据绑定的概念,前端程序员,不需要手动渲染页面了,而且,页面数据发送变化,也不需要程序员手动把 数据的变化同步到Model中;这所有的操作,都是 VM 自动完成的!
- 有了 MVVM 的思想以后,前端只关心 页面交互逻辑,不关心页面如何渲染;
Vue.js 基本代码 和 MVVM 之间的对应关系
1. 注意:Vue中,不推荐程序员手动操作DOM元素;所以,在Vue项目中,没有极其变态的需求,一般不要引入 Jquery;
2. Vue代码解析执行的步骤:
1. 当 VM 实例对象,被 创建完成之后,会立即解析 el 指定区域中的所有代码;
2. 当 VM 在解析 el 区域中所有代码的时候,会把 data 中的数据,按需,填充到 页面指定的区域;
3. 注意:每当 vm 实例对象,监听到 data 中数据发生了变化,就会立即 重新解析 执行 el 区域内,所有的代码;
<!-- 1.导入vue的包 -->
<!-- 只要导入了vue的包,在window全局会挂载Vue成员(构造函数) -->
<script src="./lib/vue-2.5.16.js"></script>
</head>
<body>
<!--2. 放一个id为app的div,将来new 出来的vue实例会控制这个div内部的代码 -->
<!-- 注意:不能vm实例对象直接控制body或者是html-->
<div id="app">
<h3>{{msg}}</h3>
</div>
<script>
// 3new 一个Vue,创建Vue的实例对象
// new出来的vm实例对象就是mvvm中的 vm
const vm=new Vue({
// 指定new 出来的vm实例要控制页面上的哪个区域
el:'#app',//这个vm中指定的区域就是mvvm中view
data:{//指定被控制的区域,要用到的数据
msg:'Hello Vue.js'
}//这个data指向对象就是mvvm 中的model
});
// function Person(obj){
// this.name=obj.name;
// this.age=obj.age;
// }
// const p1=new Person({name:'zs',age:20});
</script>
</body>
Vue指令及插值表达式
定义:Vue中,通过一些特殊的用法,扩展了HTML的能力
- 将来 创建 Vue 实例的时候,Vue 会把 这些指令 都进行解析,从而,根据不同的指令,执行不同的操作、渲染不同的结果;
Vue指令之 插值表达式 {{ }}
- 基本使用演示 在指定的位置动态插入内容,例如:
<p>{{msg}}</p>
注意:指令是框架中提出的概念,扩展了html的能力,指令如果想要生效就必须被vm实例对象所解析
- 在插值表达式中 使用简单的表达式,不能写语句
<div id="app">
<h3>{{msg}}</h3>
<h3>{{1+1}}</h3>
<h3>{{boo ? '条件为真':'条件为假'}}</h3>
<h3>{{msg.length}}</h3>
<!--注意插值表达式只能在内容区域,不能在属性节点里面-->
<h3>{{arr}}</h3>
<!--不能写循环等语句-->
</div>
<script>
const vm=new Vue({
el:'#app',
data: {
msg:'Hello Vue.js',
boo:false,
arr:[1,2,3]
},
});
</script>
- 注意:插值表达式只能用在元素的内容区域;不能用在元素的属性节点中; 4.如果对于{{}}的这种形式不满意,可以通过下面的形式修改自定义的形式:
var vm = new Vue({
delimiters:['$','#'],//把插值表达中的双花括号换成是指定的,但是一般不推荐
el: '#app',
data: {
city: '北京',
people: 2000
}
})
5.对于自增和自减运算会出现异常,所以这里暂时不推荐在插值表达中使用自增自减元素。
Vue指令之v-cloak
- 解决的问题
- 插值表达式有闪烁的问题(v-cloak 指令来解决闪烁问题)
[v-cloak]{
display: none;
}
- 应用场景
- 当 网络比较卡的时候,我们可以为 最外层的 元素,添加 v-cloak ,防止用户看到 插值表达式
- 原理
- 通过vm创建完成之后,动态的移除v-cloak的属性,从而显示插值表达式的节点
Vue指令之v-text
1.基本使用 在元素的属性节点上,添加v-text 命令,例如:
<p v-text="msg"></p>
2.v-text中也可以使用简单的语句 3.v-text与{{}}的区别
- v-text会覆盖所有内容 {{}}不会覆盖
- v-text不会出现闪烁问题,但是{{}}会出现闪烁问题
4.应用场景(v-text)
- 向指定元素的内容区域中,渲染指定的文本
Vue指令之v-html
1.基本使用 在元素的属性节点上,添加v-text 命令,例如:
<!-- 总结:
vue 中的指令只有插值表达式是用在内容节点上的,
其他的所有指令都是用在属性节点的
-->
<p v-text="msg"></p>
2.应用场景 当服务器返回的数据中,包含的html的标签,此时,这些标签只能在v-html来渲染
Vue指令之 v-bind: 属性绑定(自定义属性和固有属性)
添加在元素节点身上的属性都可以通过v-bind进行绑定(id width src)等
1.基本使用
- v-bind:是为html属性节点动态绑定数据的,例如:
<div id="app">
<!-- v-bind:指令表示属性绑定,
可以在v-bind中写一些简单的表达式
今后在开发中非常常用
v-bind:指令可以简写为英文的:代表的是属性绑定
-->
<button :title="titleStr">按钮</button>
<img :src="boo ? img:img1" alt="">
<!-- <img v-bind:src="img1" alt=""> -->
</div>
<script>
// 当vm实例被创建完毕后会立即解析执行el区域内所有vue的指令
// 而且只要data数据中发生了变化,就会立即重新解析数据
const vm=new Vue({
el:'#app',
data:{
titleStr:'这是title属性值',
boo:false,
img:'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2391791031,3623366227&fm=26&gp=0.jpg'
,img1:'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=486958716,243120904&fm=26&gp=0.jpg'
}
})
</script>
2.应用场景
- 如果元素的属性值,需要动态地进行绑定,则需要使用v-bind: 指令
Vue指令之v-on:事件绑定
- 基本使用: v-on: 的作用,是为 HTML 元素,绑定 事件处理函数,例如:
<input type="button" value="按钮" v-on:click="事件处理函数名" />
- 绑定事件处理函数并传参:
<input type="button" value="按钮" v-on:click="show(123)" />
关于参数问题:
1. 有传递使用传递的参数
2. 没有声明(),第一个形参就是事件对象object MouseEvent
3. 有声明(),还没有传递实参,形参就是undefined
methods: {
// 成员方法
exp(id){
console.log('商品被删除了'+id);
}
// 不传递实参的情况下是undefined
// 不加括号[object MouseEvent]
},
- 简写形式: v-on: 指令可以简写成 @,例如,可以简写成如下格式:
<input type="button" value="按钮" @click="事件处理函数名" />
Vue指令之v-modle实现双向绑定
1.基本使用:
- 可以把页面上数据的变化,自动同步更新到 VM 实例的 data 中。例如:
<input type="text" v-model="msg"/>
2.和v-bind的区别
- v-bind: 只能实现单向的数据同步 data ---> 页面;
- v-model 可以实现双向的数据同步 data <--> 页面;
- 在写法上v-bind可以简写为:的形式.v-model没有简写方式
- v-model ="msg",:v-bind:src="mySrc",他们里面的属性值都需要在data中设置,哪怕没有值也需要设置为空字符串。 3.注意:
- v-model只能和表单元素配合使用,例如input select textarea等
- v-model是Vue中唯一支持双向数据绑定的指令.
4.v-model简易版原理整理 v-model的原理就给input输入框中定义oninput事件,在该事件中把用户输入的信息都给随时获得到,并对data成员进行赋值
data成员变化了,页面上用到的地方就重新渲染,达成简易双向绑定的效果 代码如下:
<div id="app">
<p>{{city}}</p>
<!-- 这里的$event就代表的是event -->
<input type="text" @input="city=$event.target.value" :value="city">
<input type="text" @input="feel" :value="city">
</div>
<script src="./vue.js"></script>
<script>
var vm=new Vue({
el:'#app',
data:{
city:'北京'
},
methods: {
feel(evt){
// console.log(evt);
// console.log(evt) // InputEvent对象
// evt.target:代表触发当前事件的html元素dom对象,具体是input框对象
// evt.target.value: 随时感知输入框输入的信息
// 把随时输入的信息赋予给city,这样city变化,由于“重新渲染”,页面上用到的地方就更新了
// 就达成v-model双向绑定的效果了
this.city=evt.target.value;
}
},
})
</script>
5.应用场景: 简易计算器的实现:
<body>
<div id="app">
<!-- 第一个运算的数值 -->
<input type="text" v-model="n1">
<select v-model="opt">
<option value="+">+</option><br>
<option value="-">-</option><br>
<option value="*">*</option><br>
<option value="/">/</option><br>
</select>
<!-- 第二个元素的数值 -->
<input type="text" v-model="n2">
<!-- 注意这里不要写错 -->
<button @click="calc">=</button>
<!-- 运算的结果 -->
<input type="text" readonly :value="result">
</div>
<script>
const vm=new Vue({
el:'#app',
data:{
n1:0,
n2:0,
opt:'+',
result:0
},
methods: {
calc(){
switch(this.opt){
case '+':
this.result= parseFloat(this.n1)+parseFloat(this.n2);
break;
case '-':
this.result= parseFloat(this.n1)-parseFloat(this.n2);
break;
case '*':
this.result= parseFloat(this.n1)*parseFloat(this.n2);
break;
case '/':
this.result= parseFloat(this.n1)/parseFloat(this.n2);
break;
}
}
},
})
</script>
</body>
this 操控data
根据业务需要,事件在执行过程中需要对Vue实例的data数据进行操作,通过this关键字实现,
this代表Vue实例对象,并且针对data或methods成员都可以直接进行调用
在Vue中使用class样式
1.类名数组
- 通过 v-bind: 为元素的 class 绑定具体的类名:
<p :class="['thin', 'red', 'big']">哈哈哈</p>
2.类名数组中使用三元表达式,按需为元素添加某些类名
<button @click="getInfo()" >获取数据</button>
<script>
var vm = new Vue({
el:'#app',
data:{
address:'铁岭'
},
methods:{
getInfo:function(){
// 通过 this关键字 获得 data区域的数据信息
console.log(this.address+'是一个大城市');
}
}
})
</script>
<p :class="['thin', flag ? 'red' : '']">哈哈哈</p>
3.应用场景
- 网页开关灯
<div id="app" :class="[flag ? 'light':'dark']">
<button @click="flag=!flag">切换</button>
<h1>{{msg}}大渣好,我系咕天乐,我系渣渣辉,贪挽难约,介系一个你没有挽过的船新版本,挤需体验3番钟,里就会干我一样,挨上节款游戏</h1>
<img :src="flag ? img1:img2" alt="">
</div>
<script>
const vm=new Vue({
el:'#app',
data:{
//false是黑夜,true是白天
flag:false,
img1:'./images/6.jpg',
img2:'./images/段段.jpg'
}
})
</script>
在Vue中使用style样式
style属性也比较特殊,其可以给标签设置许多css样式,在vue中可以做如下应用
- 对象语法
<div :style="{color: 'red', 'font-size': '20px', fontWeight:'bold' }"></div>
- 数组语法
<div :style="[{color: 'red'}, {'font-size': '20px', fontWeight:'bold' }]"></div>
在一个数组元素中可以绑定多个或一个样式成员 有的样式属性名称是通过"-"中横线连接的,这在javascript的命名规则中不允许的,例如font-size、font-weight,在处理时有两种方式
- 引号限定 如 'font-size'
- 中横线去除,后边首字母大写 如 fontWeight
以上对象或数组绑定class语法均渲染为:
<div style="color:red; font-size:20px; font-weight:bold"></div>
通过 数组 或 对象 对 class/style 进行绑定的好处是,属性值可以嵌入编程内容,实现精细化控制
Vue指令之v-for和:key属性
1.基本用法:
- 普通数组 (一般般)
<li v-for="(item,i) in list1">{{item}}---索引{{i}}</li>
- 对象数组(用的最多)
<li v-for="(item,i) in list2">{{item.id}}---{{item.name}}---索引值{{i}}</li>
- 迭代对象中的属性
- 迭代数字 (这两种方法平时不怎么用,所以这里不详细介绍了)
2.:key的用法
<li v-for="item in list2" :key="item.id">{{item.id}}---{{item.name}}---索引值{{i}}</li>
注意:今后只要用到了id值,就一定要为循环的每一项,添加:key属性绑定,而且key的值最好绑定到id值上,key的值一定要唯一,单独的使用索引值也是不可以的,因为这个key值是用来标识数据唯一性的,通过key绑定的数据项和数据状态(比如复选框的选中伪选中关系)之间的关系
Vue中v-if和v-show指令
- v-if 和 v-show 的作用,都是切换界面上元素的显示或隐藏的;
<div id="app">
<button @click="flag=!flag">Toggle</button>{{flag}}
<!-- v-if是通过动态创建或者移除元素实现动态切换 -->
<h3 v-if="flag">奔跑的五花肉</h3>
<hr>
<!-- v-show是通过控制元素的display:none样式实现切换 -->
<h3 v-show="flag">2432432</h3>
</div>
一般来说,v-if 有更高的切换消耗 而 v-show 有更高的初始渲染消耗。
因此,如果需要频繁切换 v-show 较好,如果在运行时条件不大可能改变 v-if 较好。
修饰符
事件修饰符(用来修饰事件的,如果点击事件)
.prevent 阻止默认行为(使用最频繁)
<a href="http://www.baidu.com" @click.prevent="show">百度</a>
.once 只触发1次(几乎不常用)
<button @click.once="btnHandler">按钮</button>
.stop阻止冒泡
<button @click.stop="btnHandler">按钮</button>
.self只有在当前元素上触发事件的时候,才会调用处理函数
<div class="inner" @click.self="innerHandler"></div>
按键修饰符
按键修饰符是配合文本框的使用的 .enter
<input type="text" v-model="password" @keyup.enter="login"><br>
.tab
<input type="text" v-model="password" @keyup.tab="login"><br>
.esc
<input type="text" v-model="password" @keyup.esc="login"><br>
基础知识二
Vue过滤器
"2018-01-25T02:10:02.945Z" => 2018-01-25
概念:过滤器的本质就是一些函数,可被用作一些常见的文本格式化
- 过滤器只能用在两个地方,插值表达式和v-bind表达式中
<td :title="item.ctime |dateFormate">{{item.ctime | dateFormate}}</td>
- 过滤器应该被添加在javascript表达式的尾部,由 管道 符指示
全局过滤器
1.使用全局过滤器的语法
<span>{{ dt | 过滤器的名称 }}</span>
|的作用就是调用一个过滤器
2.定义全局过滤器的语法
- Vue.filter('过滤器的名称', function(originVal){ /* 对数据进行处理的过程,在这个 function 中,最后必须 return 一个处理的结果 */ })
注意:全局过滤器必须定义在new Vue()之前
3.使用过滤器的注意事项
- 如果想拿管道符前面的值,通过 function 的第一个形参来拿
- 过滤器中,一定要返回一个处理的结果,否则就是一个无效的过滤器
- 在调用过滤器的时候,直接通过 () 调用就能传参; 从过滤器处理函数的第二个形参开始接收传递过来的参数
- 可以 多次 使用 | 管道符 一次调用多个过滤器
<div id="app">
<!-- 在插值表达式中可以使用|管道符来调用指定的过滤器 -->
<!-- 如果调用过滤器了,则在这个内容区域显示的内容是过滤器方法最终返回的处理结果 -->
<!-- 过滤器只是对原有的数据做了一层包装,并没有修改原来的值 -->
<h3>{{dt | dateFormate}}</h3>
<p>{{dt}}</p>
</div>
<script>
// 通过Vue.filter定义一个全局过滤器
// 注意,调用过滤器的时候,管道符前面的值,必须通过function的第一个参数接收
// Vue.filter('过滤器的名称',function(originVal){//过滤器的处理函数
// })
Vue.filter('dateFormate',function(originVal){
//最终一定要返回一个处理结果
// return originVal+'-------'
const dt=new Date(originVal);
const y=dt.getFullYear();
const m=(dt.getMonth()+1+'').padStart(2,'0');
const d= (dt.getDate()+'').padStart(2,'0');
const hh= (dt.getHours()+'').padStart(2,'0');
const mm=(dt.getMinutes()+'').padStart(2,'0');
const ss= (dt.getSeconds()+'').padStart(2,'0');
const dtstr=`${y}-${m}-${d} ${hh}:${mm}:${ss}`;
return dtstr;
});
const vm=new Vue({
el:'#app',
data:{
dt:'2018-01-25T02:10:02.945Z'
},
methods: {
},
})
</script>
私有过滤器
const vm=new Vue({
el:'#app',
data:{
dt:'2018-01-25T02:10:02.945Z'
},
methods: {
},
// 注意是跟methods同级
// 注意:私有的过滤器带s,全局过滤器不带过滤器
// 私有过滤器的名称:function (){//私有过滤器的处理函数}
// 过滤器是按照就近原则进行调用的,先调用私有的,如果私有过滤器先看私有过滤器,没有再看全局过滤器
filters:{
// es3写法
// dateFormate:function(originVal){
// return originVal+'-----'
// }
// es6写法
dateFormate(originVal){
return originVal+'-----'
}
}
})
过滤器是按照就近原则进行调用的,先调用私有的,如果私有过滤器先看私有过滤器,没有再看全局过滤器
带参数的过滤器
有的时候,过滤器主体业务逻辑不变化的情况下,可能结果的形式根据业务要求有所调整,为了增强灵活度,可以通过传递参数实现。
Vue实例.$mount()动态挂载实例
语法:Vue.$mount("选择器 - 指定要控制的区域")
const vm=new Vue({
data:{
msg:'Hello'
}
});
//mount是挂载的意思,表示 手动指定当前的vm实例要控制的区域
vm.$mount('#app');
template属性指定模块
语法:template:'<h6>{{msg}}</h6>'
const vm=new Vue({
el:'#app',
data:{
msg:'Hello'
},
methods: {},
filters:{},
// 指定当前vm要渲染的模版
// 结论:如果同时指定了el和template,那么template会把el区域替换掉
template:'<h6>{{msg}}</h6>'
})
Vue的生命周期
生命周期
概念 : 实例的生命周期,就是一个阶段,从创建到运行,再到销毁的阶段;
生命周期函数
在实例的生命周期中,在特定阶段执行的一些特定的事件,这些特定的事件叫做生命周期函数
- 生命周期函数=生命周期钩子=声明周期事件
主要的生命周期函数分类
- 创建期间的生命周期函数(特点:每个实例一辈子只执行一次)
- beforeCreate:创建之前,此时data和methods方法尚未初始化,还不能访问里面的数据
- created:(第一个重要的函数,此时data和methods已经创建好了,里面的数据还有方法都可以被访问了)
- beforeMounted:挂载模版之前,此时页面还没有被渲染出来
- mounted:(第二个重要的函数,此时,页面刚被渲染出来,,如果要操作DOM元素,最好在这个阶段)
- 运行期间的生命周期函数(特点,按需被调用,至少0次,最多N次)
- beforeUpdate :数据是最新的,但是页面还是旧的页面
- updated 页面和数据都是最新的
- 销毁期间的声明周期函数(特点:每个实例一辈子只执行一次)
- beforeDestroy:销毁之前,实例还可以正常使用
- destroyed :销毁之后,实例已经不存在,无法工作了
axios的使用
<script>
// 通过这个属性,全局设置 请求的 根路径
axios.defaults.baseURL = 'http://www.liulongbin.top:3005'
// 将来项目中都这么搞
Vue.prototype.$http = axios
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {
async getInfo() {
const { data: res } = await this.$http.get('/api/get', { params: { name: 'zs', age: 22 } })
console.log(res)
},
async postInfo() {
const { data: res } = await this.$http.post('/api/post', { name: 'zs', age: 22 })
console.log(res)
}
}
});
</script>
注意:axios这个库可以在任意的请求中使用,他们发送get和post请求时的参数是不相同的,get请求参数是params而那个post请求,请求参数是data,但是这里太过麻烦,就用对象结构赋值的方式将代码进行解构
axios的缺点
- 只支持 get 和 post 请求,无法发起 JSONP 请求;
- 如果涉及到 JSONP 请求,可以让后端启用 cors 跨域资源共享即可;
在Vue中,还可以使用 vue-resource 发起数据请求
- vue-resource 支持 get, post, jsonp请求【但是,Vue官方不推荐使用这个包了!】
基础知识三
Vue中的动画
Vue中动画的基本介绍
使用动画的原因
- 动画能够增加页面的趣味性,目的是为了让用户更好的理解页面的功能
注意:Vue中的动画,都是简单的动画过渡,并不会有CSS3那么炫酷
动画简单介绍
1.每个动画都是由两部分组成的:
- 入场动画:从不可见(flag = false) -> 可见(flag = true)
- 出场动画:可见(flag = true) -> 不可见(flag = false)
- 入场时候,Vue把这个动画,分成了两个时间点和一个时间段:
- v-enter:入场前的样式
- v-enter-to:入场完成以后的样式
- v-enter-active:入场的时间段
- 离场时候,Vue把动画,分成了两个时间点和一个时间段:
- v-leave:离场之前的样式
- v-leave-to:离场完成以后的样式
- v-leave-active:离场的时间段
动画图示:
总结:带active的都是时间段的效果,
带to的都是完成之后的状态,
什么也不带的都是开始时的状态
也就是说动画都是从哪来,从哪去
使用过渡类名
- 把需要添加动画的元素,使用v-if或v-show进行控制
- 把需要添加动画的元素,使用Vue提供的元素 包裹起来
- 添加两组类:
<style>
/* 定义元素入场之间和离场之后的位置 */
/* 注意:他在入场之前并不是标准里的位置 */
.v-enter,
.v-leave-to {
transform: translateX(150px);
opacity: 0;
}
/* 定义元素,入场阶段,和离场阶段的过渡效果 */
.v-enter-active,
.v-leave-active {
transition: all 0.8s;
}
/* 动画完成之后,默认的位置就是标准流中的效果 */
/* 定义元素在标准流中的效果, 这个标准流的效果就是入场完成之后,移入离场开始之前,元素的效果*/
h3 {
transform: translateX(50px);
opacity: 0.5;
}
/* 通过类名,可以设置页面上的多组动画效果 */
.test-enter,.test-leave-to{
opacity: 0;
transform: translateY(200px);
}
.test-enter-active,
.test-leave-active{
transition: all 0.8s;
}
</style>
此时transition身上的name属性也要改成对应的属性值
<div id="app">
<button @click="flag=!flag">Toggle</button>
<!-- 1.使用vue框架提供的transition标签,把需要添加过渡的效果包裹起来 -->
<transition name="">
<h3 v-if="flag">函数哈哈胡莎莎</h3>
</transition>
<hr>
<button @click="flag2=!flag2">Toggle</button>
<transition name="test">
<h6 v-if="flag2">这是第二个元素</h6>
</transition>
</div>
注意:v-if和v-show在前面讲过是控制元素的显示和隐藏,具体的操作可以再回顾一下上面的笔记.
使用第三方的css动画库
- 把需要添加动画的元素,使用v-if或v-show进行控制
- 把需要添加动画的元素,使用Vue提供的元素 包裹起来
- 为 添加两个属性类enter-active-class, leave-active-class
- 把需要添加动画的元素,添加一个 class="animated"
<div id="app">
<button @click="flag=!flag">Toggle</button>
<!-- 指定入场的类名 -->
<transition enter-active-class="bounceInDown" leave-active-class="bounceOutDown">
<h3 v-if="flag" class="animated"> 哇哈哈哈哈哈哈</h3>
</transition>
</div>
v-for的列表过渡
- 把v-for循环渲染的元素,添加 :key 属性[注意:如果要为列表项添加动画效果,一定的指定key,并且key的值不能是索引]
- 在 v-for循环渲染的元素外层,包裹 标签
- 添加两组类即可:
<!-- transition-group这个标签要放在ul里面,并且这个标签会自动的渲染为span -->
<!-- 但是这里也可以强制的将这个标签渲染为ul,这时,外面的ul属性也不需要设置了 -->
<transition-group tag="ul">
<li v-for="item in list" :key="item.id">
{{item.id}}---{{item.name}}
</li>
</transition-group>
列表的排序过渡
组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move 特性,它会在元素的改变定位的过程中应用。
- v-move 和 v-leave-active 结合使用,能够让列表的过渡更加平缓柔和:
<style>
/* 为即将被删除的元素,添加样式,让他脱离标准流 */
.v-leave-active{
position: absolute;
width: 100%;
}
/* 通过.v-move这个类,可以让后续的元素,通过过渡,渐渐的顶上去 */
/* 如果不加这个类的话,下面的元素会一下就顶上去,也就没有了过渡的效果 */
.v-move{
transition: all 0.8s;
}
</style>
Webpack
什么是webpack?
- 什么是webpack:webpack 是前端项目的构建工具;前端的项目,都是基于 webpack 进行 构建和运行的;
- 为什么要使用webpack:
- 如果项目使用 webpack 进行构建,我们可以书写高级的ES代码,且不用考虑兼容性;
- webpack 能够优化项目的性能,比如合并、压缩文件等;
- 基于webpack,程序员可以把 自己的开发重心,放到功能上;
- 什么项目适合使用webpack:
- webpack 非常适合与 单页面应用程序(SinglePageApplication) 结合使用;
- vue, react, angular 只要用前端三大框架开发项目,必然会使用webpack工具;
- 不太适合与多页面的普通网站结合使用;
- webpack 非常适合与 单页面应用程序(SinglePageApplication) 结合使用;
- 根据webpack官网的图片介绍webpack打包的过程
- webpack分很多版本 1.x 2.x 3.x 4.x
ES6导入导出语法
由于下面的笔记,在配置的时候可能会遇到ES6中导入导出的语法,所以这里提前在笔记中记录一下。
在webpack中一切皆模块,这里主要是ES6中模块化的导入和导出。
在webpack中,每个js文件都需要独立的模块,每个模块都有独立的作用域,其他模块默认无法直接访问当前模块中定义的成员。
默认导入导出方式
1.默认导入
默认导入的语法可以使用任何合法的名称来进行接收
import 接收名称 from '模块名称'
2.默认导出语法:
export default {
a:a
}
按需导入和导出
- 按需导入语法: import { 成员名称 } from '模块名称'
- 按需导出语法: export var a = 10
在项目中安装和配置
webpack 是前端的一个工具,这个工具,可以从NPM官网上下载到本地使用;
- 新建一个项目的空白目录,并在在终端中,cd到项目的根目录,执行npm init -y 初始化项目
- 装包:运行 npm i webpack webpack-cli -D 安装项目构建所需要的 webpack
- 打开 package.json文件,在 scripts 节点中,新增一个 dev 的节点:
- 在项目根目录中,新建一个 webpack.config.js 配置文件,内容如下
{
"name": "code2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev":"webpack"
},
- 在项目根目录中,新增一个 src 目录,并且,在 src 目录中,新建一个 index.js 文件,作为 webpack 构建的入口;会把打包好的文件输出到 dist -> main.js
- 在终端中,直接运行 npm run dev 启动webpack进行项目构建;
实现webpack的实时打包构建
- 借助于 webpack-dev-sever 这个工具,能够实现 webpack 的实时打包构建;
- 运行npm i webpack-dev-server -D 安装包
- 打开package.json文件,把 scripts 节点下的 dev 脚本,修改为如下配置:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server"
},
- 修改 index.html 文件中的 script 的 src, 让 src 指向 内存中根目录下的 /main.js
<script src="/main.js"></script>
使用html-webpack-plugin插件配置启动页面
- 装包npm i html-webpack-plugin -D
- 在 webpack.config.js中,导入 插件:
// 导入 html-webpack-plugin,从而帮我们自动把 打包好的 main.js 注入到 index.html 页面中
// 同时,html-webpack-plugin 可以把 磁盘上的 index.html 页面,复制一份并托管到 内存中;
const HtmlPlugin = require('html-webpack-plugin')
const htmlPlugin = new HtmlPlugin({
// 传递一个配置对象
template: './src/index.html', // 指定路径,表示 要根据哪个物理磁盘上的页面,生成内存中的页面
filename: 'index.html' // 指定,内存中生成的页面的名称
})
- 把 创建好的 htmlPlugin 对象,挂载到 plugins数组中:
// webpack 这个构建工具,是基于 Node.js 开发出来的一个前端工具
module.exports = {
mode: 'development', // 当前处于开发模式
plugins: [htmlPlugin] // 插件数组
}
实现自动打开浏览器、热更新和配置浏览器的默认端口号
- --open 自动打开浏览器
- --host 配置IP地址
- --port 配置 端口号
- --hot 热更新;最新的代码,以打补丁的形式,替换到页面上,加快编译的速度;
webpack打包非js文件
webpack打包css文件
由于webpack只能打包js文件,所以对于非js文件就需要单独进行处理
使用webpack打包css文件
- 运行 npm i style-loader css-loader -D
- 打开 webpack.config.js 配置文件,在 module -> rules 数组中,新增处理 css 样式表的loader规则:
module: { // 所有 非.js 结尾的第三方文件类型,都可以在 module 节点中进行配置
rules: [ // rules 是匹配规则,如果 webpack 在打包项目的时候,发现,某些 文件的后缀名是 非 .js 结尾的
// webpack 默认处理不了,此时,webpack 查找 配置文件中的 module -> rules 规则数组;
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]
}
注意:由于使用非js文件打包,都需要对他进行loader的出来,但是这里css-loader也有他自己的调用顺序,这里这个loader的调用时逆向的,先调用css-loader,然后再调用style-loader
使用webpack打包less文件
- 运行 npm i less-loader less -D
- 在 webpack 的配置文件中,新增一个 rules 规则来 处理 less 文件:
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }
注意:只要是样式表,在他的loader配置中都需要加上style-loader和css-loader这两个选项。
使用webpack处理css中的路径
- 运行 npm i url-loader file-loader -D
- 在 webpack 的配置文件中,新增一个 rules 规则来 处理 图片 文件:
{ test: /\.jpg|png|gif|bmp$/, use: 'url-loader' }
webpack中使用bootstrap
- 运行 npm i bootstrap 对bootstrap装包
- 在webpack中使用bootstrap本身没有问题,但是由于boostrap本身引入了其他的字体的样式表,这里又只能打包的是js文件,所以这里还需要字体样式表的loader
{ test: /\.eot|woff|woff2|ttf|svg$/, use: 'url-loader' }
默认情况下,如果导入的模块是路径,webpack会优先去node_modules目录下,查找指定的路径是否存在
注意:这里打包字体文件和loader和打包处理图片的loader都是url-loader
使用babel处理高级JS语法
1.由于webpack默认只能打包处理一部分高级的js的语法,如果某些js语法,过于高级,则webpack也是处理不了的,此时只能借助于babel这个插件,来打包处理高级的js语法 2.运行两套命令,去安装相关的 loader:
- 运行 npm i babel-core babel-loader babel-plugin-transform-runtime -D
- 运行 npm i babel-preset-env babel-preset-stage-0 -D
- 添加 babel-loader 配置项:
// 注意:在配置 babel-loader 的时候,一定要添加 exclude 排除项,把 node_modules 目录排除
// 这样,只让 babel-loader 转换 程序员 自己手写的 JS 代码;
// 好处:1. 能够提高编译的转换效率; 2. 能够防止不必要的报错!
{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
- 在项目根目录中,添加 .babelrc 配置文件:
{
"presets": ["env", "stage-0"],
"plugins": ["transform-runtime"]
}
基础知识四
在 webpack 中安装和配置 vue
- 运行 npm i vue -S 把 vue 安装到项目依赖
- 在 index.js 中使用 import 导入 vue 模块:
import Vue from 'vue'
- 在 index.html 中创建将来要被 vm 实例控制的 div:
<!-- 将来,这个 div 就是 Vue实例 要控制的区域 -->
<div id="app"></div>
- 在 index.js 中创建 vm 实例,并使用 el 指定要控制的区域,使用 data 指定要渲染的数据:
const vm = new Vue({
el: '#app', // 要控制的区域
data: {
msg: 'ok' // 要渲染的数据
}
})
为什么在基于 webpack 的 vue项目中, 按照如上操作会报错呢
- 因为使用 import 导入的 vue 模块,导入的并不是功能最全的 vue 包;而是删减版的;
- 删减版的 vue 包中功能很少,目的是为了减小vue 包的体积,因为文件越小,网络请求越快!
- 如何让 import 导入最全的 vue 包呢?
- 把 import Vue from 'vue' 改写为 import Vue from 'vue/dist/vue.js'
- 注意:在学习阶段,可以暂时 import 最全的 vue 包;等开发项目的时候,一定要使用 删减版的 包;
定义Vue组件
模块化和组件化的概念解读
- 什么是模块化:是从代码的角度分析问题;把可复用的代码,抽离为单独的模块;
- 模块化的好处:
- 提供模块作用域的概念,防止全局变量污染;
- 提高了代码的复用率,方便了程序员之间 共享代码;
- 模块化的好处:
- 什么是组件化:组件化是从页面UI的角度进行分析问题的;把页面中可复用的UI结构,抽离为单独的组件;
- 组件化的好处:
- 方便了UI结构的重用;
- 随着项目开发的深入,手中可用的组件会越来越多;
- 可以直接使用第三方封装好的组件库;
- 组件化能够让程序员更专注于自己的业务逻辑;
- 组件化的好处:
定义全局组件
1.定义组件的语法
-
Vue.component('组件的名称', { 组件的配置对象 });
-
在组件的配置对象中:可以使用 template 属性指定当前组件要渲染的模板结构;
Vue.component('my-test',{
template:`<div>这是我定义的第一个Vue组件</div>`
});
注意:这里的组件名称最好都写成是小写的,并且中间最好用-连接一下
- 在组件的配置对象中:可以使用 template 属性指定当前组件要渲染的模板结构;
2.使用组件的语法(全局组件)
-
把 组件的名称, 以标签的形式,引入到页面上就行; 注意:
-
从更抽象的角度来说,每个组件,就相当于是一个自定义的元素;
-
组件中的DOM结构,有且只能有唯一的根元素(Root Element)来进行包裹!
Vue.component('my-test',{
template:`<div>
<div>这是我定义的第一个Vue组件</div>
<div>这是我定义的第一个Vue组件</div>
</div>`
});
3.使用组件的语法(私有组件)
注意:定义私有组件,只能在定义区域内使用,超出定制区域内使用无效,会报错误
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello World!'
},
components: {
//使用compoents属性定义的组件,叫做是私有组件
// '组件名称':{/组件的配置对象/}
'my-test2': {
template: `<div>这是私有组件</div>`
}
}
})
组件中定义data数据、methods方法以及生命周期函数
组件中定义data数据
Vue.component('my-test', {
template: `<div>
<div>这是我定义的第一个Vue组件{{d1}}</div>
<div>这是我定义的第一个Vue组件</div>
</div>`,
// 注意Vue规定,组件中的data必须是function函数,而且必须return一个对象
data(){
// return 当前实例对象
return {
d1:'111'
}
}
});
区分vm实例中定义data数据
//在vm实例中,data既可以是对象也可以方法,但是在组件中只能是方法
const vm = new Vue({
el: '#app',
// data: {
// msg: 'Hello World!'
// },
data() {
return {
msg: 'Hello World!'
}
},
components: {
//使用compoents属性定义的组件,叫做是私有组件
// '组件名称':{/组件的配置对象/}
'my-test2': {
template: `<div>这是私有组件</div>`
}
}
})
组件中定义自己的methods,fifters以及生命周期函数
Vue.component('my-test', {
template: `<div>
<div @click="show">这是我定义的第一个Vue组件{{d1}}</div>
<div>这是我定义的第一个Vue组件</div>
</div>`,
// 注意Vue规定,组件中的data必须是function函数,而且必须return一个对象
data(){
// return 当前实例对象
return {
d1:'111'
}
},
//组件中也可以有自己的私有方法
methods: {
show(){
console.log('调用了自定义组件中的show方法')
}
},
//组件中也能有自己的私有过滤器
filters:{
testFilter(originval){
return originval+'~~~'
}
} ,
// 组件也可以有自己的声明周期函数
created() {
console.log('调用了created函数')
; },
组件和实例的区别
-
组件中的 data 必须是一个 function 并 return 一个 字面量对象;在 Vue 实例中,实例的 data 既可以是 对象,可以是 方法;
-
组件中,直接通过 template 属性来指定组件的UI结构;在 Vue 实例中,通过 el 属性来指定实例控制的区域;但是实例也可以使用 template;
-
组件和实例,都有自己的生命周期函数,私有的过滤器,methods 处理函数;
为什么组件中的 data 属性必须定义为一个方法并返回一个对象
主要是内存中存储的地址不一样,具体操作可以看下图的指示:
.vue单文件组件
-
为什么要把组件,单独的定义到 .vue 文件中?
- 之前创建组件太麻烦,没有智能提示和代码高亮;
- 之前定义组件,和其它JS代码逻辑掺杂在一块儿,代码不易维护,没有把组件化的优势发挥到极致!
-
每个 .vue 文件,都是一个 vue 组件(叫做 单文件组件),它由三部分组成:
- template 结构
- script 行为
- style 样式
<template>
<div>
<!--注意单文件中的template节点只能有唯一的父元素进行包裹 -->
<h3>这是使用.vue文件定义单文件组件---{{msg}}</h3>
</div>
</template>
<script>
// 行为中固定写法
// 当前组件中私有的data数据
export default {
data(){
return {
msg:'hello .vue文件'
}
},
methods:{},
filters:{},
created(){}
}
</script>
<style >
h3{
color: red;
}
</style>
在webpack中配置.vue组件页面的解析
// 导入单文件组件
import Home from './components/HOME.vue';
//把单文件组件,以Vue.compondent注册为全局组件
Vue.component('my-home',Home);
从上述代码中可以看出,当前导入的是.vue文件,不是以js为后缀名的文件,同时也不是我们之前处理的文件类型,所以这里在浏览器运行就会报出一个没有loader的错误.
- 运行npm i vue-loader vue-template-compiler -D
- 添加rules匹配规则:
{ test: /\.vue$/, use: 'vue-loader' }
- 在webpack.config.js中导入并配置插件:
// 导入插件
const VueLoaderPlugin = require('vue-loader/lib/plugin')
// new 一个插件的实例对象
const vuePlugin = new VueLoaderPlugin()
// 把 new 出来的插件实例对象,挂载到 `plugins` 节点中:
plugins: [...其它插件, vuePlugin]
导入并使用 .vue 组件的两种方式
全局注册 .vue 文件:
- 在 index.js 中导入 .vue 组件
- 使用 Vue.component() 把导入的 .vue 组件,注册为全局组件
// 导入单文件组件
import Home from './components/HOME.vue';
//把单文件组件,以Vue.compondent注册为全局组件
Vue.component('my-home',Home);
const vm=new Vue({
el:'#app',
data:{
}
})
2.私有注册 .vue 文件:
- 定义两个独立的组件 Home.vue 和 Son.vue
- 在 Home.vue 组件中,导入 Son.vue 文件,并使用 Home.vue 组件的 components 属性,把 Son.vue 注册为自己的私有子组件
//导入son组件
import Son from './Son.vue';
// 行为中固定写法
// 当前组件中私有的data数据
export default {
data(){
return {
msg:'hello .vue文件'
}
},
// 在.vue文件中,可以通过components属性,将另外一个.vue组件,定义为自己的私有组件
components:{
'my-son':Son,
}
}
组件中的样式问题
由于我们期望在组件中的样式只在当前组件中生效,但是我们通过运行可以发现,子组件在父组件中使用,他也被父组件的样式影响,这个是因为默认情况下,组件中定义的样式是全局组件,所以可以使用下面的这种方法:
<style scoped>
/* 我们期望在组件中的样式只在当前组件中生效 */
/* 所以今后,我们都需要给组件的style添加scoped,防止样式冲突 */
h3 {
color: red;
}
</style>>
如果我们希望使用标签嵌套的方式,也就是less的文件样式,就需要添加一个lang="less"属性,
<style lang="less" scoped>
/* 我们期望在组件中的样式只在当前组件中生效 */
/* 所以今后,我们都需要给组件的style添加scoped,防止样式冲突 */
.home-box {
border: 1px solid #000;
h3 {
color: red;
}
}
</style>>
组件之间的数据通信
父组件向子组件传递普通数据
1.在父组件中,以标签形式使用子组件的时候,可以通过属性绑定,为子组件传递数据:
<my-son :pmsg1="parentMsg" :pinfo="parentInfo"></my-son>
2.在子组件中,如果向用父组件传递过来的数据,必须先定义 props 数组来接收:
<script>
export default {
data(){
return {}
},
methods: {},
// property 属性
// 注意:父组件传递到子组件中的数据,必须经过 props 的接收,才能使用;
// 通过 props 接收的数据,直接可以在页面上使用;注意:不接受,不能使用外界传递过来的数据
props: ['pmsg1', 'pinfo']
}
</script>
3.接收完的props数据,可以直接在子组件的 template 区域中使用:
<template>
<div>
<h3>这是子组件 --- {{pmsg1}} --- {{pinfo}}</h3>
</div>
</template>
具体操作可以看下图:
注意:父组件传递给子组件的成员数据props都是可读数据,不要为他们重新赋值,
但是data数据都是当前属性的私有数据,而且data中的数据都是可读可写的
由于props中的数据都是只读的,所以如果想为props数据重新复制,可以把数据转存到data中,从而实现重新赋值
由于上述进行值的传递和转存的时候,都是简单的数据类型的值,所以如果变成是引用数据类型的值,就会出现值会同时修改的情况,所以这里需要进行深拷贝操作,这里进行深拷贝操作的是利用一个包lodash:
import _ from 'lodash';
export default {
data(){
// 对于转存修改属性只是简单数据类型可以转,对于复杂数据类型还需要另外进行操作
return {
infoFromParent:this.pinfo,
msgFromParent:_.cloneDeep(this.pmsg)
}
},
// 子组件需要使用props按钮,接收外界传递过来的数据
props:['pmsg','pinfo']
}
通过_cloneDeep(传递一个对象),这样就可以实现深拷贝,并且返回的是一个全新的对象,修改他的值,不会影响其他的值.
父组件向子组件传递方法(属性绑定)
- 如果父向子传递方法,需要使用 事件绑定机制:
<my-son @func="show"></my-son>
其中,为 子组件传递的 方法名称为 func, 具体的方法引用,为 父组件中的 show 方法 2. 子组件中,可以直接通过 this.$emit('func') 来调用父组件传递过来的方法;
子组件向父组件传值(事件绑定)
-
子向父传值,要使用 事件绑定机制@;
-
父向子传递一个方法的引用
-
子组件中,可以使用 this.$emit() 来调用父组件传递过来的方法
-
在使用this.$emit()调用 父组件中方法的时候,可以从第二个位置开始传递参数;把子组件中的数据,通过实参,传递到父组件的方法作用域中;
methods: {
// 点击子组件中的按钮,触发按钮的点击事件
btnHandle(){
// 在子组件中通过this.$emit()方法,触发父组件,为子组件绑定func事件
this.$emit('func' +this.msg)
}
},
- 父组件就可以通过形参,接收子组件传递过来的数据;
兄弟组件之间传值
注意:兄弟组件之间,实现传值,用到的技术,是 EventBus
- 定义模块 bus.js
import Vue from 'vue'
export default new Vue()
- 在需要接收数据的兄弟组件中,导入 bus.js 模块
import bus from './bus.js'
- 在需要接收数据的兄弟组件中的 created 生命周期函数里, 使用 bus.$on('事件名称', (接收的数据) => {}) 自定义事件:
created(){
// 定义事件
bus.$on('ooo', (data)=>{
console.log(data)
})
}
- 在需要发送数据的兄弟组件中,导入 bus.js 模块
import bus from './bus.js'
- 在需要发送数据的兄弟组件中,使用 bus.$emit('事件名称', 要发送的数据) 来向外发送数据:
import bus from './bus.js'
export default {
data(){
return {
msg: 'abcd'
}
},
methods: {
sendMsg(){
// 触发 绑定的 事件,并向外传递参数
bus.$emit('ooo', this.msg)
}
}
}
使用 this.$refs来获取元素和组件
1.把要获取的DOM元素,添加 ref 属性,创建一个DOM对象的引用,指定的值,就是引用的名称:
//通过ref获取的DOM元素的引用就是一个元素的DOM对象
<p ref="myElement11">这是父组件</p>
- 如果要获取 某个引用所对应的 DOM对象,则直接使用 this.$refs.引用名称
console.log(this.$refs.myElement11)
- 也可以使用 ref 为组件添加引用;可以使用 this.$refs.组件应用名称,
console.log(this.$refs.myElement11)
3.也可以使用 ref 为组件添加引用;可以使用 this.$refs.组件应用名称, 拿到组件的引用,从而调用组件上的方法 和 获取组件data上的 数据;
使用 霸道的 render 函数渲染组件
-
如果在 vm 实例中既指定了 el 又指定了 render 函数,则会把 el 所指的的区域,替换为 render 函数中所提供的组件;
-
既然 render 函数会替换到 el 区域内的所有代码,也会让 template 属性失效;因此,在删减版的 vue 包中,new 出来的 Vue 实例对象,不允许 挂载 data 属性和 template 属性!
const vm = new Vue({
el: '#app',
// createElements 形参是一个方法,专门用于渲染一个组件,并替换掉 el 区域
/* render: function(createElements){
return createElements(App)
}, */
// 这是 render 的终极格式
// 被render渲染的组件,叫做 根组件
// 什么是根组件:【不论浏览器中的页面如何切换,根组件永远都在页面上显示】
render: h => h(App)
})
// 注意:只要在 vm 实例中,指定了 render 函数来渲染组件,那么,el 区域,就会被 render 中渲染的组件替换掉;
基础知识五
使用标签实现组件切换
- 是Vue提供的;作用是 把 is 指定的 组件名称,渲染到 内部
- 身上有一个 :is属性
<template>
<div>
<h1>App 根组件</h1>
<button @click="comName='my-home'">Home</button>
<button @click="comName='my-movie'">Movie</button>
<!-- 可以通过 component 的is属性,动态指定要渲染的组件 -->
<component :is="comName"></component>
</div>
</template>
<script>
import Home from './coms/Home.vue'
import Movie from './coms/Movie.vue'
export default {
data() {
return {
// 默认是展示home属性的
comName: 'my-home'
}
},
components: {
'my-home': Home,
'my-movie': Movie
}
}
</script>
SPA单页应用
锚链接及常规url的区别
- 普通的URL地址:会刷新整个页面;会追加浏览历史记录;
- 锚链接:不会触发页面的整体刷新;会追加浏览历史记录;(锚链接是页面内的跳转)
什么是SPA,为什么有SPA
- 概念定义:SPA英文全称是Single Page Application, 中文翻译是 “单页面应用程序”;
- 通俗的理解是:只有一个Web页面的网站;网站的所有功能都在这个唯一的页面上进行展示与切换;
- 特点:
- 只有一个页面
- 浏览器一开始请求这个页面,必须加载对应的HTML, CSS, JavaScript
- 用户的所有操作,都在这唯一的页面上完成
- 页面数据都是用Ajax请求回来的
- 好处:
- 实现了前后端分离开发,各司其职;提高了开发效率;
- 用户体验好、快,内容的改变不需要重新加载整个页面;
- 缺点:
- 对SEO不是很友好,因为页面数据是Ajax渲染出来的; (Server Side Render)服务器端渲染;
- 刚开始的时候加载速度可能比较慢;项目开发完毕之后,可以单独对首屏页面的加载时间做优化;
- 页面复杂度比较高,对程序员能力要求较高;
原生实现SPA
使用 component 标签的:is属性来切换组件
总结:单页面应用程序中,实现组件切换的根本技术点,就是 监听 window.onhashchange 事件;
路由
什么是路由:路由 就是 对应关系;
- 后端路由的定义:URL地址 到 后端 处理函数之间的关系;
- 前端路由的定义:hash 到 组件 之间的对应关系;
- 前端路由的目的:为了实现单页面应用程序的开发;
- 前端路由的三个组成部分:
- 链接
- 组件
- 链接 和 组件之间的对应关系
在 vue 中使用 vue-router【重点】
- 安装导入并注册路由模块:
- 运行 npm i vue-router -S 安装路由模块
- 在 index.js 中导入并注册路由模块
// 导入路由模块
import VueRouter from 'vue-router'
// 注册路由模块(把路由模块安装到Vue上)
Vue.use(VueRouter)
- 创建路由链接:
<!-- router-link 就是 第一步,创建 路由的 hash 链接的 -->
<!-- to 属性,表示 点击此链接,要跳转到哪个 hash 地址, 注意:to 属性中,大家不需要以 # 开头 -->
<router-link to="/home">首页</router-link>
<router-link to="/movie">电影</router-link>
<router-link to="/about">关于</router-link>
3.创建并在 index.js 中导入路由相关的组件:
import Home from './components/Home.vue'
import Movie from './components/Movie.vue'
import About from './components/About.vue'
4.创建路由规则
// 创建路由规则(对应关系)
const router = new VueRouter({ // 配置对象中,要提供 hash 地址 到 组件之间的 对应关系
routes: [ // 这个 routes 就是 路由 规则 的数组,里面要放很多的对应关系
// { path: 'hash地址', component: 配置对象 }
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About }
]
})
// 创建的 router 对象,千万要记得,挂载到 vm 实例上
const vm = new Vue({
el: '#app',
render: c => c(App),
router // 把 创建的路由对象,一定要挂载到 VM 实例上,否则路由不会生效
})
5.在页面上放路由容器
<!-- 这是路由的容器,将来,通过路由规则,匹配到的组件,都会被展示到这个 容器中 也就是切换的内容会在下面的这个区域进行显示 -->
<router-view></router-view>
或者是直接写成是单闭合标签
<router-view />
路由规则的匹配过程
- 用户点击 页面的 路由链接router-link,点击的一瞬间,就会修改 浏览器 地址栏 中的 Hash 地址;
- 当 hash 地址被修改以后,会立即被 vue-router 监听到,然后进行 路由规则的 匹配;最终,找到 要显示的组件;
- 当 路由规则匹配成功以后,就找到了 要显示的 组件,然后 把 这个组件,替换到 页面 指定的 路由容器router-view 中
设置路由高亮的两种方式
- 通过路由默认提供的router-link-active, 为这个类添加自己的高亮样式即可;
<style lang="less" scoped>
.router-link-active {
color: red;
font-weight: bold;
}
</style>
- 通过路由构造函数,在传递路由配置对象的时候,提供 linkActiveClass 属性,来覆盖默认的高亮类样式;
// 3. 创建路由实例对象
const router = new VueRouter({
routes: [
// 路由? 就是对应关系
// 前端路由? hash => 组件 之间的对应关系
// vue 中路由的格式 { path, component }
// path 路由hash地址中,路径必须以 / 开头,而且必须是小写,而且不能带空格
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About },
{ path: '/me', component: Me }
],
linkActiveClass: 'my-active' // 如果大家做项目时候, 用到的 UI 组件库中,提供了默认的高亮效果
})
//在页面上的显示类名如下:
.my-active {
color: #007ACC;
font-weight: 700;
}
嵌套路由(子路由)
- 在对应的路由组件中,新增 router-link 路由链接;
- 创建 router-link 对应要显示的组件;
- 在对应的路由规则中,通过 children 属性,定义子路由规则:
const router = new VueRouter({
routes: [
// 路由? 就是对应关系
// 前端路由? hash => 组件 之间的对应关系
// vue 中路由的格式 { path, component }
// path 路由hash地址中,路径必须以 / 开头,而且必须是小写,而且不能带空格
// 在路由规则中,通过 redirect 属性,指向一个新地址,就能够实现路由的重定向
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
// 在某个路由规则中,如何嵌套子路由规则? path 和 component 平级,还有个 children 属性
// children 属性 是一个数组, 作用,就是来嵌套子路由规则的
{
path: '/about',
component: About,
redirect: '/about/tab1',
children: [
{ path: '/about/tab1', component: Tab1 },
{ path: '/about/tab2', component: Tab2 }
]
},
{ path: '/me', component: Me }
],
linkActiveClass: 'my-active' // 如果大家做项目时候, 用到的 UI 组件库中,提供了默认的高亮效果
})
路由传参
通过属性绑定实现路由传参,也就是在路由规则的参数项之前加冒号实现路由传参
<!-- 当router-link的to地址,要动态进行拼接的时候,那么,一定要把 to 设置成属性绑定的形式 -->
<router-link v-for="item in mlist" :key="item.id" :to="`/mdetail/${item.id}/${item.name}`" tag="li">{{item.name}}</router-link>
但是还是可能会出现参数不固定的情况
const router = new VueRouter({
routes: [
{ path: '/', component: MovieList },
// 把路由规则中, 参数项位置,前面加上 : 表示这是一个参数项
// props: true 表示,为当前路由规则,开启 props 传参
{ path: '/mdetail/:id1/:name2', component: MovieDetail, props: true }
]
})
- 可以在组件中,直接使用this.route是一个路由的参数对象, this.$router是路由的导航对象
- 也可以开启路由的 props 传参,来接收路由中的参数;【推荐方式】
- 在需要传参的路由规则中,添加 props: true
{ path: '/movie/:type/:id', component: movie, props: true }
- 在 对应的组件中,定义 props 并接收路由参数
const movie = {
template: '<h3>电影组件 --- {{type}} --- {{id}}</h3>', // 使用参数
props: ['type', 'id'] // 接收参数
}
命名路由
什么是命名路由: 就是为路由规则,添加了一个 name ;
- 什么是命名路由
- router-link中通过路由名称实现跳转
- 命名路由使用 params属性传参
<!-- 使用 命名路由实现跳转 -->
<router-link v-for="item in mlist" :key="item.id" :to="{name: 'moviedetail', params: {id1: item.id, name2:item.name}}" tag="li">{{item.name}}</router-link>
编程式(JS)导航
之前所学的router-link是标签跳转;
除了使用router-link是标签跳转之外,还可以使用Javascript来实现路由的跳转;
- 什么是编程式导航
- 使用vue-router提供的JS API实现路由跳转的方式,叫做编程式导航;
- 编程式导航的用法
//跳转到指定的路由规则中
- this.$router.push('路径的地址')
//可以前进和后退
- this.$router.go(n)
//可以前进
- this.$router.forward()
//可以后退
- this.$router.back()
关于路由的方法的总结
- this.$route是路由参数对象
- this.$router是路由导航对象
- vm实例上的router属性,是挂载路由对象的
- 在new VueRouter(/配置对象/)的时候,配置对象中,有一个routes属性,是创建路由规则的
- router单独的写在 vm实例对象里面,表示挂载路由
path 是要匹配的hash值, component 要展示的组件 redirect 要重定向的路由 props 开启props传参 name 命名路由 children 嵌套子路由
路由导航守卫
- 案例需求:只允许登录的情况下访问 后台首页 ,如果不登录,默认跳转回登录页面;
- API语法:
// 参数1:是要去的那个页面路由相关的参数
// 参数2:从哪个页面即将离开
// 参数3:next 是一个函数,就相当于 Node 里面 express 中的 next 函数
// 注意: 这里的 router 就是 new VueRouter 得到的 路由对象
router.beforeEach((to, from, next) => { /* 导航守卫 处理逻辑 */ })
案例核心代码:
// 通过 路由导航守卫, 控制有权限页面的访问, 只有登录以后,才允许访问高级的页面
router.beforeEach((to, from, next) => {
// to.path 表示我们下一刻要访问哪个地址
// console.log(to)
// from.path 表示我们上一刻,所访问的是哪个地址
// console.log(from)
// next() 直接调用,表示放行
// next()
// 如果 要访问的地址,是 /login, 证明用户要去登录, 没有必要拦截,直接放行
if (to.path === '/login') return next()
// 如果用户访问的不是 登录页面,则 先尝试从sessionStorage中获取 token 令牌字符串
const tokenStr = window.sessionStorage.getItem('token')
// 如果没有 token 令牌,则 强制用户跳转到登录页
if (!tokenStr) return next('/login')
// 如果有令牌,则直接放行
next()
})
关于token验证问题
token是一个令牌,是服务器端发送过来的客户端必须通过登录,才能获取这个令牌,
watch 监听
-
watch 监听的特点:监听到某个数据的变化后,侧重于做某件事情;
- 只要被监听的数据发生了变化,会自动触发 watch 中指定的处理函数;
-
案例:登录 密码 的长度检测
-
密码长度小于8位,字体为红色;大于等于8位,字体为黑色;
export default {
data() {
return {
uname: '',
upwd: ''
}
},
// watch 是监听 data 中数据的变化, 侧重于做某件事件
watch: {
upwd(newVal, oldVal) {
if (newVal.length < 8) {
this.$refs.pwdDOM.style.color = 'red'
} else {
this.$refs.pwdDOM.style.color = ''
}
}
}
}
computed 计算属性
计算属性特点:同时监听多个数据的变化后,侧重于得到一个新的值;
- 只要依赖的任何一个数据发生了变化,都会自动触发计算属性的重新求值;
export default {
data() {
return {
firstname: '',
lastname: ''
}
},
// 计算属性
computed: {
// 定义一个计算属性,叫做 fullName
// 注意: 所有的计算属性,在定义的时候, 都要被定义为 function,
// 但是,在页面上使用计算属性的时候, 是直接当作普通的 变量 来使用的,而不是当作方法去调用的!!!
// 特点:只要计算属性的 function 中,依赖的 任何一个数据发生了变化,都会对这个计算属性,重新求值
fullName: function() {
return this.firstname + '-' + this.lastname
}
}
}
应用
如果页面需要访问一个数据,这个数据比较复杂,是需要通过其他data经过复杂步骤制作出来的,那么就可以通过“计算属性”简化获得该数据
补充:Vue本身支持模板中使用复杂表达式表现业务数据,但是这会使得模板内容过于杂乱,如果确有需求,可以通过computed计算属性实现
与methods方法的区别:
computed计算属性本身有“缓存”,在关联的data没有变化的情况下,后续会使用缓存结果,节省资源
methods方法没有缓存,每次访问 方法体 都需要加载执行,耗费资源
使用 vue-cli 快速创建 vue 项目
为什么要使用 vue-cli 创建项目:
- 在终端运行一条简单的命令,即可创建出标准的 vue 骨架项目;
- 不必自己手动搭建 vue 项目基本结构,省时省力;
- 不必关心 webpack 如何配置,只关注于项目代码的开发;
webpack 中 省略文件后缀名 和配置 @ 路径标识符
省略文件扩展名:
- 打开 webpack.config.js,在导入的配置对象中,新增 resolve 节点;
- 在 resolve 节点中,新增 extensions 节点:
resolve: {
// resolve 节点下的 extensions 数组中,可以配置,哪些扩展名可以被省略
extensions: ['.js', '.vue', '.json']
}
修改完配置以后,重新运行 npm run dev 查看效果;
配置 @ 指向 src 目录:
resolve: {
alias: {
'@': path.join(__dirname, './src') // 让 @ 符号,指向了 项目根目录中的 src
}
}
基础知识六
自定义指令
全局自定义指令
- 概念:在全局任何组件中都可以被调用的自定义指令,叫做全局自定义指令;
- 语法:Vue.directive('全局自定义指令名称', { /* 自定义指令配置对象 */ })
// Vue.directive('全局指令名称', { /*指令的配置对象*/ })
// 注意:自定义指令名称之前,不需要手动添加 v- 前缀
Vue.directive('red', {
// 只要指令被解析指令了,就会优先调用指令中的 bind 方法
bind(el) {
// 只要bind被指向了,那么el,就是它所绑定到的 UI 元素
// el 是原生DOM对象,也正是因为他是原生的DOM对象,所以他才可以通过style.color的方式修改样式
el.style.color = 'red'
}
})
私有自定义指令
概念:只有指令所属的组件中,才可以被正常调用的指令,叫做私有自定义指令;
// 私有自定义指令的定义节点
directives: {
// 指令名称: { /配置对象/ }
blue: {
bind(el) {
el.style.color = 'blue'
}
}
}
指令配置对象中 bind 和 inserted 的区别
- bind 方法:
- 绑定当前指令的元素,在内存中被创建的时候就会被立即调用;
- 推荐把样式相关的设置,都定义在 bind 方法中;
- inserted 方法:
- 绑定当前指令的元素,被插入到DOM树之后才会被调用;
- 推荐把行为相关的设置,都定义在 inserted 方法中;
- 演示 bind 和 inserted 的区别:
- 在终端中打印 el.parentNode 即可; 在bind中输出的el.parentNode为null, 但是在inserted中输出的是他的父节点
Vue.directive('focus', {
// bind 表示指令第一次被解析执行时候调用,此时,这个 DOM 元素,还没有被append到父节点中;
// 此时只是在内存中存储着,所以还你没有渲染到页面上
bind(el) {
// el.focus()
// console.log(el.parentNode),null
},
// inserted 会在元素被插入到父节点之后,执行,此时已经渲染到页面之上了
inserted(el) {
// 定义 文本框获得焦点的指令,只能通过 inserted 来实现
// 因为 bind方法 和 inserted方法 的执行时机不一样
el.focus()
}
})
// 总结:如果只是单纯的为元素设置样式,尽量写到 bind 中
// 如果要设置JS行为,比如文本框获取焦点,这种行为,尽量写到 inserted 中
自定义指令传参
Vue.directive('color', {
// 通过 形参中的 binding 来接收指令传递过来的数据
// 所有通过=传过来的值都是在binding中存储着
// 传递过来的参数,是 binding.value 属性
bind(el, binding) {
// console.log(binding.value)
el.style.color = binding.value
}
})
插槽
定义:定义子组件的时候,在子组件内部刨了一个坑,父组件想办法往坑里填内容;
单个插槽(匿名插槽)
- 定义插槽:在子组件作用域中,使用 定义一个插槽;
- 使用插槽:在父作用域中使用带有插槽的组件时,组件内容区域中的内容,会插入到插槽中显示;
- 注意:在一个组件的定义中,只允许出现一次匿名插槽
Son.vue子组件代码:
<template>
<div>
<h4>这是子组件</h4>
<p>哈哈哈</p>
<!-- 没有name属性的插槽,称为是匿名插槽 -->
<slot></slot>
<!-- 注意:在同一个组件中,只允许定义一次插槽 -->
<!-- <slot></slot> -->
<p>heiehei</p>
</div>
</template>
APP.vue主组件代码展示:
<template>
<div>
<h1>这是父组件</h1>
<hr>
<!-- 在子组件的内部放置内容 -->
<!-- 默认情况下载组件内容中,定义的信息都会被显示到匿名插槽中 -->
<my-son>
<img src="./images/土拨鼠啊.gif" alt="">
<img src="./images/老王.png" alt="">
<h3>6666</h3>
</my-son>
</div>
</template>
多个插槽(具名插槽)
- 定义具名插槽:使用 name 属性为 slot 插槽定义具体名称;
<template>
<div>
<h1>这是子组件</h1>
<p>啊,五环</p>
<!-- 匿名插槽 -->
<slot></slot>
<p>你比四环多一环</p>
<!-- 具名插槽 -->
<slot name="s2"></slot>
<p>啊,五环</p>
<slot name="s3"></slot>
<p>你比七环少两环</p>
</div>
</template>
- 使用具名插槽:在父作用域中使用带有命名插槽的组件时,需要为内容指定 slot="插槽name" 来填充到指定名称的插槽;
<template>
<div>
<h1>这是父组件</h1>
<hr />
<my-son>
<!-- 默认情况下指定的元素会被插入到匿名插槽中 -->
<img src="../03默认插槽/images/一脸懵逼表情包.jpg" alt />
<img slot="s2" src="../03默认插槽/images/土拨鼠啊.gif" alt />
<img src="../03默认插槽/images/老王.png" alt />
<img slot="s3" src="../03默认插槽/images/擅用百度.jpg" alt="">
</my-son>
</div>
</template>
作用域插槽
- 定义作用域插槽:在子组件中,使用 slot 定义插槽的时候,可以通过 属性传值 的形式,为插槽传递数据,
<template>
<div>
<h4>这是子组件</h4>
<slot smsg="hello Vue" sinfo="你好"></slot>
<p>
~~~~~~~~~~~~~~
</p>
<slot name="s2" :umsg="m1" :uinfo="m2">
</slot>
</div>
</template>
<script>
export default {
data(){
return {
m1:'abcd',
m2:'123456'
}
}
}
</script>
- 使用作用域插槽:在父作用域中,通过定义 slot-scope="scope" 属性,接收并使用 插槽数据;
- 注意:同一组件中不同插槽的作用域,是独立的!
<template>
<div>
<h1>这是父组件</h1>
<hr>
<my-son>
<h6 slot-scope="scope">{{scope}}</h6>
<!-- <h3 slot="s2" slot-scope="scope">{{scope}}</h3>
<h3 slot="s2" slot-scope="scope">{{scope}}</h3> -->
<!-- 如果要接收作用域插槽中的数据,而且渲染为多个标签,
则必须在多个标签之外,包裹一个父元素,进行接收插槽中的数据
-->
<!-- 注意 template只起到包裹元素的作用,不会被渲染为任何标签-->
<template slot="s2" slot-scope="scope">
<h3>{{scope.uinfo}}</h3>
<h3>{{scope.umsg}}</h3>
</template>
</my-son>
</div>
</template>
element-ui
待更新....
1.element-ui 是 饿了么 前端团队,开源出来的一套 Vue 组件库;
2.完整引入 Element-UI 组件:
1. 运行 yarn add element-ui -S 安装组件库
2. 在 index.js 中,导入 element-ui 的包、配套样式表、并且安装到Vue上:
// 导入 element-ui 这个包
import ElementUI from 'element-ui'
// 导入 配套的样式表
import 'element-ui/lib/theme-chalk/index.css'
// 把 element-ui 安装到 Vue 上
Vue.use(ElementUI)
3.按需导入和配置 Element-UI :
- 运行 npm i babel-plugin-component -D 安装支持按需导入的模块;
- 打开 .babelrc 配置文件,修改如下:
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties",
+ [
+ "component",
+ {
+ "libraryName": "element-ui",
+ "styleLibraryName": "theme-chalk"
+ }
]]
}
使用vue-cli快速初始化vue项目
$ npm install -g vue-cli // 全局安装脚手架工具
$ vue init webpack my-project // 初始化项目
$ cd my-project // 切换到项目根目录中
$ npm install // 安装依赖包
$ npm run dev // 一键运行项目
ESLint 语法检查规范
- 声明但是未使用的变量会报错
- 空行不能连续大于等于2
- 在行结尾处,多余的空格不允许
- 多余的分号,不允许
- 字符串要使用单引号,不能使用双引号
- 在方法名和形参列表的小括号之间,必须有一个空格
- 在单行注释的 // 之后,必须有一个空格
- 在每一个文件的结尾处,必须有一个空行
- import语句必须放到最顶部
- etc...
如何配置VSCode帮我们自动把代码格式为需要的样子
- 在安装 Vetur 插件
- 安装 Prettier - Code formatter 插件
- 打开 vs code 的 文件 -> 首选项 -> 设置,在用户设置最底部的合适位置,添加如下配置:
// 使用 ESLint 规则
"prettier.eslintIntegration": false,
// 每行文字个数超出此限制将会被迫换行
"prettier.printWidth": 100,
// 使用单引号替换双引号
"prettier.singleQuote": true,
// 格式化文件时候,不在每行结尾添加分号
"prettier.semi": false,
// 设置 .vue 文件中,HTML代码的格式化插件
"vetur.format.defaultFormatter.html": "prettier"
- 重启VS Code让插件和配置生效!
- 打开vue-cli生成的项目中,.eslintrc.js配置文件,找到 rules 节点, 将如下语法规则,粘贴到 rules 配置中: