第一部分 Vue知识框架
0.邂逅Vue
Vue读音类似于view
Vue的渐进式
Vue特点
1.如何安装
- 方式一 直接CDN引入
- 方式二 下载和引入
- NPM 安装
2.HelloWorld
编程范式:声明式编程
<div id="app">{{message}}</div>
<script>
//let 变量 const 常量
const app=new Vue({
el:"#app", //用来挂载要管理的元素
data:{ //用来管理数据
message:"你好呀"
}
})
</script>
对比以前的原生JS开发(命令式编程)
- 1.创建div元素,设置id属性
- 2.定义一个变量叫message
- 3.将message变量放到前面的div元素中显示
- 4.修改message的数据:今天天气真好
- 5.将修改后的数据再次替换到div中
3.什么是MVVM
Model ViewModel View
* View 依然是我们的DOM
* Model 就是我们抽离出来的obj
* ViewModel 就是我们创建的view实例
他们之间如何工作
首先ViewModel通过Data Binding 让obj中的数据实时的在DOM中显示
其次ViewModel通过DOM Linstener来监听DOM事件,并且通过Methods中的操作,来改变obj的数据
4.目前掌握的选项(options)
-
el
类型:string|HTMLElement 作用:决定之后的Vue实例会管理哪一个DOM
-
data
类型: Object|Function 作用:Vue实例对应的数据
-
methods
类型 {[key:string]:function}
开发中什么时候称为方法?什么时候称为函数
本质区别:方法都是和实例对象相挂钩的
5.基本语法
(1) 插值语法 Mustache(胡子)
{{}}---->体验vue的响应式
插值相关的指令
-
v-once
v-once只渲染一次,后期message数据变化以后就不会再改变 <h2 v-once> {{message}} </h2>
-
v-html
<h2 v-html="url"></h2> data: { message:"你好呀", url:'<a href="http://www.baidu.com">百度一下</a>' }
-
v-text和mustache一样
-
v-pre
<h2 v-pre>{{message}}</h2>原封不动展示不解析
-
v-cloak v-cloak 作用,一旦vue做了解析会删除 v-cloak 在vue解析之前,div有一个v-cloak 所以我们可以利用v-cloak写一些样式
<div id="app" v-cloak> <!-- --> {{message}} </div> [v-cloak]{ display: none; }
(2)v-bind
绑定属性,动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。
-
绑定基本属性:
:
v-bind:src :href
-
缩写:
:
<img v-bind:src="imgUrl" alt="">
<img :src="imgUrl" alt="">
- v-bind动态绑定class属性
两种
* 对象语法
* 数组语法
对象语法
我们可以传给 v-bind:class
一个对象,以动态地切换 class:
用法1 :直接通过{}绑定一个类
<div v-bind:class="{ active: isactive}">哈哈哈哈</div>
用法2: 也可以通判断传入多个值
<div v-bind:class="{ active: isactive ,line:isLine}">哈哈哈哈</div>
用法3:和普通类同时存在
<div class="title" v-bind:class="{ active: isactive ,line:isLine}">哈哈哈哈</div>
用法4:如果过于复杂,可以放入一个methods或者computed中
<div v-bind:class="getClasses()">呵呵呵呵呵</div>
getClasses:function(){
return { active: this.isactive ,line:this.isLine}
}
数组语法
<div class="title" :class="['active','line']">哈哈哈哈哈</div>
- v-bind动态绑定style
v-bind:style
的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。
两种
* 对象语法
* 数组语法
对象语法
<!-- 59px必须加上单引号,否则当做变量解析 -->
<h2 :style="{fontSize:'50px'}">{{message}}</h2>
<h2 :style="{fontSize:size}">{{message}}</h2>
<h2 :style="{fontSize:finalSize+'px'}">{{message}}</h2>
数组语法
多个值用逗号分隔即可
<div v-bind:style="[baseStyles, overridingStyles]">hhahahhah</div>
data: {
baseStyles:{backgroundColor:'red'},
overridingStyles:{fontSize:'100px'}
}
(3)计算属性
当我们需要对数据进行转换或者多个数据合并再来显示,直接写在{{}}里面代码看上去可读性不高
-
1、计算属性的setter和getter
这种写法其实是简写 // fullName:function(){ // return this.firstName+ ' '+ this.lastName // } 完整实现,一般我们只需要实现get方法,我们不希望别人随便给我们计算属性设置值 没有set属性,这是只读属性 fullName:{ set:function(){ }, get:function(){ return this.firstName+ ' '+ this.lastName } } 一旦有了set方法就可以设置值了 fullName:{ set:function(newValue){ console.log("-----",newValue) const name=newValue.split(" ") this.firstName=name[0] this.lastName=name[1] }, get:function(){ return this.firstName+ ' '+ this.lastName } }
-
2、计算属性和Methods的对比
<!-- 第一种方案:直接拼接,语法过于繁琐,我们希望在HTML里面代码越简洁越好 --> <h2>{{firstName}} {{lastName}}</h2> <!-- 第二种方案:通过定义methods函数,然后调用函数 --> <h2>{{getFullName()}}</h2> methods:{ getFullName(){ return this.firstName+" "+this.lastName } }, <!-- 第三种方案:计算属性 --> <h2>{{fullName}}</h2> computed:{ //计算属性,我们尽量按照属性名起名字 fullName:function(){ return this.firstName+" "+this.lastName } } 对比: 计算属性内部是做了缓存的,如果每次调用发现firstName和lastName没有发生变化,就直接返回上次结果 方法是调用几次执行几次
(4)补充ES6知识点
-
let/var
ES5中的var是没有块级作用域的
ES6中的let是有块级作用域的
变量作用域:变量在什么范围内可以使用 没有块级作用域影响: if的块级没有作用域,引起问题 for的块级没有作用域 函数有作用域 var btn=document.getElementsByTagName("button") // for(var i=0;i<btn.length;i++){ // btn[i].addEventListener("click",function(){ // console.log("第"+i+"个按钮被点击") //这里打印的都是5,因为for没有块级作用域,所有的i引用的都是同一个变量 // }) // } // 解决办法(利用闭包可以解决问题:函数是一个作用域) for(var i=0;i<btn.length;i++){ (function(i){ btn[i].addEventListener("click",function(){ console.log("第"+i+"个按钮被点击") }) })(i) } Brendan Eich是JS的设计者,当初在涉及var的时候,没有考虑很多,是一种缺陷,于是他添加了关键词:let let有块级作用域
总结:
在ES5之前因为我们的if和for都没有块级作用域的概念,所以在很多时候我们必须借助function的作用域来解决应用外面变量的问题
在ES6中,加入了let,let是有块级作用域了
//ES6来解决上面的问题就变得非常简单了
for(let i=0;i<btn.length;i++){
btn[i].addEventListener("click",function(){
console.log("第"+i+"个按钮被点击") //这里打印的都是5,因为for没有块级作用域,所有的i引用的都是同一个变量
})
}
-
const
常量
const a=30 a=40;//错误,不可以修改 const name;//错误,const标识符必须赋值
常量的含义是指向的对象不能修改,但是可以改变对象内部的属性
-
ES6对象字面量增强语法
什么叫对象字面量?
const obj={}
属性的增强写法
const name='vina'; const age=18, const obj={ name:name, age:age } 增强写法 const obj={ name, age }
函数的增强写法
// ES5写法 const obj={ run:function(){ }, eat:function(){ } } //增强写法 const obj={ run(){ }, eat(){ } }
(5) v-on
-
作用 :绑定事件监听
-
缩写 :@
-
预期 :Function|Inline Statement|Object
-
参数 : event
<button v-on:click="counter++">+</button> <button v-on:click="counter--">-</button> <button v-on:click="increment()">+</button> //语法题(推荐写法) <button @click="decrement">+</button>
-
v-on 参数问题
事件调用的方法没有参数
分析:当事件方法没有参数的时候,括号是可以省略的 <button @click="btn1Click"> 按钮1 </button> <button @click="btn1Click()"> 按钮2 </button> btn1Click(){ console.log("btnClick") },
在函数事件定义时,写函数时省略了小括号,但是方法本身需要一个参数这时候
分析:如果方法本身需要一个参数,但是方法调用的时候没有传递参数且没有小括号,那么会打印event事件,如果有小括号会打印undefined <button @click="btn2Click"> 按钮3 </button> <button @click="btn2Click()"> 按钮4 </button> btn2Click(a){ console.log(a) }
方法定义时,我们需要event事件,同事还需要多余的参数,怎么办?
分析:必须给event前面加$ $event是浏览器产生的event对象 <button @click="btn3Click($event,123)"> 按钮5 </button>
-
v-on 修饰符
.stop
<div @click="divClick">
<!-- .stop就可以阻止冒泡 -->
<button @click.stop="btnClick">按钮</button>
</div>
.prevent
form自身提交了,如果我们想要自己写提交方法,那么就要阻止默认的提交事件, 通过prevent阻止默认事件
<form action="baidu">
<input type="text">
<input type="submit" value="提交" @click.prevent="submitClick">
</form>
.keyUp 监听所有事件
<!-- 监听某个键盘(所有)的键帽 -->
<input type="text" @keyup="keyUp">
<!-- 监听某个键盘(enter)的键帽 -->
<input type="text" @keyup.enter="keyUp">
.once 只触发一次
<button @click.once="onceClick">once</button>
(6)条件判断
- v-if
<h2 v-if="score>=90">优秀</h2>
<h2 v-else-if="score>=80">良好</h2>
<h2 v-else-if="score>=60">及格</h2>
<h2 v-else>不及格</h2>
分析:当条件很多的时候,不建议这么写,我们可以写在计算属性中
为什么我输入了内容,但是切换了输入框,数据还存在?
分析: 这是因为Vue在进行DOM渲染的时候,出于性能的考虑,会复用,而不是重新创建
<span v-if="isUser">
<label for="username">用户账号 </label>
<input type="text" id="username" placeholder="用户账号">
</span>
<span v-else>
<label for="email">用户邮箱</label>
<input type="text" id="email" placeholder="用户邮箱">
</span>
<button @click="checkStatus">切换类型</button>
解决:如果我不希望切换输入框了原先的数据还存在,应该怎么做
我们给每个输入框加个key
-
v-show和v-if
v-if
当条件为false的时候,包含v-if指令的元素,根本就不会存在在dom中
v-show
当条件为false的时候,v-show指令只会在我们的元素添加一个行内样式 display:none
开发时怎么选择?
当需要在我们显示和隐藏之间切片很频繁,使用v-show 当只有一次切换的时候,通常选择 v-if,开发中v-if用的多,我们一般获取服务器传过来的数据判断是否渲染。
(7) 循环遍历
-
v-for遍历数组
//在遍历的过程中,没有使用索引值 <li v-for="item in names">{{item}}</li> //在遍历的过程中,获取索引值 <li v-for="(item,index) in names">{{index+1}}:{{item}}</li>
-
v-for遍历对象
<li v-for="item in info">{{item}}</li> <!-- 获取key和value --> <!-- (value,key) --> <li v-for="(value,key) in info">{{value}}------{{key}}</li> <!-- h获取key、value、index --> <li v-for="(value,key,index) in info">{{index}}:{{value}}------{{key}}</li> info:{ name:"why", age:19, height:1.88 }
-
组件的key属性
官方推荐我们使用v-for的时候,给对应的元素添加key属性,目的是为了让我们更好的复用
我们需要使用key来给每个节点做一个唯一标识
diff算法就可以正确的识别次节点 找到正确的位置区插入新的节点
所以,key的主要最用就是为了高效的更新虚拟DOM
(8)数组中哪些方法是响应式的
Vue内部会监听数据变化,会根据新的数据重新渲染DOM
push、pop、unshift、shift、reverse、splice
splice作用,删除元素、插入元素、修改元素
如果你要删除元素:第二个参数传入你要删除几个元素
splice(start)
this.letters.splice(1,2)
如果不传则删除所有
this.letters.splice(2)
如果我想要替换三个元素
this.letters.splice(1,3,'1','2','3')
新增元素,只要第二个元素为0
this.letters.splice(1,0,'1','2','3')
(9)高阶函数
我们先看没学高阶函数之前,完成某些需求需要的代码
// 需求:取出所有小于100的数字
const nums=[1,2,3,4,5]
let newNums=[]
for(let n of nums){
if(n<100){
newNums.push(n)
}
}
console.log(newNums)
//将取出的数据*2
let new2Nums=[]
for(let n of newNums){
new2Nums.push(n*2)
}
console.log(new2Nums)
//将所有数据相加
let total=0
for(let n of new2Nums){
total+=n
}
console.log("循环方法",total)
学了高阶函数以后,我们的需求实现代码如下
let Total=0
Total =nums.filter(function(n){
return n<100
}).map(function(n){
return n*2
}).reduce(function(pre,value){
return pre+value
})
console.log("高阶函数",Total)
分析:filter、map、reduce三种方法
filter中的回调函数有个要求,必须返回布尔值,当返回true时,函数内部会自动把n加入到新的数组值,当为false时,函数内部会自动过滤这次的n
map中的回调函数,返回的是当前这个的n,相当于遍历
箭头函数 result=nums.filter(n=>n<100).map(n=>n*2).reduce((pre,val)=>pre+val)
(10) 表单绑定v-model
-
基本操作:
<input type="text" v-model="message">
-
本质:
v-model其实是一个语法糖,他的背后本质上是包含两个操作
(1) v-bind 绑定一个value
(2) v-on 指令给当前元素绑定input事件
<input type="text" v-bind:value="message" v-on:input="valueChange">
valueChange(event){
this.message=event.target.value
}
-
简写:
<input type="text" v-bind:value="message" v-on:input="message=$event.target.value">
-
语法糖
<input type="text" :value="message" @input="message=$event.target.value">
v-model:radio
-
值绑定
值绑定就是动态的给value赋值,因为在实际的开发中,这些input的值可能是网络获取的或者定义在data中
<label v-for="item in originHobby" :for="item"> <input type="checkbox" :id="item" :value="item" v-model:value='hobby'>{{item}} </label> originHobby:['羽毛球','篮球','蹦床','跳水','弹钢琴']
-
v-model 修饰符
lazy
number
trim
<!-- 修饰符lazy 双向绑定缺点:一旦数据发生改变对应的data中的数据就会改变 .lazy的以后,回车或者失去焦点才会进行实时绑定 --> <input type="text" v-model.lazy="message"> {{message}} <!-- 有时候我们希望用户输入数字,我们可以修改input的type为number,但是数据还是string类型 --> <!-- <input type="number" v-modal="num"> {{typeof num}} --> <!-- 当我们希望数据是number类型我们可以添加修饰符number,就不需要进行类型转化 --> <input type="text" v-model.number="num"> {{typeof num}} <input type="text" v-model="name"> {{name}} <input type="text" v-model.trim="name"> {{name}}
6.组件化
我们可以将土匪完整的页面分成多个组件,每个组件可以实现自己对应的模块的功能
(1) Vue组件化思想
Vue组件化思想
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
任何的应用都会被抽象成一颗组件树
组件化思想的应用
有了组件化的思想,我们在之后的开发中要充分的利用它
尽可能的将页面拆成一个个小的、可复用的组件
这样让我们的代码更加方便组织和管理,并且扩展性更强
所以,组件是Vue开发中,非常重要的篇章,要认真学习
组件的使用步骤
-
创建组件构造器
-
注册组件
-
使用组件
Vue.extend()方法创建组件构造器 Vue.component()方法注册组件 在Vue 实例的作用范围内使用组件 const cpnC= Vue.extend({ template:` <div> <h2>我是标题</h2> <h2>我是内容,哈哈哈哈哈</h2> <h2>我是内容,呵呵呵呵</h2> </div> ` }) Vue.component('my-cpn', cpnC )
注册组件步骤解析
1、 Vue.extend():
调用Vue.extend()创建的是一个组件构造器
通常在创建组件构造器时,传入template代表我们自定义组件的模板
该模板就是使用到组件的地方,要显示的html代码
2、Vue.component()
调用Vue.component()时将刚才的组件构造器注册为一个组件,并且给他起一个组件的标签名称
所哟需要传递两个参数:1、注册组件的标签名 2、组件构造器
3、组件必须挂载在某个Vue实例下,否则他不会生效
(2)全局组件和局部组件
// 全局组件
Vue.component('my-cpn',
cpnC
)
//局部组件直接挂载在vue实例下
components:{
cpn:cpnC
}
(3)父组件和子组件
组件和组件之间存在层级关系
其中最重要的关系就是父子组件
(4) 注册组件语法糖
之前创建组件、注册组件
const cpnC= Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<h2>我是内容,哈哈哈哈哈</h2>
<h2>我是内容,呵呵呵呵</h2>
</div>
`
})
Vue.component('my-cpn',
cpnC
)
创建组件语法糖模式
实际上内部还是调用了extend
//全局组件
Vue.component('my-cpn',
{
template:`
<div>
<h2>我是标题</h2>
<h2>我是内容,哈哈哈哈哈</h2>
<h2>我是内容,呵呵呵呵</h2>
</div>
`
}
)
//注册局部组件
const app=new Vue({
el:"#app",
data: {
},
components:{
'cnp':{
template:`
<div>
<h2>我是标题</h2>
<h2>我是内容,哈哈哈哈哈</h2>
<h2>我是内容,呵呵呵呵</h2>
</div>
`
}
}
})
(5) 如何将组件模板抽取出来
(1)第一种:通过script标签,类型必须是 text/x-template ,给模板加一个id,这样可以绑定到实例上
(2)第二种:写在template标签中,同样也给template加一个id
(6)组件可以访问Vue实例数据嘛?
不能访问
Vue组件内部应该有自己保存数据的地方!!!!
组件对象也有一个data属性(也可以有methods属性)
只是这个data属性必须是一个函数
而且这个函数返回一个对象,对象内部保存数据
(7)为什么组件data必须函数
创建组件的时候,会调用data函数,每次调用的时候我会创建一个新的函数data
(8)父子组件通信
- props->properties(缩写) 父---->子
- 通过事件向父组件发送消息 子----->父
*(1)父传子通过props
第一种写法:
props:['cmovies','cmessage']
第二种写法:类型限制
props:{
cmovies:Array,
cmessage:String
}
第三种写法:有默认值、必传属性
props:{
cmovies:Array,
cmessage:String
cmessage:{
type:String,
default:'HHHHHH',
required:true //必传属性
}
}
*(2)子传父
如果子组件传递事件或者数据到父组件中,我们需要通过自定义时间来完成
什么时候需要自定义时间呢?
当子组件需要想父组件传递数据是,就要用到自定义事件了
我们之前学习的v-on不仅仅可以用于监听DOM时间,也可以用于组件间的自定义事件
自定义事件的流程
在子组件中,通过$emit()来触发事件
在父组件中,通过v-on来监听子组件事件
子组件
<template id="cnp">
<div>
<button
v-for="item in categories"
@click="btnclick(item)"
>
{{item.name}}
</button>
</div>
</template>
methods:{
btnclick(item){
console.log(item)
this.$emit('itemclick',item)
}
}
(9)父子组件通信-结合双向绑定事件
(10) 父子组件的访问方式:$children
或$refs
(reference:引用)
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者子组件访问根组件。
- 父组件访问子组件:使用
$children
或$refs
(reference:引用) - 子组件访问父组件使用:$parent
父访问子
`this.$children`开发中不常用,因为没办法判断第几个children,而且可能this.$children的长度会发生变化
只有我们需要拿到所有的子组件的时候,才会通过$children,但是大多数想要拿到特定子组件,通过设置ref
`<cpn ref="aaa"></cpn>`
子访问父
this.$parent 访问父组件
this.$root 访问根组件
(11)插槽slot
为什么使用slot?
让我们原来的设备具有扩展性,类似于电脑的USB,可以当做电源插槽、U盘、键盘、鼠标、音响等不通设备
组件的插槽
也是为了让我们封装的组件更加具有扩展性
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>我是组件哈哈哈哈哈哈哈</p>
<!-- 预留空间 -->
<slot></slot>
</div>
</template>
如何封装合适呢?
抽取共性,保留不同
具名插槽
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>我是组件哈哈哈哈哈哈哈</p>
<!-- 具名插槽 -->
<slot name="left"><h2>左</h2> </slot>
<slot name="center"><h2>中</h2></slot>
<slot name="right"><h2>右</h2></slot>
</div>
</template>
<cpn>
<button slot="left">左边</button>
<span slot="center">我想替换中间</span>
<button slot="right">右边</button>
</cpn>
编译作用域
官方准则:父组件模板中的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
<div id="app">
<!-- 这里isShow使用实例isShow还是组件呢?答案:实例 -->
<!-- 说明在实例中组件使用变量的时候,还是会去实例中寻找的,而不是组件中,因为实例把组件看做一个简单的html -->
<cpn v-show="isShow">
</cpn>
</div>
作用域插槽
总结:父组件替换插槽的标签,但是内容由子组件来提供
其实就是在父组件中拿到子组件的数据
7.前端模块化
(1)模块化基础
-
为什么要模块化
JavaScript原始功能
在网页开发早期,js作为一种脚本语言,做一些简单的表单验证或者动画实现等,那时候代码还是很少的 那时候代码是写在script标签中的 随着ajax异步请求的出现,慢慢形成了前后端分离 客户端要完成的事情越来越多,代码量与日俱增 为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护 但是这种维护方式,依然不能避免一些灾难性的问题 比如全局变量同名问题
我们使用匿名函数来实现,虽然解决了命名冲突问题,但是没有解决代码复用性问题
所以使用模块作为出口,我们可以将需要暴露在外面的变量,作为一个模块的出口,意思如下:
在匿名函数内部,定义一个对象 给对象添加各种需要暴露带外面的属性和方法(不需要暴露的直接定义即可) 最后将这个对象返回,并且在外面使用ModualeA接收 这就是模块化的最基本的封装
随着前端越来越复杂,我们已经不需要自己去使用匿名函数模块化来解决了,很多人已经写好了一些规范化的模块化
常见的模块化规范
CommonJs、AMD、CMD、也有ES6的Modules
-
CommonJS(了解)
模块化核心:导入和导出
CommonJS的导出
module.exports={ flag:true, test(a,b){ return a+b }, demo(a,b){ return a*b } }
CommonJS的导入
let {test,demo,flag}=require('moduleA') 等同于 let mA=require('moduleA') let test=mA.test let demo=mA.demo let flag=mA.flag
-
ES6的模块化
export指令是导出我们模块对外提供的接口,下面我们就可以通过import命令来加载这个模块
首先我们需要再HTML代码中引入两个js文件,并且需要设置type为module
<script src="aaa.js" type="module"></script> <script src="ccc.js" type="module"></script>
import指令用于导入模块中的内容,比如main.js代码
import {flag,num1,height,mul,sum,Person} from './aaa.js' // 这种导入表示导入aaa.js里面默认导出的内容,有且只有一个 import addr from './aaa.js' // 统一全部导入 import * as aaa from './aaa.js' console.log("aaa",aaa)
export指令用于导出
// 导出方式1 export {name,age,flag} // 导出方式2 export var num1=1000; export var height=200. // 导出函数 export {sum}; export function mul(num1,num2){ return num1+num2 } // 导出es6 export class Person{ run(){ console.log('在奔跑') } } //某些情况下,一个某块包含某个功能,我们并不希望给这个功能命名,而让导入者自己命名 export default const address='北京市' // 在开发中默认的导出只能有一个 export default address
注意: export default在同一个模块中,不允许存在多个
8.webpack
内容概述
* 认识webpack
* webpack的安装
* webpack的起步
* webpack的配置
* webpack的使用
* webpack中配置Vue
* Plugin的实验
* 搭建本地服务器
(1)什么是webpack
从官方解释看,webpack是一个现代的JavaScript应用的静态模块打包工具
-
前端模块化
前面我们学习了几种前端模块化的方案:AMD、CMD、CommonJS、ES6规范,目前只能用ES6,原因是浏览器只支持ES6的规范,浏览器做了ES6的底层的支撑 但是在webpack中就能用AMD、CMD、CommonJS,他会在打包的的时候自动将代码进行转换,转换成大部分浏览器都能识别的模块化,所以打包的里面没有AMD、CMD、CommonJS的代码,我们在开发的时候这么使用。webpack做了底层的支撑! 在ES6之前,我们想要进行模块化开发,就必须借助其他的工具,让我们可以进行模块化开发 并且通过模块化开发完成项目后,还需要处理各个模块之间的依赖 而webpack其中一个核心就是让我们可以进行模块化开发,并且他会帮助我们处理模块之间的依赖关系 而且不仅仅是js文件,我们的css、图片、json文件等等再webpack中都可以被当做模块来使用 这就是webpack中模块化的思想
-
打包
理解了webpack可以帮助我们进行模块化,并且处理好了模块间复杂的关系后,打包的概念就非常好理解了 就是将webpack中各种资源进行打包合成一个或多个包 并且在打包过程中,还可以对资源进行处理,比如压缩图片,将scss转换成ES5语法,将ts转换为js等等操作
-
webpack和grunt/gulp对比
但是打包的操作似乎grunt/gulp也可以帮助我们完成,他们有什么不同呢?
gulp的核心是Task
我们会配置一些列的task,并且定义task要处理的事务(例如ES6、ts转化、图片压缩、scss转换成css) 之后grunt/gulp来一次执行这些task,而且让整个流程自动化 所以grunt/gulp也称为前端自动化任务管理工具
什么时候用到grunt/glup呢?
如果你的工程依赖非常简单,甚至没有用到模块化的概念 只需要进行简单的合并、压缩、就使用grunt/glup 但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就需要用到最强大的工具webpack
所以,grunt/glup和webpack有什么不同呢?
grunt/glup更加强调前端流程的自动化,模块化不是他的核心 webpack更加强调模块化开发管理,而文件压缩合并、预处理功能,是他附带的功能
(2)webpack和node和npm关系
webpack为了可以正常运行,必须依赖node环境
node环境为了可以正常执行很多代码,必须其中依赖各种包,手动管理太麻烦,所以安装node的时候会自动安装npm工具,npm工具只是为了方便管理node各种包,npm(node packages manager)。
(3)webpack安装
-
安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm
-
查看自己的node版本
- 全局安装webpack(这里指定版本3.6.0,因为vue cli2依赖该版本 )
-
局部安装webpack(后续才需要)
--save-dev是开发时依赖,项目打包后不需要继续使用
-
为什么全局安装后,还需要局部安装呢?
在终端直接执行webpack命令,使用时全局安装webpack 当在package.json中定义了scripts是,其中包含了webpack命令,那么使用的是局部webpack
webpack如何打包?
webpack ./src/main.js ./dist/bundle.js
bundle.js是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html
webpack配置 webpack.config.js
const path =require('path')
// 将入口文件,出口文件放在配置,执行webpack即可
module.exports={
entry:'./src/main.js',
output:{
// path.resolve()拼接路径,当前路径拼接__dirname+dist
path:path.resolve(__dirname,'dist'),
filename:'bundle.js'
}
}
(4) 局部安装webpack
--save-dev是开发时依赖,项目打包后不需要继续使用的
npm install webpack@3.6.0 --save-dev
webpack作用是打包出去包,只有在开发阶段才需要,打包完了webpack就没有用了
在这里定义脚本,优先执行本地的webpack
只要在终端输入的webpack用的都是全局的webpack
(5)什么是loader
loader是webpack中非常核心的概念
webpack最主要用来做什么?
在我们之前的实例中,我们主要是用webpack来处理我们的js代码,并且webpack会自动处理js之间相关的依赖。
但是,在开发中,我们不仅仅有基本的js代码处理。我们也需要加载css、图片也包括一些高级的将es6转换成ES5代码,将scss、less转换成css,将jsx、.vue文件转换成js文件等等。
对于webpack本身来说,对于这些转移是不支持的
那么怎么办呢?
webpack扩展对应的loader就可以了
loader使用过程
步骤一:通过npm安装需要使用的loader
步骤二:在webpack.config.js的modules关键字下进行配置
大部分的loader我们可以在webpack官网可以看到
(6)处理CSS的loader
文件加载必须依赖css-loader、style-loader。css-loader只负责将css文件进行加载,style-loader负责将样式添加到DOM,依赖这两个loader的时候,先使用css-loader,然后再使用style-loader。但是因为webpack在读取use顺序的时候,是从右向左读取的,所以书写的时候要写成use: ["style-loader","css-loader"]
(7)webpack-less文件处理
如果我们希望在项目中使用less\scss\stylus来写样式,webpack怎么处理呢?
npm install less less-loader@4.1.0 --save-dev
(8)webpack-图片文件处理
npm install url-loader --save-dev
当加载的图片,小于limit时,会将图片编译成base64字符串形式。
大于limit,必须加载file-loader。但是大于图片的时候运行的时候还是会报错,我们主要到,再次打包的时候,dist文件夹下多了一个图片文件。有了这个图片的时候,我们发现图片没有显示出来,这是因为图片的路径没有写对。
-
默认情况下webpack会将生成的路径直接返回给使用者
-
但是,我们整个程序是打包在dist文件夹下的,所以我们需要再路径下添加一个dist/ ,则我们在output中添加
publicPath:'dist/'
-
我们发觉dist打包中的图片命名是32位的hash值,目的是为了防止重名
如果我们相对图片进行命名,我们可以设置name如下:
(8)webpack-ES6语法处理
如果你仔细阅读webpack打包的js文件,会发现写的ES6语法并没有转成ES5,部分浏览器是不能认识的。
babel就是将ES6转换成ES5
babel也是一个loader
(9) webpack配置vue
因为我们后续需要再实际项目中使用vue,所以,并不是开发时依赖
- 第一步:安装vue
npm i vue --save
- 第二步:导入vue,
import Vue from 'vue'
创建vue实例对象
如果页面什么都没显示也没报错,可以设置,然后npm run dev
打包运行的时候发现报错,所以我们指定alias指定我们当前要使用的到底是哪一个vue
(10) el和template区别
我们以后的项目可能是只有一个index.html,一般我们不改index.html。 如果我们想要在index中展示内容,我们可以写在template中
vue内部会把template替换到之前el挂载的位置
同时有el和template,template会吧el替换掉 这样的好处就是我们并不需要改动index.html中的内容
(11)vue终极解决方案
改造(一)
改造(二)
改造(三)
报错了,因为没有对应的loader和vue-template-compiler
安装vue-loader和vue-template-compiler都是开发时依赖
npm install vue-loader@15.4.2 vue-template-compiler@2.5.21 --save-dev
详情见 vue-loader.vuejs.org/zh/guide/#v…
到此.vue已经正常编译了。
注:resolve主要就是用来解决路径问题!
(12) 认识plugin
* plugin是插件的意思,通常是对某个现有的架构进行扩展
* webpack中的插件,就是对webpack现有功能进行扩展,比如打包优化,文件压缩
loader和plugin区别
* loader主要用于转换某些类型的模块,他是一个转换器
* plugin是插件,他是对webpack本身的扩展,是一个扩展器
plugin的使用过程:
步骤一:通过npm安装需要使用的plugins(某些插件不需要安装,内置了)
步骤二:在webpack.config.js中的plugin中配置插件
1.添加版权的Plugin
该插件名字叫做BannerPlugin,属于webpack自带插件
(12) webpack-HtmlWebpackPlugin的使用
目前,我们的index.html文件是存放在项目的根目录下的
* 我们知道在正式发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也没有意义了
* 所以,我们需要将index.html文件打包到dist中,这时候就需要用到HtmlWebpackPlugin插件了
HtmlWebpackPlugin可以为我们做什么?
* 自动生成一个index.html文件(可以指定模板来生成)
* 将打包的js文件,自动通过script标签插入到body中
安装HtmlWebpackPlugin插件
npm install html-webpack-plugin@3 --save-dev
const HtmlWebpackPlugin=require('html-webpack-plugin')
plugins: [
new HtmlWebpackPlugin()
],
此时,打包路径就多余了
(12) js的压缩Plugin的使用
在我们项目发布之前,我们必然需要对js等文件进行压缩处理
* 这里,我们就对打包的js文件夹进行压缩
* 我们使用一个第三方的插件 uglifyjs-webpack-plugin,并且版本号指定1.1.1,和cli2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
修改webpack.config.js配置文件
const UglifyjswebpackPlugin=require('uglifyjs-webpack-plugin')
plugins: [ new UglifyjswebpackPlugin()],
(13)webpack-dev-server搭建本地服务器
webpack可以搭建一个本地的可选服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果
不过它是一个单独的模块,在webpack中使用之前需要先安装它 npm install --save-dev webpack-dev-server@2.9.1
9.Vue-Router
(1)导航守卫
我们通过修改网页标题案例引入导航守卫。
- 普通的修改方式:
我们可以在每个路由对应的vue组件中,通过mounted声明周期函数,执行对应的代码进行修改,但是当页面特别多的时候,维护不方便。
- 导航守卫解决: 当我们从一个路由跳转到另一个路由的时候,beforeEach是一个函数,本身要求传入函数作为参数,传入的的函数有3个参数,from、to、next,next是必须调用的,当你从一个路由跳转到另一个路由的时候,就会执行beforeEach。之后可以在路由中定义meta,元数据(描述数据的数据),我们可以动态的通过ro,拿到meta,但是如果路由存在嵌套的话,就拿不到meta,所以我们直接去拿matched的第一个元素。
第一个home有路由嵌套,拿到meta里面没有title,我们可以去matched里面找
router.beforeEach(function(to,from,next){
console.log("to",to)
// 从from跳转到to
document.title=to.meta.title
next()
})
改造
router.beforeEach(function(to,from,next){
console.log("to",to)
// 从from跳转到to
// document.title=to.meta.title
document.title=to.matched[0].meta.title
next()
})
(2)导航守卫补充
beforeEach()是前置钩子,必须调用next() afterEach()是后置钩子,不需要主动调用next()函数
router.beforeEach(function(to,from,next){
console.log("to",to)
// 从from跳转到to
// document.title=to.meta.title
document.title=to.matched[0].meta.title
next()
})
// afterEach()是后置钩子,不需要主动调用next()函数
router.afterEach((to,from)=>{
console.log("+++++",to,from)
})
上面是我们使用导航守卫,我们称之为全局守卫
- 路由独享的守卫
- 组件内的守卫
(3)路由独享守卫
你可以在路由配置上直接定义beforeEnter守卫
{
path:'/about',
component:()=>import('../components/About'),
meta:{
title:'关于'
},
beforeEnter:((to,from,next)=>{
console.log("路由独享守卫————————",to,from)
next()
})
},
(4)vue生命周期
(5) keep-alive
keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或者避免重新渲染
router-view也是一个组件,如果直接被包在keep-alive里面,所有路径匹配到的组件都会被缓存
使用keep-alive的时候,组件不会被频繁的创建和销毁(可以排除一些组件),被排除的组件还是会被频繁创建和销毁
<keep-alive exclude="Profile,User">
<router-view></router-view>
</keep-alive>
created(){
console.log("created")
},
destroyed(){
console.log("destroyed")
},
5.1补充
//这两个函数只有该组件被保存了状态,使用keep-alive才是有效的
activated(){
console.log("aaa")
this.$router.push(this.path)
},
deactivated(){
console.log('deactivated')
},