目录
- 一、Vue3基础语法
- 1.1. Vue3新特性
- 1.2. Vue3带来的变化(性能)
- 1.3 CDN
- 1.4. Vue初体验
- 1.5. 声明式和命令式
- 1.6. MVVM模型
- 1.7. template属性
- 1.8. data属性
- 1.9. methods属性
- 1.10. vue3的源码
- 1.11. VSCode设置代码片段
- 1.12. Mustache双大括号语法
- 1.13. 基本指令
- 1.14. v-bind绑定属性
- 1.15. v-on绑定事件
- 1.16. 条件渲染(v-if)
- 1.17. template元素
- 1.18. v-show和v-if的区别
- 1.19. 列表渲染(v-for)
- 1.20. 虚拟节点(VNode)
- 1.21. 虚拟DOM(Virtual Dom)
- 1.22. 数组更新检测
- 1.23. 计算属性computed
- 1.24. 侦听器watch
- 1.25. v-model的基本使用
- 1.26. 实例方法
- 二、前端模块化
- 三、Webpack
- 三、Vue3组件化开发
- 三、Vue Cli详解
- 四、Vue3核心语法
- 五、vue-router路由
- 六、Vuex状态管理
- 七、TypeScript
- 八、自动化部署
一、Vue3基础语法
1.1. Vue3新特性
更好的性能、更小的包体积、更好的TypeScript集成、更优秀的API设计
1.2. Vue3带来的变化(性能)
- 使用Proxy进行数据劫持
Vue2是使用Object.defineProperty来劫持数据的getter和setter方法的;这种方式存在一个缺陷就是当给对象添加或者删除属性时,是无法劫持和监听的;
Vue3是使用Proxy进行数据劫持。 - 删除了一些不必要的API:
移除了实例上的$on, $off 和 $once;
移除了一些特性:如filter、内联模板等; - 包括编译方面的优化:
生成Block Tree、Slot编译优化、diff算法优化;
1.3 CDN
CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network),它是指通过相互连接的网络系统,利用最靠近每个用户的服务器;更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;来提供高性能、可扩展性及低成本的网络内容传递给用户。
开源的CDN服务器:国际上使用比较多的是unpkg、 JSDelivr、cdnjs。
1.4. Vue初体验
<div id="app"></div>
<script src="https://unpkg.com/vue@next"></script>
<script>
//第一种写法
/* const why = {
template: "<h2>嗨</h2>"
}
const app = Vue.createApp(why);
app.mount("#app") */
//第二种简写
Vue.createApp({
template: `<h2>嗨嗨嗨</h2>`
}).mount("#app")
</script>
1.5. 声明式和命令式
- 两种不同的编程范式:
命令式编程和声明式编程;
命令式编程关注的是 “how to do”;声明式编程关注的是 “what to do”,由框架(机器)完成 “how”的过程; - 原生的实现过程中
我们每完成一个操作,都需要通过JavaScript编写一条代码,来给浏览器一个指令,这样的编写代码的过程,我们称之为命令式编程;
在早期的原生JavaScript和jQuery开发的过程中,我们都是通过这种命令式的方式在编写代码的。 - Vue的实现过程中
我们会在createApp传入的对象中声明需要的内容,模板template、数据data、方法methods,这样的编写代码的过程,我们称之为是声明式编程;
目前Vue、React、Angular的编程模式,我们称之为声明式编程。
1.6. MVVM模型
- MVC和MVVM都是一种软件的体系结构
MVC是Model – View –Controller的简称,是在前期被使用非常框架的架构模式,比如iOS、前端;
MVVM是Model-View-ViewModel的简称,是目前非常流行的架构模式。 - 通常情况下,我们也经常称Vue是一个MVVM的框架
Vue官方其实有说明,Vue虽然并没有完全遵守MVVM的模型,但是整个设计是受到它的启发的。
1.7. template属性
template属性表示的是Vue需要帮助我们渲染的模板信息
方式一:使用script标签,并且标记它的类型为 x-template;
<script type="x-template" id="why">
<div>
<h2>{{counter}}</h2>
<button @click='increment'>+1</button>
<button @click='decrement'>-1</button>
</div>
</script>
方式二:通常使用template标签,因为不会被浏览器渲染,并设置id;
<template id="why">
<div>
<h2>{{counter}}</h2>
<button @click='increment'>+1</button>
<button @click='decrement'>-1</button>
</div>
</template>
在createApp的对象中,我们需要传入的template以 # 开头:如果字符串是以 # 开始,那么它将被用作 querySelector,并且使用匹配元素的 innerHTML 作为模板字符串;
1.8. data属性
Vue3的data属性必须传入一个函数(否则就会直接在浏览器中报错),并且该函数需要返回一个对象。
data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理,所以我们在template中通过 {{counter}} 访问counter,可以从对象中获取到数据;所以我们修改counter的值时,template中的 {{counter}}也会发生改变。
1.9. methods属性
methods属性是一个对象,通常我们会在这个对象中定义很多的方法:这些方法可以被绑定到 template 模板中;在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性。
注意:不能使用箭头函数来定义methods函数(例如plus: => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以this将不会按照期望指向组件实例,this.a将是undefined。
前端面试之彻底搞懂this指向
1.10. vue3的源码
下载源代码,第一步安装Vue源码项目相关的依赖,执行yarn install
;第二步对项目执行打包操作,执行yarn dev
1.11. VSCode设置代码片段
左上角Code——首选项——用户片段——选择html
具体的步骤如下:
第一步:复制自己需要生成代码片段的代码;
第二步:https://snippet-generator.app/在该网站中生成代码片段;
第三步:在VSCode中配置代码片段;
1.12. Mustache双大括号语法
data返回的对象是有添加到Vue的响应式系统中,当data中的数据发生改变时,对应的内容也会发生更新,当然,Mustache中不仅仅可以是data中的属性,也可以是一个JavaScript的表达式。
<!-- 基本使用 -->
<h2>{{message}}</h2>
<!-- js表达式 -->
<h2>{{counter * 2}}</h2>
<!-- 调用methods中的函数 -->
<h2>{{getReverseMessage()}}</h2>
<!-- 三元运算符 -->
<h2>{{isShow ? "哈哈哈" : "" }}</h2>
<button @click="toggle">切换</button>
1.13. 基本指令
- v-once
用于指定元素或者组件只渲染一次:当数据发生变化时,元素或者组件以及其所有的子元素<.font>将视为静态内容并且跳过;该指令可以用于性能优化。 - v-text
用于更新元素的 textContent<h2 v-text="message"></h2>
等价于<h2>{{message}}</h2>
v-text缺点是不够灵活。 - v-html
解析data中的html内容<h2 v-html="message"></h2>
,message: "<div style='color:red'>哈哈哈</div> "
- v-pre
用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签 - v-cloak
用于保持在元素上直到关联组件实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕。
1.14. v-bind绑定属性
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值
v-bind的语法糖是:
可动态绑定的属性:如图片的链接src、网站的链接href、动态绑定一些类、样式等
①绑定属性img、href
<a :href="link">百度一下</a>
<img :src="imgSrc" alt="">
②绑定class有两种方式:对象语法;数组语法;
<!-- -----对象语法----- -->
<!-- {'active': boolean} 引号不写也可以 -->
<div :class="{'active': isActive}">哈哈哈</div>
<button @click="toggle">切换</button>
<!-- 可以有多个键值对(多个动态class) -->
<div :class="{'active': isActive, 'titles': true}">哈哈哈</div>
<!-- 默认的class和动态的class结合 -->
<div class="haha hehe" :class="{'active': isActive, 'titles': true}">哈哈哈</div>
<!-- 绑定对象 -->
<div class="haha hehe" :class="classObj">哈哈哈</div>
<!-- 从methods(computed)中获取 -->
<div class="haha hehe" :class="getClassObj()">哈哈哈</div>
<script>
data() {
return {
isActive: true,
classObj: {
active: true,
titles: true,
ceshi: true
}
}
},
methods: {
toggle() {
this.isActive = !this.isActive
},
getClassObj() {
return {
active: true,
titles: true,
ceshi: true,
lala: true
}
}
}
</script>
<!-- -----数组语法----- -->
<!-- 直接传入一个数组 -->
<div :class="['active', titles]">哈哈哈</div>
<!-- 数组中使用三元运算符 -->
<div :class="['active', titles, isActive? 'active2': '']">哈哈哈</div>
<!-- 数组中使用对象语法 -->
<div :class="['active', titles, {active3 :isActive}]">哈哈哈</div>
<script>
data() {
return {
titles: 'cba',
isActive: true
}
}
</script>
③绑定style有两种方式:对象语法;数组语法;
某些样式我们需要根据数据动态来决定,比如某段文字的颜色,大小等等;
CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来)来命名;
注意:style中的class名用-拼接的属性key需要加引号;value值必须加引号。
<!-- -----对象语法----- -->
<!-- 基本使用:传入一个对象,对象内容基本都是确定的 -->
<div :style="{color: textColor, fontSize: '30px', 'background-color': 'blue'}">哈哈哈</div>
<!-- 基本使用:传入一个对象,值来自于data -->
<div :style="{color: 'red', fontSize: size+'px', 'background-color': 'blue'}">哈哈哈</div>
<!-- 对象数据:直接在data中定义好对象在这里使用 -->
<div :style="styleObj">哈哈哈</div>
<!-- 调用一个方法 -->
<div :style="getStyleObj()">呵呵呵</div>
<script>
data() {
return {
size: '18px',
textColor: 'green',
styleObj: {
fontSize: '50px',
fontWeight: 700,
backgroundColor: 'red'
}
}
},
methods: {
getStyleObj() {
return {
fontSize: '60px',
fontWeight: 700,
backgroundColor: 'red'
}
}
}
</script>
:style的数组语法可以将多个样式对象应用到同一个元素上:
<!-- -----数组语法----- -->
<div :style="[style1Obj, style2Obj]">哈哈哈</div>
<script>
data() {
return {
style1Obj: {
color: 'red',
fontSize: '30px'
},
style2Obj: {
textDecoration: "underline"
}
}
}
</script>
④动态绑定属性:如果属性名称不是固定的,我们可以使用:[属性名]=“值”的格式来定义,这种绑定的方式,我们称之为动态绑定属性。
<h2 :[name]="value">{{message}}</h2>
<script>
data() {
return {
message: '加油',
name: 'why',
value: 'kobe'
}
}
</script>
<!-- 最终渲染成 <h2 why="kobe">加油</h2> -->
⑤绑定一个对象:将一个对象的所有属性,绑定到元素上显示所有属性。
案例:info对象会被拆解成div的各个属性
<h2 v-bind="info">{{message}}</h2>
<script>
data() {
return {
message: '呵呵',
info: {
name: 'why',
age: 18,
height: 188
}
}
}
</script>
<!-- 最终渲染成 <h2 name="why" age="18" height="188">呵呵</h2> -->
1.15. v-on绑定事件
我们需要经常和用户进行各种各样的交互,监听用户发生的事件:比如点击、拖拽、键盘事件等等;
v-on指令,语法糖(缩写)是@
,v-on:click可以写成@click
①基本使用
<!-- ---1.基本使用--- -->
<!-- 绑定一个表达式 -->
<h2 v-on:click="counter++">{{counter}}</h2>
<!-- 绑定到一个methods方法中 -->
<h2 @click="btnClick">{{counter}}</h2>
<!-- 绑定鼠标移动事件 -->
<h2 @mousemove="mouseMove">{{counter}}</h2>
<!-- ---2.绑定对象(绑定多个事件)--- -->
<h2 v-on="{click: btnClick, mousemove:mouseMove}">特殊按钮</h2>
<script>
data() {
return {
counter: 100
}
},
methods: {
btnClick() {
console.log("btnClick")
},
mouseMove() {
console.log("mouseMove")
}
}
</script>
②参数传递
情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去;
情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
<!-- 当按钮被点击时,浏览器会生成一个event对象 -->
<!-- 默认传入event对象, 可以在方法中获取 -->
<button @click="btn1Click">按钮1</button>
<!-- $event可以获取到事件发生时的事件对象 -->
<button @click="btn2Click($event, 'coderwhy', 18, 188)">按钮2</button>
<script>
methods: {
btn1Click(event) {
console.log(event);
},
btn2Click(event, name, age, height) {
console.log(event, name, age, height);
}
}
</script>
③v-on的修饰符
修饰符相当于对事件进行了一些特殊的处理:
.stop - 调用 event.stopPropagation();停止冒泡。
.prevent - 调用 event.preventDefault();阻止默认事件。
.capture - 添加事件侦听器时使用 capture 模式。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyAlias} - 仅当事件是从特定键触发时才触发回调。
.once - 只触发一次回调。
.left - 只当点击鼠标左键时触发。
.right - 只当点击鼠标右键时触发。
.middle - 只当点击鼠标中键时触发。
.passive - { passive: true } 模式添加侦听器。
<!-- 点击按钮两个都有事件,加上.stop阻止冒泡,就只有btnClick事件,不会把事件向外冒泡-->
<div @click="divClick">
<button @click.stop="btnClick">按钮</button>
</div>
<!-- 监听键的点击。默认情况下,键盘抬起的时候输入每一个字都会执行,加上.enter输完所有的敲回车才会执行 -->
<input type="text" @keyup.enter="enterKeyup">
<script>
methods: {
divClick() {
console.log("divClick")
},
btnClick() {
console.log("btnClick")
},
enterKeyup() {
console.log("enterKeyup", event.target.value)//拿到输入的内容
}
}
</script>
1.16. 条件渲染(v-if)
在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,这个时候我们就需要进行条件判断了。这些内容只有在条件为true时,才会被渲染出来。
Vue提供了下面的指令来进行条件判断: v-if、v-else、v-else-if、v-show
<input type="text" v-model="score">
<h2 v-if="score > 90">优秀</h2>
<h2 v-else-if="score > 60">良好</h2>
<h2 v-else>不及格</h2>
<script>
data() {
return {
score: 92
}
}
</script>
1.17. template元素
因为v-if是一个指令,所以必须将其添加到一个元素上,但是我们并不希望div这种元素被渲染,这个时候,我们可以选择使用template;
template元素可以当做不可见的包裹元素,并且在v-if上使用,但是最终template不会被渲染出来;有点类似于小程序中的block。
1.18. v-show和v-if的区别
用法区别:v-show不支持template;v-show不可以和v-else一起使用。
本质区别:v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有渲染的,只是通过CSS的display属性来进行切换;v-if当条件为false时,其对应的原生不会被渲染到DOM中。
如何进行选择呢?当需要在显示与隐藏之间切片很频繁时,使用v-show;如果不会频繁的切换,使用v-if。
1.19. 列表渲染(v-for)
①v-for基本使用(遍历数组)
v-for的基本格式是“item in 数组”或者“item of 数组”:
数组通常是来自data或者prop,也可以是其他方式;
item是我们给每项元素起的一个别名,这个别名可以自己来定义;
在遍历一个数组的时候会经常需要拿到数组的索引:
如果我们需要索引,可以使用格式: “(item, index) in 数组”;
注意上面的顺序:数组元素项item是在前面的,索引项index是在后面的;
<!--没有索引值-->
<li v-for ="item in movies">{{item}}</li>
<!--获取索引值-->
<li v-for ="(item, index) in movies">{{index+1}}---{{item}}</li>
②v-for遍历对象
v-for也支持遍历对象,并且支持有一二三个参数:
一个参数: “value in object”;
二个参数: “(value, key) in object”;
三个参数: “(value, key, index) in object”;
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
<script>
data() {
return {
movies: [
"星际穿越",
"盗梦空间",
"大话西游",
"教父",
"少年派"
],
info: {
name: "why",
age: 18,
height: 1.88
}
}
}
</script>
③v-for遍历数字:每一个item都是一个数字
<li v-for="(num, index) in 10">{{num}}-{{index}}</li>
④v-for中的key是什么作用
官方解释:key的作用主要是为了高效的更新虚拟DOM。key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes;如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素。
1.20. 虚拟节点(VNode)
VNode的全称是Virtual Node
HTML元素创建出来的VNode(虚拟节点);
无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode;
VNode的本质是一个JavaScript的对象;
1.21. 虚拟DOM(Virtual Dom)
如果我们不是只有一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode Tree(多个VNode形成的树结构)
为什么不直接渲染成真实DOM?为了做多平台的适配(跨平台)
1.22. 数组更新检测
直接修改原来的数组的方法(以下是响应式的):
以下三个方法不会替换原来的数组,会生成新的数组的方法(以下不是响应式的):filter()、 concat() 和 slice()
1.23. 计算属性computed
复杂data的处理方式,在某些情况,可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示,有以下三种方式:
①在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算;在模板中放入太多的逻辑会让模板过重和难以维护,并且如果多个地方都使用到,那么会有大量重复的代码;多次使用的时候,很多运算也需要多次执行,没有缓存;
②将逻辑抽取到一个method中,这种做法有一个弊端,就是所有的data使用过程都会变成了一个方法的调用;在多次使用方法的时候,没有缓存,也需要多次计算;
③使用计算属性computed。计算属性看起来像是一个函数,但是我们在使用的时候不需要加(),并且计算属性是有缓存的。
对于任何包含响应式数据的复杂逻辑,都应该使用计算属性;计算属性将被混入到组件实例中。所有getter和setter的this上下文自动地绑定为组件实例。
-
计算属性的缓存
计算属性是有缓存的,当多次使用计算属性时,计算属性中的运算只会执行一次,而methods当多次使用的时候,没有缓存,需要多次计算;
因为计算属性会基于它们的依赖关系进行缓存,在数据不发生变化时,计算属性是不需要重新计算的,如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算。 -
计算属性的setter和getter
计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数
<div id="app"></div>
<template id="my-app">
<h2>{{fullName}}</h2>
</template>
<script>
Vue.createApp({
template: '#my-app',
data() {
return {
firstName: 'xixi',
lastName: 'hehe'
}
},
computed: {
//原始写法:计算属性一般是没有set方法,只读属性
/* fullName: {
//set: function () {},
get: function () {
return this.firstName + ' ' + this.lastName
}
} */
//最终简便写法:省略get方法 ES5写法
/* fullName: function () {
return this.firstName + ' ' + this.lastName
} */
//最终简便写法:省略get方法 ES6增强写法
fullName() {
return this.firstName + ' ' + this.lastName
}
}
}).mount("#app")
</script>
1.24. 侦听器watch
-
什么是侦听器?
开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中;当数据变化时,template会自动进行更新来显示最新的数据;但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了。(一般用在想侦听data数据发生变化时需要进行一些逻辑处理的时候使用) -
使用场景
比如现在我们希望用户在input中输入一个问题;每当用户输入了最新的内容,我们就获取到最新的内容,并且使用该问题去服务器查询答案;那么,我们就需要实时的去获取最新的数据变化。
-
侦听器watch的配置选项
原info: {name: 'aa', age: 18}
,只能侦听info引用的变化(把原Info改成新的指针对象this.info={name: 'bb'}
),对于内部属性的变化是不会做出响应的(this.info.name='cc'
内部属性发生的改变是不能侦听的)。
实现侦听内部属性的方法有以下3个:
①配置选项 深度侦听
watch: {
//深度侦听/立即执行
info: {
handler: function (newInfo, oldInfo) {
console.log("新值:", newInfo, "旧值:", oldInfo)
},
deep: true,//深度侦听
immediate: true //立即执行
}
}
②Vue2文档中有提到的是侦听对象的属性
watch: {
"info.name": function (newName, oldName) {
console.log("新name:", newName, "旧name:", oldName)
}
}
③$watch实例方法
created() {
this.$watch('info', (newInfo, oldInfo) => {
console.log("新值:", newInfo, "旧值:", oldInfo)
}, { deep: true, immediate: true })
}
1.25. v-model的基本使用
表单提交是开发中非常常见的功能,也是和用户交互的重要手段:
比如用户在登录、注册时需要提交账号密码;
比如用户在检索、创建、更新信息时,需要提交一些数据;
v-model指令可以在表单 input、textarea以及select元素上创建双向数据绑定;它会根据控件类型自动选取正确的方法来更新元素。
- v-model的原理
v-bind绑定value属性的值;
v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;
<input v-model="searchText">
<!--等价于-->
<input :value="searchText" @input="searchText=$event.target.value">
- v-model绑定textarea、checkbox、radio、select
checkbox:单个勾选框v-model即为布尔值;多个复选框,因为可以选中多个,所以对应的data中属性是一个数组。还需要传一个value值,不写value无法区分选中了哪个。
select:也可以多选。
<div id="app"></div>
<template id="my-app">
<!-- 1.绑定textarea -->
<label for="intro">
自我介绍
<textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea>
</label>
<h2>intro: {{intro}}</h2>
<!-- 2.checkbox -->
<!-- 2.1.单选框 -->
<label for="agree">
<input id="agree" type="checkbox" v-model="isAgree"> 同意协议
</label>
<h2>isAgree: {{isAgree}}</h2>
<!-- 2.2.多选框 -->
<span>你的爱好: </span>
<label for="basketball">
<input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
</label>
<label for="football">
<input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
</label>
<label for="tennis">
<input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
</label>
<h2>hobbies: {{hobbies}}</h2>
<!-- 3.radio -->
<span>你的性别: </span>
<label for="male">
<input id="male" type="radio" v-model="gender" value="male">男
</label>
<label for="female">
<input id="female" type="radio" v-model="gender" value="female">女
</label>
<h2>gender: {{gender}}</h2>
<!-- 4.select multiple是多选的意思-->
<span>喜欢的水果: </span>
<select v-model="fruit" multiple size="2">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>fruit: {{fruit}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
intro: "Hello World",
isAgree: false,
hobbies: ["basketball"],
gender: "",
fruit: "orange"
}
}
}
Vue.createApp(App).mount('#app');
</script>
- v-model的值绑定
在真实开发中,我们的数据可能是来自服务器的,那么我们就可以先将值请求下来,绑定到data返回的对象中, 再通过v-bind来进行值的绑定,这个过程就是值绑定。 - 修饰符
①lazy:v-model.lazy="message"
默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步;如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为change事件,只有在提交时(比如回车)才会触发;
②number:v-model.number="score"
v-model绑定后的值是string类型,换为数字类型可以使用.number修饰符(在进行逻辑判断时,如果是一个string类型,会进行隐式转换)
③trim:v-model.trim="msg"
如果要自动过滤用户输入的首尾空白字符,可以给v-model添加 trim修饰符
1.26. 实例方法
二、前端模块化
2.1. CommonJS导入导出
①通过CommonJS导出
var flag = true
module.exports = {
flag
}
②通过CommonJS导入
let { flag } = require('./main.js')
2.2. ES6导入导出
2.2.1. export的基本使用(导出变量)
①方式一
let name = 'tu'
let age = 18
let height: 160
export {
name, age ,height
}
②方式二
export let name = 'tu'
export let age = 18
export let height = 160
2.2.2. ES6的export导出函数或类
①导出函数
//方式一
export function test(){
}
//方式二
function test(){
}
export { test }
②导出类
//方式一
export class Preson(){
}
//方式二
class Preson(){
}
export { Preson }
③export default
导入者可以自己来命名
export default address //default在同一个模块中,不允许同时存在多个
export default function () {
}
import addr from './info.js'
2.2.3. ES6的import导入
①需要在HTML代码中引入js文件,并且类型需要设置为module
<script src="info.js" type="module"></script>
<script src="main.js" type="module"></script>
②import指令用于导入模块中的内容,比如main.js的代码
import { name, age, height } from './info.js'
③模块中所有的信息都导入
import * as info from './info.js'
④只有在export default时才可以以这种方式(相当于自己起名字)导入
import HelloVuex from './components/HelloVuex'
三、Webpack
记录:
①新下载的项目要npm install
安装package.json里的所有依赖;
②main.js为项目的入口文件;
③第一次打包时,打包后的html文件中的路径默认是/开头,需要在vue.config.js中设置publicPath: './'
(CLI提供的属性)(vue-cli3.x已经没有了webpack.config.js文件。取而代之的是创建一个vue.config.js文件)
④webpack是在node环境运行的,要通过module.exports
导出;
⑤–save-dev
是开发时依赖,可以简写为-D,项目打包后不需要继续使用的;
⑥跨域问题,部署的时候和后端配合,使用nginx解决跨域,还可以使用proxy代理;
⑦如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念,只需要进行简单的合并、压缩,就使用grunt/gulp即可。grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
3.1. 认识webpack
- webpack is a static module bundler for modern JavaScript applications(webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序)
打包bundler:webpack可以将帮助我们进行打包,将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。(并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。)
静态的static:可以将代码打包成最终的静态资源(部署到静态服务器);
模块化module:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等;
现代的modern:正是因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展。(并且会帮助我们处理模块间的依赖关系。) - 脚手架都是依赖于webpack的
- webpack可以做什么
①进行模块化开发,并且会帮助我们处理模块间的依赖关系;
②使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码;
③开发过程中,实时的监听文件的变化来并且反映到浏览器上,提高开发的效率;
④比如开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化。
3.2. Webpack的使用前提和安装
Webpack的运行是依赖Node环境的,需要先安装Node.js,并且同时会安装npm。查看node版本node -v
- webpack和webpack-cli的关系
执行webpack命令,会执行node_modules下的.bin目录下的webpack;
webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;
而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程; - 安装命令
全局安装npm install webpack webpack-cli –g
局部安装npm install webpack webpack-cli –D
2.3. Webpack的默认打包(全局)
- 在目录下直接执行
webpack
命令进行打包,之后运行打包之后的代码; - 生成一个dist文件夹,里面存放一个main.js的文件,就是我们打包之后的文件:
这个文件中的代码被压缩和丑化了;另外我们发现代码中依然存在ES6的语法,比如箭头函数、const等,这是因为默认情况下webpack并不清楚我们打包后的文件是否需要转成ES5之前的语法,后续我们需要通过babel来进行转换和设置。 - 通过配置来指定入口和出口
当我们运行webpack时,webpack会查找当前目录下的src/index.js作为入口,所以,如果当前项目中没有存在src/index.js文件,那么会报错;我们也可以通过配置来指定入口和出口npx webpack --entry ./src/main.js --output-path ./build
2.4. 创建局部的webpack
任何时候开发项目都有自己局部的webpack
- 第一步:创建package.json文件,用于管理项目的信息、库依赖等;
npm init
- 第二步:安装局部的webpack;
npm install webpack webpack-cli –D
- 第三步:使用局部的webpack;
npx webpack
- 第四步:在package.json中创建scripts脚本,执行脚本打包即可;
配置完最后再执行npm run build
打包即可
"script": {
"build": "webpack"
}
2.5. 配置文件(配置入口和出口)
在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件:
//配置入口和出口
const path = require('path') //依赖包
module.exports = {
//入口:可以是字符串/数组/对象
entry: './scr/main.js',
//出口:通常是一个对象,里面至少包含path和filename两个重要的属性
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist') //需要动态获取路径;__dirname获取当前文件所在路径
}
}
2.6. 指定配置文件
比如我们将webpack.config.js修改成了wk.config.js,这个时候我们可以通过 --config 来指定对应的配置文件,可以在package.json中增加一个新的脚本:
"script": {
"build": "webpack --config wk.config.js"
}
之后执行npm run build
来打包即可
2.7. 打包css和资源
css-loader、style-loader、less-loader、postcss-loader、file-loader、url-loader、babel-loader、vue-loader
-
什么是loader?
loader可以用于对模块的源代码进行转换;
①css-loader
:将css文件也看成是一个模块,通过import来加载这个模块;在加载这个模块时,webpack其实并不知道如何对其进行加载,需要一个可以读取css文件的loader来完成这个功能;
②style-loader
:css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中,如果我们希望再完成插入style的操作,还需要style-loader;
③less-loader
:如何让我们的环境支持这些预处理器?less、sass等编写的css需要通过less工具转换成普通的css;安装lessnpm install less -D
,执行npx lessc ./src/css/title.less title.css
编译转换;再使用less-loader自动转换,来自动使用less工具转换less到css;
④postcss-loader
:借助于构建工具来对css进行处理npm install postcss-loader -D
;注意:因为postcss需要有对应的插件才会起效果,所以我们需要配置它的plugin;也可以将这些配置信息放到一个单独的文件中进行管理,在根目录下创建postcss.config.js;
⑤file-loader
:作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中;
⑥url-loader
:
⑦babel-loader
:
⑧vue-loader
: -
loader配置方式
安装css-loader:npm install css-loader -D
安装style-loader:npm install style-loader -D
安装less-loader:npm install less-loader -D
安装file-loader:npm install file-loader -D
-
PostCSS工具
PostCSS是一个通过JavaScript来转换样式的工具,这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置,实现这些功能,我们需要借助于PostCSS对应的插件,安装:npm install postcss postcss-cli -D
;
插件①autoprefixer:添加前缀的插件,安装npm install autoprefixer -D
,直接使用使用postcss工具,并且制定使用autoprefixernpx postcss --use autoprefixer -o end.css ./src/css/style.css
插件②postcss-preset-env:事实上,在配置postcss-loader时,我们配置插件并不需要使用autoprefixer,postcss-preset-env也是一个postcss的插件,它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境 添加所需的polyfill,也包括会自动帮助我们添加autoprefixer,安装npm install postcss-preset-env -D
,之后,直接修改掉之前的autoprefixer即可;
/*postcss.config.js*/
module.exports = {
plugins: [
//在使用某些postcss插件时,也可以直接传入字符串
require("postcss-preset-env")
]
}
/*webpack.config.js*/
const path = require('path');
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "./build"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.css$/, //正则表达式
// 1.loader的写法(语法糖)
// loader: "css-loader"
// 2.完整的写法
use: [
// {loader: "css-loader"}
//因为loader的执行顺序是从后到前(或者说从下到上),所以我们需要将style-loader写到css-loader的前面
"style-loader",
"css-loader",
"postcss-loader"
/*{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require("autoprefixer")
]
}
}
}*/
]
},
{
test: /\.less$/,
//test: /\.(less|css)$/,
use: [
"style-loader",
"css-loader",
"less-loader"
]
}
]
}
}
2.8. Plugin
1、CleanWebpackPlugin
安装npm install clean-webpack-plugin -D
2、HtmlWebpackPlugin
安装npm install html-webpack-plugin -D
3、DefinePlugin
4、CopyWebpackPlugin
安装npm install copy-webpack-plugin -D
2.9. 模块热替换(HMR)
2.10.代码分包
三、Vue3组件化开发
记录:
①scope会添加data-v-fxxx的属性,子组件最好加上根元素div;
②导入组件可以不跟后缀名,因为当前创建的项目是基于vuecli,vuecli是基于webpack,webpack里面有一个extensions会配置一些后缀名。(尽量加上后缀名,使用组件时会有提示,还可以ctrl点进去跟踪源组件)
③响应式的API:computed函数,computed是vue3的新特性;
④$refs
的使用:在某个子组件上使用ref属性设置标识,然后通过this.$refs.scroll.message访问到该组件。
3.1. 认识组件化开发
3.2. 注册全局组件
3.3. 注册局部组件
3.4. 组件的通信
3.4.1. 父子组件的通信方式props/$emit
1、父传子:通过props
Props是你可以在组件上注册一些自定义的attribute(属性);父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值。
- props的值有两种方式:
①字符串数组。数组中的字符串就是attribute的名称(只能传入的attribute的名称,并不能对其进行任何形式的限制);
②对象类型。对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、 默认值等等;type的类型都可以是String、Number、Boolean、Array、Object、Date、Function、Symbol;
<!--父组件-->
<template>
<!--两种方式:title是直接传递数据,banner是动态绑定数据-->
<HelloWorld title="is title msg" :banner="bannerMsg" />
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld,
},
data() {
return {
bannerMsg: "我是banner",
};
},
};
</script>
<style></style>
<!--子组件-->
<!-----Props的数组用法----->
<template>
<div>
<h1>{{ title }}</h1>
<h1>{{ banner }}</h1>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: ["title", "banner"],
};
</script>
<style scoped></style>
<!-----Props的对象用法----->
<template>
<div>
<h1>{{ title }}</h1>
<h1>{{ banner }}</h1>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
//基础的类型检查
title: String,
//带有默认值的对象
banner: {
type: String,
required: true,
defalut() {
return {};
},
},
},
};
</script>
<style scoped></style>
- 注意:
①attribute名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符,camelCase (驼峰命名法) 的prop名需要使用其等价的 kebab-case (短横线分隔命名) 命名
②子组件中的模板中必须要在最外层写上根元素div - 非Prop的Attribute
当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为非Prop的 Attribute;常见的包括class、style、id属性等。 - Attribute继承
当子组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中:
<!--父组件:给子组件传了个class属性-->
<HelloWorld title="is title" :banner="bannerMsg" class="bannerWrap" />
<!--子组件渲染出来的DOM:
因为子组件中有根元素div,所以直接给div加了个class属性-->
<div id="app" data-v-app="">
<div class="bannerWrap">
<h1>is title</h1>
<h1>我是banner</h1>
</div>
</div>
- 禁用 Attributes 继承
如果我们不希望组件的根元素继承attribute,可以在子组件中export default中设置inheritAttrs: false
;
禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素,可以通过$attrs
来访问所有的非props的attribute;
<!--子组件:需要给banner的h1添加class-->
<template>
<div>
<h1>{{ title }}</h1>
<h1 :class="$attrs.class">{{ banner }}</h1>
</div>
</template>
<!--子组件渲染出来的DOM-->
<div id="app" data-v-app="">
<div>
<h1>is title</h1>
<h1 class="bannerWrap">我是banner</h1>
</div>
</div>
- 多根节点的 Attributes 继承
<!--父组件:有多个属性class、id需要传给子组件-->
<HelloWorld title="is title" :banner="bannerMsg" class="bannerWrap" id="bannerWrap"/>
<!--子组件写法,将所有的值都绑定过来-->
<template>
<div>
<h1>{{ title }}</h1>
<h1 v-bind="$attrs">{{ banner }}</h1>
</div>
</template>
<!--子组件渲染出来的DOM-->
<div id="app" data-v-app="">
<div>
<h1>is title</h1>
<h1 class="bannerWrap" id="bannerWrap">我是banner</h1>
</div>
</div>
2、子传父:通过$emit
触发事件
- 什么情况下子组件需要传递内容到父组件呢?
当子组件有一些内容想要传递给父组件的时候;
当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容; - 操作步骤:
首先,在子组件中定义好在某些情况下触发的事件名称;
其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中;
最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件。
<!--父组件-->
<template>
<h1>当前数字:{{ counter }}</h1>
<HelloWorld @add="addNumber" @sub="subNumber" @addN="addNnum" />
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld,
},
data() {
return {
counter: 0,
};
},
methods: {
addNumber() {
this.counter++;
},
subNumber() {
this.counter--;
},
addNnum(num, name, age) {
this.counter += num;
console.log(name,age);
},
},
};
</script>
<style></style>
<!--子组件:内部其实是监听两个按钮的点击,点击之后通过this.$emit的方式发出去事件-->
<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 {
name: "HelloWorld",
//不写这个好像也可以
emits: ["add", "sub"],
//可以写成对象的形式;对象写法的目的是为了对传递的参数进行验证
/*emits: {
add: null,
sub: null,
addN: (num, name, age) => {
console.log(num, name, age);
if (num > 10) {
return true;
}
return false;
},
},*/
data() {
return {
num: 0,
};
},
methods: {
increment() {
console.log("+1");
this.$emit("add");
},
decrement() {
console.log("-1");
this.$emit("sub");
},
,
incrementN() {
console.log("+n");
//可以传递多个参数
this.$emit("addN", this.num, "why", 18);
}
},
};
</script>
<style scoped></style>
3.4.2. 非父子组件的通信
1、Provide依赖/Inject注入(用于非父子组件之间共享数据)
父组件有一个provide选项来提供数据;
子组件有一个inject选项来开始使用这些数据;
<!--父组件-->
<template>
<hello-world></hello-world>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
import { computed } from 'vue'
export default {
name: "App",
components: {
HelloWorld,
},
/*provide: {
name: "why",
age: 18,
},*/
//如果Provide中提供的一些数据是来自data,那么我们可能会想要通过this来获取,这个时候需要将provide写成函数的形式并返回
provide() {
return {
name: "why",
age: 18,
//如果修改了names数据,不是响应式的,可通过computed函数
//length: this.names.length,
length: computed(() => this.names.length)
};
},
data() {
return {
names: ["haha", "hehe", "xixi"],
};
},
};
</script>
<style></style>
<!--孙组件-->
<template>
<!--因为computed返回的是一个ref对象,需要取出其中的value来使用-->
<div>HelloContent: {{ name }} {{ age }} {{ length.value }}</div>
</template>
<script>
export default {
name: "HelloContent",
inject: ["name", "age", "length"],
};
</script>
<style scoped></style>
2、全局事件总线eventBus:第三方Mitt库
Vue3从实例中移除了$on
、$off
和$once
方法
①安装npm install mitt
②封装一个工具(创建)eventbus.js
import mitt from 'mitt';
const emitter = mitt();
// export const emitter1 = mitt();
// export const emitter2 = mitt();
export default emitter;
③使用事件总线工具
④Mitt的事件取消
//取消emitter中所有的监听
emitter.all.clear()
//定义一个函数
function onFonn(){}
emitter.on('foo', onFoo) //监听
emitter.off('foo', onFoo) //取消监听
3、Vuex(用的最多)
3.5. 插槽
3.6. 动态组件的使用
3.7. 异步组件的使用
3.8. ref引用元素和组件
3.9. 生命周期
3.10. 组件的v-model
三、Vue Cli详解
四、Vue3核心语法
4.1 setup(){}函数
4.1.1 setup(){}的参数
setup接收两个参数props和context
①props是一个对象,它其实就是父组件传递过来的属性会被放到props对象中,可以通过props参数获取。在template中依然是可以正常去使用props中的属性;如果我们在setup函数中想要使用props,那么不可以通过this去获取;因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可。
②context它里面包含三个属性:
attrs:所有的非prop的attribute;
slots:父组件传递过来的插槽;
emit:当我们组件内部需要发出事件时会用到emit(因为不能访问this,所以不可以通过 this.$emit发出事件);
4.1.2 setup(){}的返回值
①setup的返回值可以在模板template中被使用;也就是说我们可以通过setup的返回值来替代data选项;甚至是我们可以返回一个执行函数来代替在methods中定义的方法;这些操作不是响应式的;setup是同步的;
②setup是处于beforecreate和created生命周期间的函数,所以setup在被调用之前是无法使用data、computed、methods等,所以为了避免错误使用,vue将setup函数中的this转化成undefined;
③setup函数中定义的变量和方法都是需要return出去的,不然没有办法在模板中使用;
4.2 reactive
如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数,因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
4.3 ref
①reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
②ref 会返回一个可变的响应式对象,该对象作为一个响应式的引用维护着它内部的值,这就是ref名称的来源;它内部的值是在ref的value属性中被维护的;
③在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过ref.value
的方式来使用;但是在setup函数内部,它依然是一个ref引用, 所以对其进行操作时,我们依然需要使用ref.value
的方式。
4.4 defineComponent({})
①默认的export default {}
,对于编辑器而言,{}只是一个Object的类型,无法有针对性的提示vue组件 {} 里应该有哪些属性。
② 它并没有实现任何的逻辑,只是把接收的Object直接返回,它的存在是完全让传入的整个对象获得对应的类型,最主要的功能是为了TypeScript下的类型推导。
③只是对setup函数进行封装,返回options的对象。
使用方法:
import { defineComponent } from 'vue'
export default defineComponent({ ... })
五、vue-router路由
六、Vuex状态管理
七、TypeScript
八、自动化部署
全局拦截器是每个实例都有的拦截器,只有对应的实例才有的拦截器,某一个请求的拦截器