前言
技术栈
- 使用vue-cli 创建工程化Vue项目
- 熟练使用vue指令
- 熟练封装和使用axios来请求后端的API接口
- 熟练使用vue-router实现SPA的开发
- 能够使用Vant, Element UI等组件库, 结合业务需求完成开发
前端工程化
- 模块化
- 组件化
- 规范化
- 自动化
什么是 DOM?
文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使程序对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。
一个web页面是一个文档。这个文档可以在浏览器窗口或作为HTML源码显示出来。但上述两个情况中都是同一份文档。文档对象模型(DOM)提供了对同一份文档的另一种表现,存储和操作的方式。 DOM是web页面的完全的面向对象表述,它能够使用如 JavaScript等脚本语言进行修改。
简介
定义: Vue是一套用于构建用户界面的前端框架
遵循 UI = f(state) 的设计哲学
用户界面的构建历史
-
构建用户界面1.0
- 编写结构:基于HTML超文本标记语言
- 美化样式:基于CSS
- 处理交互:基于JS操作网页中的DOM对象,处理用户和页面的交互行为
-
构建用户界面2.0
- 基于JQuery+模板引擎
-
构建用户界面3.0:开发者把精力放在核心业务的实现上,而不是操作DOM上
- 编写结构:基于vue中的指令,方便快捷的渲染页面结构,数据驱动视图(页面依赖的数据源变化则自动渲染)
- 美化样式:基于CSS
- 处理交互:基于vue的事件绑定机制,轻松处理用户和页面的交互行为
前端框架
vue提供构建用户界面的一整套解决方案
- vue核心库
- vue-router路由方案
- vuex状态管理方案
- vue组件库
辅助vue项目开发的一系列工具
- vue-cli(npm全局包,基于webpack,大而全)
- vite(npm全局包,小而巧)
- vue-devtools(浏览器调试工具)
- vetur(vscode插件:提供语法高亮和提示)
Vue的特性
-
数据驱动视图(单向数据绑定)
- 数据的变化会驱动视图自动更新
- 程序员只需要维护好数据,页面结构会被Vue的底层逻辑, 根据数据自动渲染出来
-
双向数据绑定
- js数据的变化会被自动渲染到页面上
- 页面上表单的数据变化时会自动同步到js数据中
- 作用:填写表单时,在不操作DOM的前提下,自动把用户填写的内容同步到数据源
-
vue组件的特点:
- 先编译整个页面结构(执行js),再决定要渲染哪一个标签
- 所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可。
-
MVVM
- View:当前页面所渲染的DOM结构
- Model:当前页面渲染时依赖的数据源
- ViewModel:也即Vue实例对象,是MVVM的核心 ,用于把当前页面的数据源(Model)和页面结构(View)连接在了一起, 基于"发布订阅"的设计模式
Vue的版本
-
Vue2
-
Vue3
- 组合式API,多根节点组件,更好的Ts支持
- 废弃了过滤器节点,不支持
$on, $off, $once
指令
vue指令可以看作html标签的特殊属性,这样方便理解
vue指令中可以写js代码,进行简单的判断、运算等等
vue指令中写的是js代码,要遵守js的语法规范,但是只能写简单的js表达式,不能写复杂的js语句
1.内容渲染指令——{{}}
辅助开发者渲染DOM元素的文本内容
-
Mustache插值表达式: 使用
{{}}作为内容的占位符,常用{{}}内部可以渲染JS表达式- :id=" 'stu' - list.number ",属性值内部也可以渲染JS表达式,案例的渲染值为
:id="stu-1"
-
v-text : 会默认覆盖元素内部的值,不常用
-
v-html:可以把带有html标签的字符串渲染为真正的DOM结构
2.属性绑定指令——:
解决插值表达式只能用在元素的内容节点中,无法动态绑定属性的不足
-
v-bind:
-
简写为
: -
属性绑定期间,如果绑定的内容需要进行动态拼接,字符串的外面应该包裹单引号
<div :title="'box'+index">1段文本</div>
3.事件绑定指令——@click/@input/@keyup
v-on:click="事件处理函数",简写为
@click="事件处理函数"
-
事件对象
- 原生DOM事件绑定中,可以在事件处理函数的形参处接收事件对象event,同理
- 在
v-on:/@所绑定的事件处理函数中,同样可以接收事件对象event $event可以解决事件参数对象被实际参数覆盖的问题,只需在标签的事件处理函数中占位即可-
<button @click="add(2, $event)">点击加2</button> Vue.createAPP({ data(){ return{ num : 1; } }, methods:{ add(n, e){//点击时,改变按钮背景颜色 const nowBgColor = e.target.style.backgroundColor; e.target.style.backgroundColor = nowBgColor === 'green' ? '' : 'green' this.num += n; } } })
-
事件修饰符
事件处理函数中preventDefalut和stopPropagation是常见需求,事件修饰符辅助我们更方便的对事件的触发进行控制
- @click.prevent : 阻止事件的默认行为,例如submit的默认刷新行为,a链接的默认跳转行为
- @click.stop : 阻止事件冒泡---(有时在一个元素中包含了子元素,而且父元素和子元素都有点击事件,此时我们希望的点击效果是:点击子元素区域的的时候,不触发父级元素的点击事件)
- @click.capture以捕获模式触发当前的事件处理函数
- @click.once绑定的事件只触发一次
- @click.self只有在event.target是当前元素自身时才触发事件处理函数
-
按键修饰符
<input type="text" @keyup.enter="submit" @keyup.esc="clearInput" /> methods: { clearInput(e) { e.target.value = ""; }, submit(e){//按下enter,就发送数据 let value = e.target.value;//获取到当前input输入框内的值 //将value通过axios提交到数据库 }, }
4.双向绑定指令——v-model
v-model=""辅助开发者在不操作DOM的前提下,快速获取表单的数据v-model只能配合表单元素使用
<input type="text" v-model="username" />
<hr />
<p>选中的省份是:{{province}}</p>
<select v-model="province">
<option value="">请选择省份</option>
<option value="1">北京</option>
<option value="2">河北</option>
<option value="3">黑龙江</option>
</select>
data(){
return{
username:'Bart',
province:'HeBei'
}
}
-
单向数据绑定
- 数据源的变动会同步到页面上,但页面的变化不会同步到数据源上
-
双向数据绑定v-model
-
只有表单元素使用双向数据绑定才有意义
-
input输入框
- type="radio"
- type="checkbox"
-
textarea
-
select
-
-
v-model的修饰符
-
v-model.number - 输入值自动转化为数字
-
v-model.trim - 自动过滤首尾的空白字符
<input type="text" v-model.trim="age" /> <button @click="submitAge">提交</button> methods: { submitAge() { console.log(`用户年龄${this.age}`); }, }, -
v-model.lazy : 阻止即时更新,中间的变化过程不会同步到数据源中——失去焦点时才更新数据
-
-
-
组件的v-model指令
作用:维护组件内外数据的同步
外界数据(根组件中的数据)的变化会被同步到组件中
组件中数据的变化也会同步到外界(根组件中的数据)
步骤:
-
父组件->子组件
- 父组件中通过
:属性绑定的形式,把数据传递给子组件 - 子组件中通过
props接收父组件传递过来的数据
- 父组件中通过
-
子组件向父组件
- 在
:属性绑定指令前添加v-model指令 - 在子组件中声明
emits自定义事件,格式为:update:xxx - 调用$emit触发自定义事件,更新父组件中的数据
- 在
-
5.条件渲染指令——v-if-else
实际开发不用考虑性能,二者都可以
-
v-if=""-
通过每次动态创建或移除元素——来实现元素的展示与隐藏
-
有更高的切换开销
-
使用条件:刚进来页面时某些元素默认不需要被展示,而且后期也很有可能不被展示
-
指令值一般是Boolean类型
-
v-if,v-else-if,v-else
<div v-if="type==='A'">优秀</div> <div v-else-if="type==='B'">良好</div> <div v-else-if="type==='C'">一般</div> <div v-else>较差</div> data(){ return{ type:"A"; } }
-
-
v-show=""- 有更高的初始渲染开销
- 通过添加和删除display:none——来实现元素的展示与隐藏
- 使用条件:频繁切换元素的显示和隐藏状态时使用
6.列表渲染指令——v-for
循环创建谁,就为谁添加v-for指令,常用于列表的
li标签中
<li v-for="(item, index) in list" :key="item.id"><li>官方建议,使用v-for指令时,一定要绑定一个:key属性
使用key属性维护列表的状态
列表的数据变化时,默认情况下vue会尽可能的复用已存在的DOM元素,从而提升渲染的性能。
但这种默认的性能优化策略,会导致有状态的列表(例如复选框
<input type="checkbox">)无法被正确更新为了给vue一个提示,以便它能跟踪每个节点的身份,我们要为每一项提供一个唯一的key属性
- key的值类型有要求:字符串或者数字类型
- key值必须具有唯一性(key的值不能重复):尽量把id作为key的值
- index的值当作key的值没有任何意义:index的值不具有唯一性
- 使用v-for时一定要指定key的值:既提升性能,又防止列表状态紊乱
<table class="table-striped table-hover table-bordered">
<thead>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
</thead>
<tbody>
//官方建议,使用v-for指令时,一定要绑定一个:key属性
//尽量把id作为key的值
//key的值类型有要求:字符串或者数字类型
//key值不能重复,否则终端报错:Duplicate keys detected
<tr v-for="(item, index) in list" :key="item.id">
<td>{{index+1}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
</tr>
</tbody>
</table>
过滤器
用于文本格式化
1.在插值表达式
{{}}中通过管道符|调用过滤器函数2.在
v-bind:=""中通过管道符|调用过滤器函数
定义
-
过滤器函数,必须被定义在filters节点之下
-
本质上是函数,且函数必须有返回值
-
过滤器函数的形参,永远都是管道符前面的值
-
分类
-
私有过滤器:定义在vue实例中的filters里
-
全局过滤器:定义在script标签里,实际开发中常用
//Vue.filter(全局过滤器的名字,过滤器的处理函数) Vue.filter('capitalize',str=>{ return str.charAt(0)+str.slice(1) })//全局定义首字母大写的过滤器函数capitalize -
若全局过滤器和私有过滤器重名,私有过滤器优先级更高
-
-
过滤器可以连续调用
{{msg | capitalize | maxLength}} -
过滤器可以传参
Vue.filter('capitalize',function(item, arg1, arg2){ //过滤器语法 return ... })
案例
(连续调用+传参)
<div id="app">
<p :title="info | capitalize">{{message | capitalize | maxLength(3)}}</p>
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script>
// 全局过滤器
// 首字母转大写的过滤器
Vue.filter('capitalize', (str) => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
// 定义控制文本长度的过滤器
Vue.filter('maxLength', (str, len = 10) => {
if(str.length <= len) return str
return str.slice(0, len) + '...'
})
</script>
(时间格式化)
<script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
Vue.filter("dateFormat", (time) =>
dayjs(time).format("YYYY-MM-DD-HH:mm:ss")
);
组件的节点
-
name——组件名称
- 短横线命名法:my-component
- 帕斯卡命名法:MyComponent
-
components——局部注册的子组件名称
-
data——数据源:是MVVM里的Model
-
methods——方法
-
props——自定义属性:方便组件的使用者为组件提供数据,提高组件可复用性
-
computed——计算属性
-
emits自定义事件:方便组件的使用者监听子组件的数据变化
-
watch——侦听器
-
filter——过滤器,过滤器函数必须要有返回值
vue3中不支持,建议使用计算属性或方法代替过滤器功能
filters:{ capitalize(s){ return something; } } -
待定
SPA
定义:一个web网站内只有唯一的一个HTML页面,所有的功能与交互都是基于这个页面呈现的
特点:
- 仅在web页面初始化时加载相应资源(前端三件套)
- 一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载跳转,而是利用js动态变换html的内容,实现页面与用户的交互
优点:
-
良好的交互体验
- 改变无需重新加载整个页面
- 获取数据通过Ajax异步获取
- 没有页面之间的跳转,没有白屏
-
良好的前后端分离模式
- 后端专注提供API接口
- 前端专注于页面的渲染
-
减轻服务器的压力
- 服务器只是提供数据,不负责页面的合成于逻辑的处理,吞吐能力大幅度提高
缺点:
-
首屏加载慢
- 路由懒加载
- 代码压缩
- CDN加速
- 网络传输压缩
-
不利于SEO
- SSR服务端渲染
创建工程化的Vue项目
-
vite-学习用,仅支持vue3
npm init vite-app demo1 cd demo1 npm i npm run dev项目组成:
-
node_modules——第三方依赖
-
public——公开的静态资源目录
-
src——项目的源代码目录
-
assets——静态资源文件(css,fonts)
-
components——自定义组件
-
App.vue——项目根组件
-
index.css——全局样式表
-
main.js——项目的打包入口文件
import {createApp} from 'vue'//按需导入vue实例的构造函数 import App from './App.vue' import MyComponent from "./components/Test.vue" const app = createApp(App) app.component('my-component',组件名)//这里是全局注册,注意和局部注册区分 app.mount("#app")
-
-
.gitignore——git的忽略文件 -
index.html——SPA中唯一的HTML页面
-
package.json——项目的包管理配置文件
-
-
vue/cli-企业开发用
VUE项目的运行流程
通过main.js打包入口文件把App.vue渲染到index.html的指定区域
组件化开发:
- 把页面上可以复用的部分封装为组件
- 提高复用性
- 加快开发效率
组件
1.构成
-
template模板结构,没有实际意义,渲染时不会作为页面的标签 -
script行为 -
<style lang='less' scoped>样式——less语法实现嵌套css样式的效果npm i less -D
2.注册
导入——>注册——>使用
全局注册
在
main.js打包入口文件中注册
import Swiper from "./components/MySwiper.vue"//此时的Swiper就是组件导入进来的名称
import App from "./App.vue"
const app = createApp(App)
app.component('my-swiper', Swiper)
//app.component('组件的使用名称',组件导入进来的名称)
局部注册
在对应要注册的组件.vue文件下
导入——>注册——>使用
3.组件样式冲突问题
- 通过自定义属性data-v-xxx
<template>
<div data-v-001>
<h1 data-v-001>Hello</h1>
<p data-v-001>123</p>
</div>
</template>
<style>
h1[data-v-001]{
color:blue;
}
</style>
为
style标签添加scoped属性可以达到相同的效果<style scoped>
-
deep样式穿透
作用:当前组件设置
scoped后,默认其样式对子组件是不生效的。如果想要让组件的某些样式对子组件生效,可以在样式前添加/deep/深度选择器,vue3中使用:deep(<inner-selector>)
4.Class与Style绑定
实际开发中,遇到动态操作元素样式的需求,因此,使用
v-bind属性绑定指令,为元素动态绑定class属性的值和行内的style样式
动态绑定HTML的class
- 三元表达式
<h1 :class="isItalic ? 'Italic' : ''">where data(){ return {isItalic:true} }——可以通过修改data中的数据,改变h1的class属性,达到修改css样式的效果
- 数组形式
<h1:class="[isItalic ? 'Italic' : '', isDelete ? 'Delete' : '']">
-
以对象语法绑定HTML的class
<h1 :class="classObj">classObj:{
isItalic : false,
isDelete : false
}
<style>isItalic{font:''},isDelete:{display:none}</style> -
以对象语法绑定内联的style
5.父子组件传值⭐
又称为组件的v-model指令
作用:维护组件内外数据的同步
作用:
- 外界数据的变化会被同步到组件中
- 组件中数据的变化也会同步到外界
步骤:
-
父组件->子组件
- 父组件中通过
:属性绑定的形式,把数据传递给子组件 - 子组件中通过
props接收父组件传递过来的数据
- 父组件中通过
-
子组件向父组件
-
在
:属性绑定指令前添加v-model指令 -
在子组件中
emits节点中定义自定义事件,格式为:update:xxxemits:['update:要更新的属性值'] -
调用$emit触发自定义事件,更新父组件中的数据
-
Props
自定义属性
props是只读的,无法修改
1.props基础
封装组件时要注意:
- 组件的DOM结构,style样式要尽量复用
- 组件中展示的数据由组件的使用者提供
Vue提供props自定义属性节点,方便使用者为组件提供要展示的数据,增强组件的复用性
定义:
组件的使用者可以通过props把数据传递到子组件的内部,供子组件内部使用
使用方法:
-
数组类型
props: ['name','age'], -
对象类型
props: init:{},init1:{},init2:{}
动态绑定props的值:
可以使用v-bind:属性绑定指令,为组件动态绑定props的值
<MyComponent :name='student1.name', :age='student1.age' v-for="(student, index) in list"></MyComponent>
props: ['name','age'],
细节:
- props 是"自定义属性",允许使用者通过自定义属性,为当前组件指定初始值
- 自定义属性的名字,是封装者自定义的(只要名称合法即可)
- props 中的数据,可以直接在模板结构中被使用
- props 是只读的,不要直接修改 props 的值,否则终端会报错!
2.props验证
在封装组件时对外界传递的props数据进行合法校验,防止数据不合法的问题
type为类型
required为必填项校验
default为默认值
validator为自定义验证函数
//props: ["info","age"]
props:{
info : {
type:[String, Number],
required : true,
default : '张三',
validator(value){
//不等于-1说明value值在数组里
return ['success','warning','danger'].indexOf(value) !== -1
}
},
age : Number
}
computed
计算属性
本质是一个
function函数,可以实时监听data中数据的变换,并return一个计算后的值,供组件渲染DOM时使用会缓存计算结果,性能比方法更好
必须有一个返回值
- 计算属性是一个
function函数,被定义在computed:{}节点下 - 计算属性的使用方法和data中的数据一样
- 会自动渲染求值,减少了代码复用
- function里的this和箭头函数中不同
相对于methods方法,计算属性会缓存计算结果,只有计算属性的依赖项变化时,才会重新运算,所以计算属性的性能更好
emits节点
作用:让组件的使用者可以监听到组件内状态的变化
使用步骤
-
封装组件时
- 声明自定义事件:自定义事件声明到emits数组中
- 触发自定义事件:
this.$emit('自定义事件的名称',要传递的val)
-
使用组件时
-
监听自定义事件:
@的形式的形式监听自定义事件//使用组件时 <myEvent @自定义事件的名称="处理事件"></myEvent> 处理事件(val){ do(val); }
-
watch
用来监听数据变化
定义在watch节点之下
本质是函数,把想要监听的数据名作为侦听器的函数名
案例:检测用户名是否被占用
//script引入jquery-v3.6.0.js
<script src="/lib/jquery-v3.6.0.js"></script>
<script>
const vm = new Vue({
el: "#app",
data:{
username: "";
},
watch:{
username(newVal, oldVal){
if(newVal==='') return
//调用jQuery中的Ajax发起请求,并判断newVal是否被占用
$.get("https://www.escook.cn/api/finduser/" + newVal, function(result){
console.log(result);
})
}
}
})
</script>
侦听器的格式
-
方法格式的侦听器(优先定义为方法格式)
- 缺点1:无法在刚进入页面的时候自动触发
- 缺点2:如果侦听的是一个对象,当对象的属性变化时不会触发侦听器
-
对象格式的侦听器
-
可以通过immediate选项,让侦听器自动触发:immediate选项的默认值是false,设置为true后在进入页面的时候会自动触发侦听器函数
-
可以通过deep选项,让侦听器侦听对象中每个属性的变化
data:{ info:{ username: "Lucy" } }, watch: { // 如果要侦听的是对象的子属性的变化,则必须包裹一层单引号 'info.username'(newVal) { console.log(newVal) } }
-
生命周期⭐
关注的是时间点,不是时间段
创建阶段
-
beforeCreate
-
created
- 组件的props/data/methods都已创建好
- 调用Ajax获取数据,加载到data里
- 核心:数据渲染完成
-
beforeMount
-
mounted
- 内存中的HTML结构,已经渲染到浏览器中
- 操作DOM元素
- 核心:DOM渲染完成
运行阶段
-
beforeUpdate
- props/data已更新
- HTML模板结构未渲染
-
updated
- props/data已更新
- HTML模板结构已渲染
- 已经根据最新数据完成了DOM结构的重新渲染