Vue是一套构建用户界面的渐进式框架。
一、初识Vue3
1.1 概述
在2020年9月19日,万众期待的Vue3终于发布了正式版,命名为“One Piece”
新的特性:更好的性能、更小的包体积、更好的TypeScript集成、更优秀的API设计
vue3带来的新变化:
1、源码——源码通过Monorepo的形式来管理源代码
源码使用TypeScript来进行重写
Mono:单个
Repo : repositiry仓库
主要是将许多项目的代码存储在同一个repository中,这样做的目的是多个包本身相互独立,可以有自己的功能逻辑、单元测似乎等,同时又在同一个仓库下方便管理,而且模块划分的更加信息,可维护性、可拓展性更强
2、性能
在vue2.x的时候,Vue2是使用Object.defineProperty来劫持数据的getter和setter方法的,但是这种方法的缺陷是当给对象添加或删除属性时,是无法劫持和监听的
而在Vue3.x开始,Vue使用Proxy来实现数据的劫持
3、删除了不必要的API
移除了实例上的 $on, $off 和 $once ;
移除了一些特性:如filter、内联模板等
4、新的API
(1)由Option API 到 Composition API:在vue2.x的时候,我们通过Option API来描述组件的对象,Option API包括data、props、methods、computed、生命周期等等的选项。
存在比较大的问题是多个逻辑可能是在不同的地方:比如created中使用某一个method来修改data的数据,代码的内聚性非常差
(2)Composition API可以将相关联的代码放在同一处进行处理
(3)Hooks函数增加代码的复用性,在vue2.x,我们使用mixins在多个组件之间共享逻辑,但是mixins也是由一大堆Options组成的,并且多个mixins会存在命名冲突;
(3)vue3.x中,Hook函数将一部分独立的逻辑抽取出去,并且它们还可以做到是响应式的
1.2 基本使用
<div id="app">
{{str}}
</div>
<script>
const app = Vue.createApp({
data(){
return {
str:"hello world"
}
}
})
app.mount('#app') //挂载
//或者
Vue.createApp({
data(){
return {
str:"hello world"
}
}
}).mount("#app") //挂载
//或者
const App ={
template:"#my-app",
data () {
return {
info:"hello world"
}
}
}
Vue.createApp(App).mount("#app")
</script>
Vue.createApp({ 对象 })
该对象有什么属性呢?
1、template
表示Vue需要帮助我们渲染的模板信息 它里边包含很多的HTML标签,这些标签替换掉我们挂载的元素的innerHTML
//方法一
<script type="template" id="tem">
//模板内容
</script>
<script>
Vue.createApp({
template:"#tem"
})
</script>
//方法二
<template id="tem">
//模板内容
</template>
<script>
Vue.createApp({
template:"#tem"
})
</script>
2、data属性
1、data属性传入的是一个函数,并且函数返回一个对象。 2、在Vue3.x的时候,如果传入一个对象,会直接报错 3、data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理
3、methods属性
改属性是一个对象,通常会在这个对象中定义很多方法: 1、这些方法会被绑定到template模板中 2、在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性
注意:不能在methods中使用箭头函数,因为这里箭头函数绑定了父级作用域的上下文
- 为什么不能使用箭头函数?
methods:{
fn:()=>{ //不能这样子写
console.log(this) //this指向window
}
}
1.3 拓展补充
CDN
内容分发网络
1、它是指通过相互连接的网络系统,利用最靠近每个用户的服务器
2、更快、更可靠地将音乐、图片、视频、应用程序以及其他文件发送给用户
3、来提供高性能、可拓展性及低成本的网络内容传递给用户
vue3的cdn
<script src="https://unpkg.com/vue@next"></script>
MVVM模式
mvvm(Model - View - ViewModel)是非常流行的架构设计模式
Vue并没有完全遵守MVVM的模型,但是整个设计是受到了它的启发
vscode代码片段
如果经常需要在编辑器中编写重复的代码,我们可以自定义代码片段,从而达到节省时间,高效编程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info}}</h2>
</template>
<script src="./js/vue.js"></script>
<script>
const App ={
template:"#my-app",
data () {
return {
info:"hello world"
}
}
}
Vue.createApp(App).mount("#app")
</script>
</body>
</html>
那么,如何将上边的代码定义为用户的代码片段呢?
1、找到vscode的User snippets
2、选择其中的html.json
3、将我们需要作为模板的代码转换为json格式(https://snippet-generator.app/)
4、将生成的代码输入html.json的对象中
5、如果我们在html.json设置的"prefix": "vueapp",那么以后就可以在html文件中快速通过vueapp命令快速创建模板
二、基本语法
2.1 Mustache语法
如果我们希望把数据显示到模板(template)中,使用最多的语法是 “Mustache”语法 (双大括号) 的文本插值。
注:vue3允许template有多个根元素
<div id="app"></div>
<template id="my-app">
<div>
<!-- 1.直接使用 -->
<div>{{info}}</div>
<!-- 2.表达式 -->
<div>{{count * 10}}</div>
<!-- 3.可以调用计算属性 -->
<div>{{reverseInfo}}</div>
<!-- 4.三元运算符 -->
<div>{{isShow? info : ""}}</div>
</div>
</template>
<script src="./js/vue.js"></script>
<script>
const App ={
template:"#my-app",
data () {
return {
info:"hello world",
count:10,
isShow:true
}
},
computed: {
reverseInfo(){
return this.info.split(" ").reverse().join(" ")
}
}
}
Vue.createApp(App).mount("#app")
</script>
2.2 基本指令
(以下的指令用的并不多,特殊情况下使用)
v-once
当数据发生变化时,元素或者组件以及所有的子元素将视为静态内容并且跳过(该指令可以用于性能优化)
<template id="my-app">
<div>{{count}}</div>
<div v-once>{{count}}</div> //组件渲染后将不会再改变
<button @click="increment">增加</button>
</template>
v-text
用于更新元素的 textContent
<template id="my-app">
<div v-text="count"></div>
//等价于
<div>{{count}}</div>
</template>
v-html
默认情况下,如果展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析。
如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;
<div v-html="info"></div>
//info的HTML标签会被解析
//info:"<h2 >hello world</h2>"
v-pre
v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:
跳过不需要编译的节点,加快编译的速度
<template id="my-app">
<div v-pre>{{info}}</div>
//输出{{info}}
</template>
2.3 v-bind绑定属性
1、v-bind绑定class(对象语法)
<!-- 对象语法:{'类名':Boolean} -->
<div :class="{'actvie':true}">
我走过你的世界
</div>
也可以有多个键值对
<div :class="{'actvie':true,'bgcolor':true}">
我走过你的世界
</div>
注:'actvie' 与 'bgcolor' 可以不用加单引号
也可以将| :class = 对象 |中的对象放在data中,或者通过computed(methods)中定义函数返回
<div :class="isFunc">
我走过你的世界
</div>
2、v-bind绑定class(数组语法)
<div :class="['title',msg]"></div>
// 字符串 变量
//title,以及msg指向的内容会被当类名做添加进div
3、绑定style(对象语法)
<div :style="{color:'red'}">我变红了</div>
//注:这里的red必须加引号,不加则会当变量处理
<div :style="{color:'red','font-size':'20px'}">我变红了</div>
//使用-连接的css属性需要引号,使用驼峰命名则不用
<div :style="{color:'red',fontSize:'20px'}">我变红了</div>
4、动态绑定属性
根据data中的数据动态决定div的属性名称与值
<div :[name]="value">
动态绑定属性名与值
</div>
data(){
return {
name:'abc',
value:'info'
}
}
5、动态绑定一个对象
<div v-bind="info">哈哈哈哈</div>
data(){
return {
info:{
name:'mzlin',
age:20,
height:1.80
} }}
//效果:最终
<div name:'mzlin' age="20" , height="1.80">哈哈哈哈</div>
2.4 v-on绑定事件
在前端开发中,我们需要经常和用户进行各种各样的交互: 这个时候,我们就必须监听用户发生的事件,比如点击、拖拽、键盘事件等等
在Vue中如何监听事件呢?使用v-on指令。
<!-- 基本使用 -->
<button v-on:click="btnClick">按钮</button>
<!-- 语法糖 -->
<button @click="btnClick">按钮</button>
<!-- 绑定一个表达式 -->
<button @click="count++">按钮</button>
<!-- 绑定一个对象 -->
<div class="box" v-on="{click:btnClick,mouseover:mouseMove}"></div>
参数传递
<!-- 参数传递 -->
<button @click="btnClick">按钮</button>
methods:{
btnClick(event){
console.log(event)
//输出事件对象
}
}
如果需要传递多个参数,需要给改为$event
<!-- 参数传递 -->
<button @click="btnClick($event,'mzlin')">按钮</button>
methods:{
btnClick(event,name){
console.log(event,name)
//输出事件对象
}
}
修饰符
2.5 条件渲染
v-if 、v-else 、 v-else-if
根据条件来渲染某一块的内容,只有当条件为true时,才会被渲染出来
<template id="my-app">
<div v-if="isShow">你好</div>
</template>
data () {
return {
isShow:false
}
}
<template id="my-app">
<div v-if="score > 90">优秀</div>
<div v-else-if="score > 60 && score <=90">良好</div>
<div v-else="score <= 60">不及格</div>
</template>
data () {
return
score:88
}
渲染原理
1、v-if 是惰性的
2、当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉(即标签不存在)
3、当条件为true时,才会真正渲染条件块中的内容
template元素
v-if是一个指令,所以必须添加到一个元素上,但是如果我们希望切换多个元素呢?
<template id="my-app">
<div v-if="isShow">
<h2>嘿嘿嘿嘿</h2>
<h2>嘿嘿嘿嘿</h2>
<h2>嘿嘿嘿嘿</h2>
</div>
<div v-else>
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
</div>
</template>
问题:template最终都会残留一个外层div标签,然后并没有必要
这个时候我们可以使用template来包裹
<template v-if="isShow">
<h2>嘿嘿嘿嘿</h2>
<h2>嘿嘿嘿嘿</h2>
<h2>嘿嘿嘿嘿</h2>
</template>
<template v-else>
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
</template>
template元素可以当做不可见的包裹元素,并且在v-if上使用,但是最终template不会被渲染出来
v-show
v-show与v-if都是能够动态控制元素是否渲染到页面上
<div v-show="isShow">
哈哈哈哈诶
</div>
//区别
<div v-if="isShow">你好</div>
v-if与v-show的区别
1、v-show不支持template
2、v-show不可以和v-else一起使用
3、v-show元素无论是否需要显示到浏览器上,它的DOM实际上都是有渲染的,只是通过css的display来进行切换
4、v-if当条件为false时,其对应的元素不会被渲染到DOM
如何选择?
1、如果元素需要在显示和隐藏之间平凡切换,则使用v-show(它只是改变display的值)
2、如果不频繁切换,则使用v-if
2.6 v-for
v-for除了遍历数组,也可以遍历对象
<template id="my-app">
<!-- 数组 -->
<div v-for="(item,index) in arr">
{{index}}-{{item}}
</div>
<!-- 对象 -->
<div v-for="(value,key,index) in obj">
//默认只传一个参数时,默认为value
{{index}} - {{key}} - {{value}}
</div>
</template>
data () {
return {
arr:['魔戒','哈利波特','小谢尔顿','楚门的世界'],
obj:{
movie:"盗梦空间",
music:"枫",
game:'LOL',
novel:'穆斯林的葬礼'
}
}
}
也可以遍历数字
<!-- 遍历数字 -->
<div v-for="num in 20">
{{num}}
</div>
v-for中key的作用
在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性
官方的解释是:
1、key属性主要用在vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes
2、如果不使用key,vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改、复用相同类型元素的算法
3、而使用key时,它会基于key的变化重新排列元素的顺序,并且会移除、销毁key不存在的元素
要理解以上概念,我们需要先了解VNode
VNode
VNode的全称是Virtual Node,也就是虚拟节点。VNode的本质是一个JavaScript的对象。
事实上,无论是组件还是元素,他们最终在Vue中表示出来的都是一个个VNode
vue会将template里边的内容进行处理,形成VNode
例如:
<template id="my-app">
<div class="title" style="font-size: 30px; color: red;">嘿嘿嘿</div> </template>
//最终表现出来的VNode是
const vnode = {
type:"div",
props:{
class:"title",
style:{
"font-size":"30px",
"color":"red"
}
},
chlidren:"嘿嘿嘿"
}
虚拟DOM
vNode不只是一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode Tree:
我们通过案例分析key的作用
<template id="my-app">
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<button @click="insertF">insert f</button>
</template>
const App ={
template:"#my-app",
data () {
return{
letters:['a','b','c','d']
}
},
methods:{
insertF(){
this.letters.splice(2,0,'f')
}
}
可以确定的是,这次更新对于ul和button是不需要进行更新,需要更新的是li的列表:
Ø 在Vue中,对于相同父元素的子元素节点并不会重新渲染整个列表;
Ø 因为对于列表中 a、b、c、d它们都是没有变化的;
Ø 在操作真实DOM的时候,只需要在中间插入一个f的li即可;
那么Vue中对于列表的更新究竟是如何操作的呢?
Ø Vue事实上会对于有key和没有key会调用两个不同的方法;
Ø 有key,那么就使用 patchKeyedChildren方法;
Ø 没有key,那么久使用 patchUnkeyedChildren方法;
2.7 计算属性
对于任何响应式数据的复杂逻辑,都应该使用计算属性
计算属性被混入到组件实例中,所有getter和setter的this上下文自动地绑定为组件实例
计算属性的用法:
选项:computed
类型:{ [key: string]: Function | { get: Function, set: Function } }
<template id="my-app">
<!-- 执行三次 -->
<div>{{getFullName()}}</div>
<div>{{getFullName()}}</div>
<div>{{getFullName()}}</div>
<!-- 执行一次 -->
<div>{{fullName}}</div>
<div>{{fullName}}</div>
<div>{{fullName}}</div>
</template>
data () {
return {
firstName:'lin',
lastName:'明'
}
},
methods:{
getFullName(){
console.log('调用多次,执行多次');
return this.firstName + " " + this.lastName
}
},
computed: {
fullName(){
console.log('执行一次,有缓存');
return this.firstName + ' ' + this.lastName
}
}
}
计算属性会基于他们的依赖关系进行缓存,当数据不发生变化时,计算属性不需要重新计算的。
但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算
计算属性的getter和setter
computed: {
fullName(){ //相当于get的语法糖写法
return this.firstName + ' ' + this.lastName
},
// 相当于get
fullName:{ //计算属性的另一种写法
get:function() {
return this.firstName + ' ' + this.lastName
}
}
}
<button @click="changeFullName()">改值</button>
computed: {
fullName:{
get:function() {
return this.firstName + ' ' + this.lastName
},
set:function (newValue) {
console.log(newValue)
}
}
},
methods:{
changeFullName(){
this.fullName = '林林同学'
}
}
2.8 侦听器watch
类型:{ [key: string]: string | Function | Object | Array }
侦听器也称为监听器,当代码逻辑发生变化时,我们可以使用watch来进行相应的监听操作
<template id="my-app">
请输入密码:<input type="text" v-model="password">
</template>
<script src="./js/vue.js"></script>
<script>
Vue.createApp({
template:'#my-app',
data () {
return {
// 侦听password的变化,去进行一些逻辑的处理
password:''
}
},
// 监听
watch: {
// password侦听data中属性的名称
// newValue变化后的新值
// oldValue变化前的旧值
password(newValue,oldValue) {
console.log('新值:',newValue, '旧值:',oldValue);
}
}
}).mount('#app')
</script>
问题:默认情况下,侦听器只会针对侦听数据本身的改变(内部的改变时不能侦听的)
例如:可以监听到info对象,整个对象的改变。但是当info对象中的某个属性改变时,侦听不了
<template id="my-app">
{{info.name}}
<!--点击按钮2时改变,但是侦听不到-->
<button @click="changeInfo">改变对象</button>
<button @click="changeInfoName">改变对象中的值</button>
</template>
<script src="./js/vue.js"></script>
<script>
const app = {
template:'#my-app',
data () {
return {
info:{
name:'linming',
age:18
}
}
},
// 监听
watch: {
info(newValue,oldValue){
console.log('新值',newValue,'旧值',oldValue);
}
},
methods:{
changeInfo(){
// 改变整个info
this.info = {name:'哈利波特',age:12}
},
changeInfoName(){
//改变info中的值
this.info.name = '嘿嘿嘿'
}
}
}
Vue.createApp(app).mount("#app")
</script>
为了解决这个问题,我们需要设置侦听器的配置选项
侦听器的配置选项
1、深度侦听
开启深度侦听后,我们便可以侦听info的内部的变化了
watchinfo{ deep:true }
// 监听
watch: {
info:{
handler: function(newValue,oldValue) {
console.log('newValue:',newValue,'oldValue',oldValue);
},
deep:true //深度侦听
}
},
2、立即执行
页面加载时,立即侦听一次info
watch: {
info:{
handler: function(newValue,oldValue) {
console.log('newValue:',newValue,'oldValue',oldValue);
},
deep:true, //深度侦听
immediate:true
}
},
侦听器的另一种使用方式
created(){
this.$watch("info", (newValue,oldValue)=>{
console.log('newValue',newValue,'oldValue',oldValue);
}, { deep:true })
}
2.9 v-model数据绑定
实现原理:
1、v-bind绑定value属性
2、v-on绑定input事件监听到函数中,函数会获取最新的值,并赋值到绑定的属性中
<template id="my-app">
<input type="text" :value="info" @input="changeInput">
<h2>{{info}}</h2>
</template>
<script src="./js/vue.js"></script>
<script>
const App ={
template:"#my-app",
data () {
return {
info:"hello world"
}
},
methods: {
changeInput (event) {
this.info = event.target.value
}
}
}
Vue.createApp(App).mount("#app")
</script>
所以,v-model实际上是上边写法的一种语法糖
1、v-model与textarea
<!-- v-model绑定textarea -->
<textarea cols="30" rows="10" v-model="article"></textarea>
<div>内容:{{article}}</div>
2、v-model绑定checkbox
单个勾选框
<!-- v-model绑定checkbox -->
<!-- 1、单选框 -->
<label for="agreement">
<input id="agreement" type="checkbox" v-model='isAgree'>同意协议
<div>isAgree:{{isAgree}}</div>
</label>
注:当label与相关属性的id属性相同时,当鼠标点击在文本上,就会触发此控件
多个勾选框
<!-- 2、多选框 -->
<div>
<label for="football">
<input type="checkbox" value="football" id="football" v-model="hobbies">足球
</label>
<label for="basketball">
<input type="checkbox" value="basketball" id="basketball" v-model="hobbies">篮球
</label>
<label for="tennis">
<input type="checkbox" value="tennis" id="tennis" v-model="hobbies">网球
</label>
<div>hobbies:{{hobbies}}</div>
</div>
3、v-model绑定radio
<div>
<label for="male">
<input type="radio" id="male" value="male" v-model="gender">男
</label>
<label for="female">
<input type="radio" id="female" value="female" v-model="gender">女 </label>
<div>你的选择:{{gender}}</div>
</div>
4、v-model绑定select
<!-- v-model 绑定select -->
<div>
<select v-model="fruit">
<option value="banana">香蕉</option>
<option value="apple">苹果</option>
<option value="pear">梨子</option>
</select>
<div>你的选择:{{fruit}}</div>
</div>
v-model修饰符
1、v-model.lazy=""
默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定 的属性进行同步;如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车) 才会触发;
2、number
使用v-model双向绑定数据时,vue会将同步的数据转化为string类型。使用number修饰符则可以将其改为number类型
3、trim
使用v-model.trim="",可以自动过滤自动过滤用户输入的守卫空白字符
<template id="my-app">
账号:<input type="text" v-model.trim="msg">
<button @click="getMsg">捕捉</button>
</template>
methods: {
getMsg(){
console.log(this.msg);
}
}
三、 组件化开发
3.1 注册全局组件
<body>
<div id="app"></div>
<template id="my-app">
{{info}}
<component-a></component-a>
</template>
<script>
const App = {
template:'#my-app',
data(){
return{
info:'hello world'
}
}
}
const app = Vue.createApp(App)
// 注册全局组件
app.component('component-a',{
template:
`<h2>我是标题</h2>
<div>我是内容</div>`,
data(){
return {}
},
methods:{}
})
app.mount("#app")
</script>
3.2 注册局部组件
<body>
<div id="app"></div>
<template id="my-app">
{{info}}
<component-a></component-a>
</template>
<!-- 局部组件模板 -->
<template id="ComponentA">
<h2>我是标题</h2>
<div>我是内容</div>
</template>
<script>
const ComponentA ={
template:'#ComponentA'
}
const App ={
template:"#my-app",
components: {
ComponentA
},
data () {
return {
info:"hello world"
}
}
}
Vue.createApp(App).mount("#app")
</script>
</body>
四、webpack
4.1 初识webpack
webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序
1、打包bundler:webpack可以将代码进行打包
2、静态的static:最终打包的代码是静态资源
3、模块化module:webpack默认支持各种模块化开发,如ES Module、CommonJS等
脚手架是依赖于webpack的,目前前端三大框架的脚手架Vue-CLI、create-react-app、Angular-CLI都依赖于webpack。
webpack的运行依赖于Node,所以电脑上必须要有Node环境
webpack各个版本之间不能相互兼容
安装
webpack的安装目前分为两个:webpack、webpack-cli
npm install webpack webpack-cli –g # 全局安装
npm install webpack webpack-cli –D # 局部安装
使用局部webpack npx webpack
注意:在真正的开发过程中,用的更多的是局部安装。一个项目有对应该项目版本的webpack。
另外:在开发项目时,只需要安装webpack的开发时依赖,不需要运行时依赖。
webpack与webpack-cli之间的联系:
1、执行webpack命令,会执行node_modules下的.bin目录下的webpack
2、webpack在执行时是依赖webpack-cli的,没有安装就报错
3、而在webpack-cli中代码执行时,也需要用到webpack进行编译和打包
4.2 webpack的基本使用
在js文件夹下创建两个函数,并用不同的规范导出
//sum.js文件// ES6语法导出
export function addNum (a,b){return a + b ;}
//mult.js文件
function mult (a,b) { return a * b}
// CommonJS的导出
module.exports = {mult}
将两个文件导入到入口文件index.js中
// 获取通过es6导出的addNum函数
import {addNum} from './js/Sum'// 获取CommonJS导出的mult函数
const {mult} = require('./js/mult')
console.log(addNum(10,20));
console.log(mult(10,20));
在index.html中引用入口文件index.js(注意需要开启服务器才能运行)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title>
</head>
<body>
//直接导出,浏览器识别不了
<!-- <script src="./index.js" type="module"></script> -->
//引用打包好的文件
<script src="./dist/main.js"></script>
</body>
</html>
但是报错了
原因:浏览器不能识别ES6语法,或者不能识别node语法
解决:通过webpack将入口文件打包成浏览器能够识别的js代码
webpack ./index.js -o ./dist
自定义打包命令
初始化:npm init
在package.json中定义
"scripts": { "build":"webpack" },
之后就可以通过npm run build进行打包(注意:源代码必须在src文件夹下)
当执行npm run build打包时,webpack默认会到src文件夹下寻找index.js,所以src文件夹是必要的,index.js入口文件也是必要的
当然,也可以指定以哪个文件作为入口文件(可以不必是index.js),哪个文件夹作为出口文件夹(可以不必是dist)
npx webpack --entry ./src/main.js --output-path ./build
//入口 任意文件名 出口 任意文件名
最终解决方案:自定义打包
综上所述的打包方式都是不是很方便,即使使用的是npm run build,也需要提前创建好src,并且将index.js文件创建好
所以,可以在项目中创建webpack.config.js文件,用来解决打包的路径文件问题
const path = require("path");
module.exports = {
entry:"./src/main.js", //入口文件的位置
output:{
path:path.resolve(__dirname,'./build'),
//利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build
filename:"bundle.js"
// 打包好的文件名
}}
再次使用npm run build时,会执行webpack.config.js的代码,读取里边的信息,按照配置信息进行打包
loader配置方式
rules属性对应的值是一个数组:[Rule]
数组中存放的是一个个的Rule,Rule是一个对象,对象中可以设置多个属性:
1、test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;
2、use属性:对应的值时一个数组:[UseEntry]
UseEntry是一个对象,可以通过对象的属性来设置一些其他属性
Ø loader:必须有一个 loader属性,对应的值是一个字符串;
Ø options:可选的属性,值是一个字符串或者对象,值会被传入到loader中;
Ø query:目前已经使用options来替代;
传递字符串(如:use: [ 'style-loader' ])是 loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ]);
loader属性: Rule.use: [ { loader } ] 的简写。
webpack打包css
webpack默认可以识别CommonJS、ES6,但是不能识别css。如果想要识别css,必须安装css-loader
1、安装
npm install css-loader -d
npm install style-loader -d
2、配置
const path = require("path");module.exports = {
entry:"./src/main.js", //入口文件的位置
output:{
path:path.resolve(__dirname,'./build'),
//利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build
filename:"bundle.js"
// 打包好的文件名
},
module:{
rules:[
{
test:/\.css$/, //匹配模块中以.css文件结尾的
//用法一(语法糖)
// loader:"css-loader",
// 用法二:(完整写法)
use:[
// 注意顺序
"style-loader",
"css-loader"
]
}
]
}}
webpack打包less
局部安装less :npm install less -d
测试
// test.less
@bgColor : #abc;
@fontSize : 30px;
#box2{
background-color:@bgColor;
font-size:@fontSize;
}
通过npx找到module包里边lessc转换为css:npx lessc ./test.less demo.css
// demo.css#box2 {
background-color: #abc;
font-size: 30px;
}
通过webpack的方式就行打包less文件
1、安装less-loader: npm install less-loader -d
2、配置webpack.config.js文件
module:{
rules:[
{
test:/\.less$/, //匹配模块中的.less结尾的文件
use:[
"style-loader",
"css-loader",
"less-loader"
]
}
]
}
注意:webpack是从下往上执行loader的,过程为:先把less转为css,再由css转为浏览器能够识别的打包代码
4.3 webpack打包其他资源
1、打包图片资源
安装loader:npm install file-loader -d
配置
module:{
rules:[
{
test:/\.(jpg|png|jpeg|gif)/ ,
use:[
"file-loader"
]
}
]
}
测试:
通过element.js向页面添加div元素,并设置类名为bg-img
// 创建div标签
const bgdiv = document.createElement("div")
bgdiv.className = "bg-img"
// 将bgdiv添加进body
document.body.appendChild(bgdiv)
通过css将图片作为该div的背景图
.bg-img{
background-image: url('../img/b1.jpeg');
width: 200px;
height: 100px;
}
将文件都导入入口文件后进行打包,就可以看到图片被打包到了build文件夹下
如果有多种静态资源(如多个图片)都放在build文件夹下,结构会显得混乱。那么我们可以通过配置webpack的相关配置来规定路径
module:{
rules:[
{
test:/\.(jpg|png|jpeg|gif)/ ,
use:{
loader:"file-loader",
options:{
// [ext]表示根据原有图片的后缀命名
name:'img/[name]_[hash:6].[ext]'
//[name]_[hash:6] 表示文件名的命名规则
}
}
}
]
}
2、打包图片资源2
原理:url-loader和file-loader的工作方式是相似的,但是可以将较小的文件,转成base64的URI。
在被打包好的文件夹中,不再能看见图片资源,默认情况下,url-loader会将所有图片资源转成base64编码
优点:占用更小的空间,有利于优化高并发
安装npm install url-loader -d
配置
{
test:/\.(jpg|png|jpeg|gif)/ ,
use:{
loader:"url-loader",
options:{
name:'img/[name]_[hash:6].[ext]'
}
}
}
问题:但是对于体积较大的图片,一般不进行打包。因为打包出来的base64编码多,造成bundle.js文件体积过大,反而也会影响加载速度。
所以一般只对体积小的图片使用用url-loader
{
test:/\.(jpg|png|jpeg|gif)/ ,
use:{
loader:"url-loader",
options:{
name:'img/[name]_[hash:6].[ext]',
limit: 100*1024
//即小于100kb的图片使用url-loader进行打包,大于的不使用
}
}
}
3、打包图片资源3
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现;
asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;
asset/source 导出资源的源代码。之前通过使用 raw-loader 实现;
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体
积限制实现;
4.4 Plugin 插件
plugin与loader的区别
loader适用于特定的模块类型进行转换,Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等
1、cleanWebpackPlugin
功能:每次执行打包命令时,将原有的dist文件夹删除
安装:npm install clean-webpack-plugin -d
配置:
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
plugins:[
new CleanWebpackPlugin()
// CleanWebpackPlugin是一个类
]
2、HtmlWebpackPlugin
最终在打包的dist文件夹下根据webpack的模板生成index.html文件。
安装:npm install html-webpack-plugin -d
配置:
module.exports = {
entry:"./src/main.js", //入口文件的位置
output:{
path:path.resolve(__dirname,'./build'),
//利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build
filename:"js/bundle.js"
// 打包好的文件名
},
module:{
rules:[]
},
plugins:[
new CleanWebpackPlugin() ,
// CleanWebpackPlugin是一个类
new HtmlWebpackPlugin()
]}
注:默认情况下是根据ejs模板来生成的inedx.html的,不是根据根目录下的
3、自定义html模板
如果不想使用ejs模板的index.html模板,想用自己自定义的html模板,如下vue3-cli模板
在根目录下创建public/index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
所以,当我们在配置HtmlWebPackPlugin时,我们可以添加如下的配置
1、template:指定想要使用的模板所在的路径
plugins:[
new HtmlWebpackPlugin({
template:'模板路径'
})
]
2、title:在进行htmlWebpackPlugin.options.title读取时,就会读到该信息;
但是此时使用npm run build进行打包时会报错,因为模板中还使用到一个BASE_URL的常量
解决:
使用DefinePlugin
DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装)
配置
const {DefinePlugin} = require('webpack');
plugins:[
new CleanWebpackPlugin() ,
// CleanWebpackPlugin是一个类
new HtmlWebpackPlugin({
template:'./public/index.html',
//根据这个html模板进行打包
}) ,
new DefinePlugin({
BASE_URL:"'./'"
})
]
此时打包就不会报错了
4、CopyWebpackPlugin
在vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。
因为public文件夹一般用于存放静态资源,不需要进行转换改变的资源。
这个复制的功能,我们可以使用CopyWebpackPlugin来完成;
安装:npm install copy-webpack-plugin -d
相关的配置:from:设置从哪一个源中开始复制。to:复制到的位置。globOptions:设置一些额外ed选项,如要忽略的文件
plugins:[
new CleanWebpackPlugin() ,// CleanWebpackPlugin是一个类
new HtmlWebpackPlugin({
template:'./public/index.html', //根据这个html模板进行打包
}) ,
new DefinePlugin({
BASE_URL:"'./'"
}),
new CopyWebpackPlugin({
patterns:[
{
from:"public",
to:"./",
globOptions:{
ignore:[
"**/index.html" //除了index.html外,都复制过去
]
}
}
]
})
]
4.5 mode模式
Mode配置选项,可以告知webpack使用响应模式的内置优化:
1、默认值是production(什么都不设置的情况下---生产阶段的代码,难以阅读调试);
2、可选值有:'none' | 'development' | 'production';
module.exports = {
//设置模式
//development 开发阶段,会设置development
//production 准备打包上线的时候,设置production
mode:"development",
// 设置source-map,建立js映射文件,方便调试代码和错误
devtool:"source-map",
entry:"./src/main.js", //入口文件的位置
output:{
path:path.resolve(__dirname,'./build'), //利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build
filename:"js/bundle.js" // 打包好的文件名
},
module:{
},
plugins:[]
}
4.6 Babel
在实际开发中,我们很少直接去接触babel,但是babel对于前端开发来说,目前是不可缺少的一部分:
1、ES6+、TypeScript、开发react项目,都离不开Babel
2、学习Babel对于理解代码从编写到线上转变过程至关重要
那么,什么是Babel
Babel是一个工具链,主要用于旧浏览器或环境中将ES6+代码转换为向后兼容版本的JavaScript,包括:语法转换、源代码转换等
babel命令行的使用
安装:
npm install @babel/cli @babel/core -d
1、@babel/core:babel的核心代码,必须安装
2、@babel/cli:可以让我们在命令行使用babel
使用:
npx babel src --out-dir dist
src : 是源文件目录
--out-dir : 指定要输出的文件夹dist
babel可以指定代码中的那些语法进行转换(使用插件),也可以使用预设(preset):
安装:
npm install @babel/preset-env -d
使用:
npx babel src --out-dir dist --presets=@babel/preset-env
webpack的babel-loader
1、安装:
npm install babel-loader -d
npm install @babel/core -d babel核心代码(必要)
npm install @babel/preset-env -d 预设插件
2、配置webpack.config.js
module:{
rules:[
{
test:/\.js$/, //js结尾的文件使用babel进行打包转换
use:{
loader:"babel-loader",
options:{
presets:[
"@babel/preset-env"
]
}
}
}
]
}
当webpack.config.js插件或者loader太多,难以阅读维护,可以单独抽离出去
//babel.config.js文件
module.exports = {
presets:[
["@babel/preset-env"]
]
}
//webpack.config.js文件
module:{
rules:[
{
test:/\.js$/, //js结尾的文件使用babel进行打包转换
use:"babel-loader"
}
]
}
4.7 vue
在vue的开发过程中,有三种方式来编写DOM元素:
方式一:template模板的方式
方式二:render函数的方式,使用h函数来编写渲染的内容
通过.vue文件的template来编写模板
vue在让我们选择版本的时候分为:运行时+编译器 vs 仅运行时
1、运行时+编译器 包含了对template模板的编译代码,更加完整,但是也更大一些;
2、仅运行时,没有包含对template版本的编译代码,相对更小一些。
安装vue2:npm install vue
安装vue3:npm install vue@next
当我们向main.js中使用vue时,出现了警告
import {createApp} from 'vue' //导入vue的createApp方法
const app = createApp({
template:`<div>这是vue渲染出来的<div>`,
data(){
return{
info:"hahahah"
}
}
})
app.mount("#app")
由于webpack默认使用了 ‘ 仅运行时 ’ 版本的代码,不包含template的编译,所以无法渲染出来
修改import {createApp} from 'vue/dist/vue.esm-bundler'
安装npm i vue-loader@next -d 并配置
// 配置.vue文件打包
{
test:/.\vue$/,
loader:"vue-loader"
}
打包时报错,因为vue-loader同时依赖于另外一个包:安装npm i @vue/compiler-sfc -d
同时需要再配置一个插件,该插件存在于vue-loader的包里,可以直接配置使用
const {VueLoaderPlugin} = require("vue-loader/dist/index")
// 使用v-loader插件
new VueLoaderPlugin()
4.8 webpack搭建本地服务
每次修改代码都需要使用npm run build打包编译代码,影响效率。
为了完成自动编译,webpack提供了几种可选的方式:
1、webpack watch mode
2、webpack-dev-server(常用)
3、webpack-dev-middleware
1、webpack watch mode
webpack提供了watch模式,在该模式下,webpack依赖于图中所有的文件,只要一个发生了更新,那么代码将重新编译
开启方式一:
在导出的配置中,添加watch:true
module.exports = {
mode:"development",
devtool:"source-map",
watch:true
}
开启方式二:在启动webpack的命令中,添加--watch标识
"scripts": {
"build": "webpack --watch"
},
2、webpack-dev-server
通过配置watch的方式可以监听到文件的变化,但是事实上它本身没有自动刷新浏览器的功能,而是依赖了vscode的live-server插件
那么,如何在不适用live-serve的情况下,可以具备live reloading(实时重新加载)的功能
安装:
npm install webpack-dev-server -d
配置:
"scripts": {
"build": "webpack --watch",
"serve":"webpack serve"
},
注意:webpack-dev-server在编译之后不会写入到任何输出文件(即不进行任何打包),而是将bundle文件保留在内存中。事实上,它使用了一个名为menfs的库
另外,如果修改了webpack.confog.js里边的代码,还是要重新执行npm run serve命令
dev-sercer的配置
1、contentBase
module.exports = {
//dev-server的配置
devServer:{
contentBase:"./public"
}
}
在不适用CopyWebpackPlugin插件的情况下,该配置意味着public文件夹下的资源也可以在浏览器中呈现。但是跟CopyWebpackPlugin相区别,contentBase只是不会将资源打包进bundle。
该配置,可以有利于开发阶段,不想将大型public下的资源进行打包,浪费时间性能时可以使用
2、模块热替换(HMR)
HMR全称是Hot Module Replacement,是指在应用程序运行的过程中,添加、替换、删除模块,而无须重新刷新整个页面
优点:
- 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失
- 只更新变化的内容,节省开发时间
- 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式
如何使用?
默认情况下,webpack-dev-server支持HMR,只需要开启即可
target:"web", //针对web浏览器
devServer:{
hot:true //开启
},
为需要操作的模块进行配置
import add from './js/add'
if(module.hot) {
module.hot.accept("./js/add.js",()=>{
console.log("add文件热加载成功");
})
}
这样一来,在add.js文件中更改内容,就可以进行热更新了
事实上,如果在vue中进行修改、替换、删减操作,不需要以上的操作,因为vue-loader中默认已经有HMR,开箱即用
3、host配置
host设置主机地址:默认是localhost,如果希望其他地方也可以访问,可以设置为0.0.0.0
devServer:{
host: "0.0.0.0"
},
localhost与0.0.0.0的区别:
1、localhost本质上是一个域名,通常情况下会被解析成127.0.0.1;
2、127.0.0.1:回环地址,表达的意识是主机自己发出去的包,直接被自己接收;
a、正常的数据库包经历:应用层-传输层-网络层-数据链路层-物理层
b、回环地址:在网络层直接被获取到了,是不会经历数据链路层和物理层的
c、比如监听127.0.0.1时,在同一网段下的主机中,通过ip地址是不能访问的;
3、0.0.0.0 :监听IPV4上所有的地址,再根据端口找到不同的应用程序;
a、比如监听0.0.0.0时,在同一网段下的主机中,通过ip地址是可以访问的
4、其他配置
port
设置监听的端口,默认情况下是8080
open
more是false,设置为true会打开浏览器
compress
默认值为false,设置为true会开启静态文件打包为gzip
5、proxy
proxy是开发中非常常用的一个配置选项,它的目的是设置代理来解决跨域访问问题。
**跨域问题 **:
比如发送一个api请求时http://localhost:8888,但是本地启动服务器的域名是 http://localhost:8000,这是候发送网络请求就会出现跨域的问题。
解决方案:将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域问题,就可以解决我们的跨域问题
案例
假设我们现在开启一个本地服务器http://localhost:8888,并向里边content请求内容.已知我们当前的服务器域名为http://localhost:8000,于是出现了跨域访问问题,导致请求数据失败
// 测试跨域问题
import axios from 'axios'
axios.get("http://localhost:8888/content").then(res=>{
console.log(res);
})
所以我们需要通过live-server的代理来解决这个问题
module.exports = {
devServer:{
proxy:{
"/api": {
target:"http://localhost:8888",
pathRewrite:{
"^/api" : ""
}
}
}
}
}
同时,发送网络请求的代码也要相应的进行修改
import axios from 'axios'
axios.get("/api/content").then(res=>{
console.log(res);
})
这样子就可以正常访问到想要的数据了
4.9 resolve模块解析
(用于起别名,引用文件的时候省略扩展名)
resolve用于设置模块如何被解析
1、在开发中我们会有各种各样的模块依赖,这些模块可能自己编写的代码,也可能是来自第三方库
2、resolve可以帮助webpack从每个require/import语句中,找到需要引入的合适的模块代码
- 应用一:省略文件扩展名配置
module.exports = {
resolve:{
extensions:['.js','.vue','.ts']
}
}
之后引用文件由import App from './vue/App.vue'可变为 import App from './vue/App'
原理:如果导入的是一个文件,文件具有扩展名,则可以直接打包;否则,将使用resolve.extensions选项作为扩展名解析。
extensions的默认值:【'.wasm' , '.mjs' , '.js' , '.json'】
- 应用二:起别名alias
1、当我们项目的目录结构比较深的时候,或者一个文件的路径需要.././../这种路径片段;就可以给某些常见的路径起一个别名
const path = require('path')
module.exports = {
resolve:{
alias: {
"js":path.resolve(__dirname,"./src/js"),
"@":path.resolve(__dirname,"./src/js")
}
}
}
之后就可以在导入文件的时候使用别名了
import add from 'js/add'
const {mult} = require("js/mult")
五、组件化开发
vscode插件:
1、volar(识别vue——官方指定)
2、vetur(识别vue)
3、Vue VSCode Snippets(代码片段)
5.1 父传子
props
1、props是在组件上注册一些自定义的attribute;
2、父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值
两种写法:
方式一:字符串数组,数组中的字符串就是attribute的名称
//子组件
<script>
export default {
props: ["info1","info2"]
}
</script>
//父组件
<template>
<nav-bar info1="传递信息一" info2="传递信息二"></nav-bar>
</template>
方式二:对象类型,对象类型使我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等。
//指定类型
props: {
propsA:String
}
//指定是否为必传的字符串
props: {
propsB:{
type:String,
require:true,
}
}
//指定类型为数值,默认值为200,父组件没传值时,显示200
props: {
propsC:{
type:Number,
default:200
}
}
//当父组件传递给子组件的值是一个对象时,且有默认值时,default是一个函数。
props: {
obj:{
type:Object,
default(){
return {} //默认返回一个空对象
}
}
}
5.2 子传父
vue3有新的写法
数组写法
//子组件
<template>
<div>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<input type="text" v-model.number="num">
<button @click="incrementN">+n</button>
</div>
</template>
<script>
export default {
data () {
return {
num:0
}
},
// 数组写法
emits: ["add", "sub", "addN"],
methods: {
increment(){
console.log("++");
this.$emit("add");
},
decrement(){
console.log("--");
this.$emit("sub");
},
//传递参数
incrementN(){
this.$emit("addN",this.num)
}
}
}
</script>
//父组件
<template>
<div>
{{counter}}
<counter-click @add="addOne" @sub="subOne" @addN="incrementN"></counter-click>
</div>
</template>
对象写法
// 对象写法,可以对传递的参数进行验证
emits:{
"add":null, //不进行验证
"sub":null, //不进行验证
"addN": function(payload) {
if(payload ===10){
return true
}
return false
//当输入的值>10时,也能传递过去,但是会报警告
}
},
5.3 非父子组件通信
两种方式:
1、provied/inject
2、Mitt全局事件总线
方式一:Provide与Inject
有一些深度嵌套的组件,子组件想要获取父组件的部分内容,在这种情况下,如果仍然将props沿着组建链逐级传递下去,就会非常麻烦。
比如A是B的子组件,C是B的子组件,D是C的子组件,现在D想要获取A的部分内容,就可以使用Provide与Inject
//祖先(提供数据)
provide:{
name:"linming",
age:18
},
//后代(接收数据)
inject:['name','age']
如果Provide中提供的数据来自于data,那么应该将Provide作为一个函数来使用,才能使用正确的this
//祖先(提供数据)
provide(){
return{
data:this.info, //包含data中的数据
name:"linming",
age:18
}
},
data () {
return {
info:"120220"
}
}
//后代(接收数据)
inject:['name','age','data']
方式二:Mitt全局事件总线
vue3从实例中移除了$on,$off和$once方法,所以如果想继续使用全局事件总线,需要安装第三方库。
vue官方推荐了mitt
安装:npm install mitt
具体步骤:
1、引用mitt
// (eventBus.js)
import mitt from 'mitt';
const emitter = mitt();
export default emitter;
2、在相互存在依赖关系的两个组件比如Home.vue、detail.vue中,实现通信
//Home.vue 触发事件
import emitter from './components/eventbus'
<button @click="btnClick">触发</button>
methods:{
btnClick(){
emitter.emit("ming",{name:"linming",age:18})
}
}
//detail.vue 监听事件
import emitter from './eventbus'
export default {
created () {
emitter.on("ming",(info)=>{
console.log("接收到了",info);
})
}
}
父子组件的访问方式
1、父组件访问子组件:$refs
//案例
<async-profile ref="profile"></async-profile>
<button @click="getInfo">获取</button>
methods:{
getInfo(){
console.log(this.$refs.profile.info);
}
}
2、子组件访问父组件:$parent $root
//子组件
methods: {
getParent(){
console.log(this.$parent.currentTab);
}
}
5.4 插槽的使用
1、基本使用
//NavBar.vue
<div class="nav-bar">
<div>我是左边</div>
<slot></slot>
<div>我是右边</div>
</div>
//App.vue
<nav-bar></nav-bar>
2、默认值
//NavBar.vue
<div class="nav-bar">
<div>我是左边</div>
<slot>我是默认值(中间)</slot>
<div>我是右边</div>
</div>
//App.vue
<nav-bar></nav-bar> //如果中间有插入新值,新值将替换默认值
假如使用nav-bar时,中间插入多个值,那么这多个值都会被替换到插槽中
//App.vue
<nav-bar>
<div>ahha</div>
<span>hieehie</span>
</nav-bar>
3、具名插槽
不指定具名插槽的话,当存在多个插槽时,会造成混乱
例如
//定义插槽
<div class="nav-bar">
<slot>我是左边</slot>
<slot>我是中间</slot>
<slot>我是右边</slot>
</div>
//使用
<nav-bar>
<div>哈哈哈</div>
<span>嘿嘿嘿</span>
</nav-bar>
//问题:div和span两者会同时存在于3个插槽中
//定义插槽
<div class="nav-bar">
<slot name="left">我是左边</slot>
<slot name="center">我是中间</slot>
<slot name="right">我是右边</slot>
</div>
//使用
<nav-bar>
<template v-slot:letf>
<div>哈哈哈</div>
</template>
<template v-slot:center>
<div>嘿嘿嘿</div>
</template>
<template v-slot:right>
<span>呵呵呵</span>
</template>
</nav-bar>
4、v-slot的缩写
v-slot :替换为字符#
<nav-bar>
<template #letf>
<div>哈哈哈</div>
</template>
<template #center>
<div>嘿嘿嘿</div>
</template>
<template #right>
<span>呵呵呵</span>
</template>
</nav-bar>
5、渲染作用域
1、父级模板里的所有内容都是在父级作用域中编译的
2、子模板里所有的内容都是在子作用域中编译的
//NavBar.vue
<div class="nav-bar">
<slot>我是左边</slot>
</div>
//App.vue
<nav-bar>
<div>{{info}}</div>
</nav-bar>
//注意:这里的info只会在App.vue中查找data,虽然div最终是替换进slot里,但不会访问NavBar里边的内容
但是,有时候我们也想要插槽能够访问到子组件中的内容,所以有了作用域插槽
6、作用域插槽
//App.vue
data () {
return {
//在父组件中定义数据,并传给子组件
arr:['孙为空','唐僧','沙和尚','猪八戒']
}
}
<slot-area :arr="arr">
<template v-slot="slotProps">
<span>{{slotProps.item}}-{{slotProps.index}}</span>
</template>
</slot-area>
//SlotArea.vue
<template v-for="(item,index) in arr" >
<slot :item="item" :index="index"></slot>
</template>
props:{
arr:{
type:Array,
default(){
return []
}
}
}
5.5 动态组件的实现
假设我们需要实现一个功能:点击一个tab-bar,切换不同的组件显示
两种思路实现:
1、通过v-if来判断,显示不同的组件
<div>
<button v-for="item in btn" :key="item" :class="{active:currentTab == item}" @click="btnclick(item)">
{{item}}
</button>
<!-- 显示 -->
<category v-if="currentTab==='分类'"></category>
<share v-else-if="currentTab==='分享'"></share>
<about v-else></about>
</div>
<script>
import Category from './components/Category.vue'
import Share from './components/Share.vue';
import About from './components/About.vue'
export default {
name: 'App',
components: {
Category,
Share,
About
},
data () {
return {
btn:['关于','分类','分享'],
currentTab:'关于'
}
},
methods:{
btnclick(item){
this.currentTab = item
}
}
}
</script>
2、动态组件的方式
动态键使用的是component 组件,通过一个特殊的attribute——is来实现
<component :is="组件"></comonent>
is后绑定的‘组件’:应是通过component函数注册过的组件,或者在组件对象的components对象中注册的组件
<component :is="currentTab"></component>
export default {
name: 'App',
components: {
Category,
Share,
About
},
data () {
return {
btn:['About','Category','Share'],
currentTab:'About'
}
},
methods:{
btnclick(item){
this.currentTab = item
}
}
}
</script>
同时,也可以通过component标签向对应的组件传值。
keep-alive
在开发中,我们希望切换组件时,可以继续保存组件的状态,而不是销毁掉,这个时候就可以使用内置组件keep-alive
keep-alive的一些属性:
1、include——string | RegExp | Array
只有名称匹配的组件会被缓存
2、exclude——string | RegExp | Array
任何名称匹配的组件都不会被缓存
3、max——number | string
最多可以缓存多少个组件实例
<keep-alive include="share">
//这里的share是根据组件的name属性来确定的
<component :is="currentTab"></component>
</keep-alive>
对于缓存的组件来说,再次进入时,是不会执行created或者mounted等生命周期函数的:
所以为了监听到何时重新进入到组件,何时离开了组件,我们可以使用activated、deactivated
1、activated:组件处于活跃状态时触发
2、deactivated:组件不活跃时触发
5.6 vue中实现异步组件
webpack的代码分包
默认情况下,在构建整个组件树的过程中,因为组件和组件之间通过模块化直接依赖的,那么webpack在打包时就会将组件模块打包到一起(比如打包到一个app.j文件中)
但是随着项目的不断庞大,app.js文件内容过大,会造成首屏的渲染速度变慢
这个时候,对于一些不需要立即用到的组件,可以单独对其进行拆分,拆分成一些小的代码块chunk.js。这些chunk.js会在需要时,从服务器加载下来,并且运行代码。
例子
//add.js(将该文件单独打包)
export function add(a,b) {
return a + b;
}
//main.js(将add.js加入组件树)
import("./add.js").then(res=>{
console.log(res.add(20,30));
})
实现异步组件
如果项目过大,某些组件我们希望通过异步的方式进行加载(目的是可以对其进行分包处理)
vue提供了一个函数:defineAsyncComponent
defineAsyncComponent接收两种类型的参数
//类型一:工厂函数
<template>
<div>
<async-profile></async-profile>
</div>
</template>
<script>
import {defineAsyncComponent} from 'vue';
const AsyncProfile = defineAsyncComponent(()=> import('./components/Profile.vue'));
export default {
components: {
AsyncProfile //注册
}
}
</script>
//类型二:接收一个对象类型,对异步函数进行配置
import {defineAsyncComponent} from 'vue';
const AsyncProfile = defineAsyncComponent({
loader: () => import('./components/Profile.vue'),
//其他参数
})
5.7 组件的生命周期
每个组件都可能会经历:创建、挂载、更新、写在等一系列的过程。在这个过程中的某个阶段,用于可能会想要添加一些属于自己的代码逻辑。
生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调。

