Vue2
Vue-basic
简述
Vue是一套用于构建用户界面的渐进式(自底向上逐层应用)JavaScript框架
特点
-
采取组件化模式(提高代码复用率、利于代码维护)
-
是一种声明式编码(无需之间操作DOM、提高开发效率)【区别于命令式编码】
-
使用==虚拟DOM+==优秀的Diff算法(尽可能复用DOM节点)
初识
-
下载并配置
<script type="text/javascript" src="./js/vue.js"></script>
下载vue.js并以
<script>
标签引入,Vue会注册成一个全局变量 -
初始
<body> <div id="root"> <h1>Hello,{{name}}</h1> </div> </body> <script type="text/javascript"> Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示。 // 创建Vue实例 new Vue({ // el 用于指定当前实例为哪个容器服务,值通常为css选择器字符串; el: '#root', // data中存储数据,数据供el所指定的容器使用; data: { name: 'Vue' } }) </script>
1.创建vue实例,并传入一个配置对象;
2.root容器代码符合html规范,视为Vue模板;
3.Vue实例和容器是一一对应的;
4.只有一个Vue实例,并且会配合着组件使用
5.
{{xxx}}
xxx代表的是js表达式,且会自动读取到data所有属性;6.data数据发送改变,页面中数据会自动更新;
- el的两种写法
<!-- 方式1 --> <div id="root"> <h1>你好,{{name}}</h1> </div> <script> new Vue({ el: '#root', data: { name: '尚硅谷' } }) </script>
<!-- 方式2 --> <div id="root"> <h1>你好,{{name}}</h1> </div> <script> const vm = new Vue({ data: { name: '尚硅谷' } }) v.$mount('#root') </script>
new Vue
时配置el属性- 先创建Vue实例,再通过
v.$mount('#root')
指定el值
- data的两种写法
<!-- 方式1:对象式 --> <div id="root"> <h1>你好,{{name}}</h1> </div> <script> new Vue({ el: '#root', data: { name: '尚硅谷' } }) </script>
<!-- 方式2:函数式 --> <div id="root"> <h1>你好,{{name}}</h1> </div> <script> new Vue({ el: '#root', data: function() { return{ name:'尚硅谷' } } }) </script>
1.对象式
2.函数式,组件时必用
重要原则:Vue管理的函数不能使用箭头函数,因会使之成为Window实例,而非Vue实例。
-
模板语法
-
插值语法
用于解析标签体内容
{{xxx}}
xxx是js表达式,且可以直接读取data中所有属性
-
指令语法
解析标签属性、解析标签体内容、绑定事件
v-bind:href = 'xxx' :href='xxx'
xxx是js表达式
有很多指令,形式均为
v-????
-
数据绑定
-
单向数据绑定
v-bind:href = 'xxx' <!-- 缩写 --> :href = 'xxx'
数据只能从data流向页面
-
双向数据绑定
v-mode:value = 'xxx' <!-- 缩写 --> v-model = 'xxx'
数据不仅能从data流向页面,还能从页面流向data
v-model只能应用在表单类元素(输入类元素)上,例如input、select等;
v-model:value
可简写为v-model
,而v-model一般默认收集value值
MVVM模型
-
模型(Model):对应data中的数据
-
视图(View):模板
-
视图模型(ViewModel):Vue实例对象
data中所有属性都会出现在vm上,而vm上所有属性及Vue原型上所有属性都能被直接使用。
数据代理
-
通过一个对象代理对另一个对象中属性的操作(读/写)
-
JavaScript中数据代理
<script> let obj1 = { x: 100 } let obj2 = { y: 100 } Object.defineProperty(obj2, 'x', { get() { return obj1.x }, set(value) { obj1.x = value } }) </script>
-
Vue中数据代理
- 通过vm对象代理data对象中属性的操作(读/写)
- 更为方便地操作data中的数据
- 原理:通过
Object.defineProperty()
把data对象中所有属性添加到vm上,为每个添加到vm上的属性,都指定一个getter/setter内部去操作(读/写)data中的数据。
图示:
- 加工了data
- vm._data = data
事件处理
事件基本使用
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx式事件名 - 事件回调需要配置再methods对象中最终会放在vm上
- methods中配置的函数不使用箭头函数,否则导致this的对象不是vm,而是window
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
@click='demo'
和@click='demo($event)'
效果一致,后者可传参
事件修饰符
- prevent:阻止默认事件
- stop:阻止事件冒泡
- once:事件只触发一次
- capture:使用事件的捕获模式
- self:只有event.target是当前操作元素时才能触发事件
- passive:事件默认行为立即执行,无须等待事件回调执行完毕
修饰符可以连续写
键盘事件
-
Vue中将常用按键起了别名
- 回车 --> enter 删除–>delete(捕获“删除“和”退格“键) 退出–>esc 空格–>space 换行–>tab(配合keydown使用)
- 上–>up 下–>down 左–>left 右–>right
-
Vue中未提供别名的按键,使用按键原始key值绑定,
-
系统修饰按键,例如
ctrl
、alt
、shift
、meta/window/command
- 配合keyup使用:按下修饰键的同时,需要再按下其他键,随后在释放其他键,事件才能被触发
- 配合keydown使用:正常触发事件
-
定制按键别名
Vue.config.keyCodes.自定义键名 = 键码
计算属性与监视属性
计算属性
-
定义:需要的属性不存在,而通过已有属性计算得来
-
原理:底层借助
Object.defineproperty()
方法提供getter和setter -
get()函数的执行
- 初次读取时执行一次
- 当依赖数据发生变化时再次被调用
-
应用
new Vue({ el: '#root', data: { firstName: '张', lastName: '三' }, computed: { fullName: { // 读取fullName时,get就会被调用,且返回值会作为fullName的值 // 初次读取fullName时,或者所依赖的数据发生变化时,get被调用 get() { return this.firstName + '-' + this.lastName }, set(value) { const arr = value.split('-') this.firstName = arr[0] this.lastName = arr[1] } } // 简写(不考虑修改时使用,即不配置set()时) fullName(){ return this.firstName + '-' + this.lastName } } })
-
优势
与methods实现相比,内部有缓存机制(可复用),效率高,调试方便
-
备注
计算属性最终会出现在vm对象上,直接读取使用
如果计算属性被修改,必须通过写set函数响应修改,且set中药引起计算时依赖的数据发生变化
监视属性
-
普通监视
-
当被监视的属性变化时,回调函数自动调用,而进行相关操作
-
监视的属性必须存在,才能进行监视
-
语法
new Vue
时传入watch配置
const vm = new Vue({ el: '#root', data: { isHot: true }, watch: { isHot: { immediate: true, handler(newValue, oldValue) { console.log('isHot被修改了', newValue, oldValue); } } }, })
- 通过
vm.$watch
监视
const vm = new Vue({ el: '#root', data: { isHot: true }, }) vm.$watch('isHot', { immediate: true, handler(newValue, oldValue) { console.log('isHot被修改了', newValue, oldValue); } })
-
-
深度监视
-
Vue中的watch默认不监测对象内部值的改变(一层)
-
配置
deep:true
可以监测对象内部值改变(多层)Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
使用watch时根据数据的具体结构,决定是否采用深度监测
-
计算对比与监视属性对比
-
computed
能完成的功能,watch
都可以完成; -
watch
能完成的功能,computed
不一定能完成,例如:watch
可以进行异步操作,而computed
则不可以。所有被Vue管理的函数,最好写成普通函数,此时this会指向vm或组件实例对象;
所有不被Vue所管理的的函数(如定时器回调函数、ajax回调函数、Promise回调函数等),最好写成箭头函数,此时可使this的指向vm或组件实例对象。
绑定样式
绑定class
-
字符串写法
适用于样式类名不确定,需要动态指定
<body> <div class="basic" :class="classStr" @click="changeMood">{{name}}</div> </body> <script> new Vue({ el: '#root', data: { name: 'Vue', classStr: 'normal', }, methods: { changeMood() { const arr = ['happy', 'sad', 'normal'] const index = Math.floor(Math.random() * 3) this.mood = arr[index] } }, } }) </script>
-
数组写法
适用于绑定的样式个数不确定、名字不确定
<body> <div class="basic" :class="classArr">{{name}}</div> </body> <script> new Vue({ el: '#root', data: { name: 'Vue', classArr: ['wwk1', 'wwk2', 'wwk3'], }, }) </script>
-
对象写法
适用于绑定的样式个数确定、名字确定,但要动态用不用
<body> <div class="basic" :class="classArr">{{name}}</div> </body> <script> new Vue({ el: '#root', data: { name: 'Vue', classObj: { wwk1: false, wwk2: true, }, }, }) </script>
绑定style
-
数组写法
<body> <div class="basic" :style="styleArr">{{name}}</div> </body> <script> new Vue({ el: '#root', data: { name: 'Vue', styleArr: [{ fontSize: '40px', color: 'red', }, { backgroundColor: 'orange' }, ] }, }) </script>
-
对象写法
<body> <div class="basic" :style="styleObj">{{name}}</div> </body> <script> new Vue({ el: '#root', data: { name: 'Vue', styleObj: { fontSize: '40px', color: 'red', backgroundColor: 'orange' }, }, }) </script>
条件渲染
-
v-if
-
写法
v-if = "expression" v-else-if = "expression" v-else = "expression"
-
适用
切换频率较低的场景
-
特点
不展示DOM元素,直接被移除
-
注意点
v-if可以和v-else-if及v-else一起使用,但要求其结构不能被打断;使用
v-if
时,元素可能无法获取到;
-
-
v-show
-
写法
v-show = "expression"
-
适用
切换频率较高的场景
-
特点
不展示DOM元素,未被移除,仅仅使用样式
display=none
隐藏掉了 -
注意点
使用
v-show
时,元素一定能被获取到
-
列表渲染
-
v-for指令
-
用于展示列表数据
-
语法
v-for="(item,index) in xxx" :key="yyy"
-
可遍历
数组、对象、字符串(使用较少)、指定次数(使用较少)
-
-
key的内部原理
-
虚拟DOM中key的作用
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据“新数据”生成“新的虚拟DOM”,随后Vue进行“新虚拟DOM”与“旧虚拟DOM”的差异比较
-
对比规则
- “旧虚拟DOM”中找到与“新虚拟DOM”相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM;
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- “旧虚拟DOM”中未找到与“新虚拟DOM”相同的key:
- 创建新的真实DOM,随后渲染到页面
- “旧虚拟DOM”中找到与“新虚拟DOM”相同的key:
-
用index作为key可能引发的问题
- 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生不必要的真实DOM更新,导致界面效果不出现问题,但会降低效率
- 若结构中还包含输入类的DOM,会产生错误DOM更新,导致界面出现问题
-
如何选择
- 最好使用每条数据的唯一标识作为key,如id、手机号、身份证号、学号等唯一值
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作而仅用于渲染列表用于展示,使用index作为key是没问题的
-
-
列表过滤
-
列表排序
监视数据
-
原理
Vue会监视data中所有层次的数据
-
分类
-
监视对象中数据
通过setter实现监视,且要在
new Vue
时就传入要监视的数据-
对象中后追加的属性,Vue默认不做响应式处理
-
需给后追加的属性做响应式,需使用API:
Vue.set(target,propertyName/index,value) vm.$set(target,propertyName/index,value)
-
-
监测数组中数据
通过包裹数组更新元素的方法实现,本质做两件事:其一,调用原生对应的方法队数组进行更新;其二,重新解析模板,进而更新页面;
在Vue修改某个元素会用到到以下方法:
// 一些API push() pop() shift() unshift() splice() sort() reverse() // 直接更改 Vue.set() vm.$set()
-
-
示例
<!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> <script type="text/javascript" src="./js/vue.js"></script> </head> <body> <div id="root"> <h1>学生信息</h1> <button @click="student.age++">年龄加一岁</button> <button @click="addSex">添加性别属性(默认值为男)</button> <button @click="student.sex='未知'">修改性别</button> <button @click="addFriend">在列表首位添加一条朋友信息</button> <button @click="updateFirstFriendName">修改第一个朋友为张三</button> <button @click="addHobby">添加一个爱好</button> <button @click="updateHobby">修改第一个爱好</button> <h2>学生姓名:{{student.name}}</h2> <h2 v-if="student.sex">学生性别:{{student.sex}}</h2> <h2>学生年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2> <h2>爱好</h2> <ul> <li v-for="(h,index) in student.hobbies" :key="index"> {{h}} </li> </ul> <h2>朋友们</h2> <ul> <li v-for="(f,index) in student.friends" :key="index"> {{f.name}}--{{f.age}} </li> </ul> </div> </body> <script type="text/javascript"> Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示。 // 创建Vue实例 new Vue({ // el 用于指定当前实例为哪个容器服务,值通常为css选择器字符串; el: '#root', // data中存储数据,数据供el所指定的容器使用; data: { student: { name: "tom", age: { rAge: 21, sAge: 18, }, hobbies: ['抽烟', '喝酒', '烫头', '听相声'], friends: [{ name: 'jerry', age: 20 }, { name: 'tony', age: 21 }] } }, methods: { addSex() { // Vue.set(this.student, "sex", '男') this.$set(this.student, "sex", '男') }, addFriend() { this.student.friends.unshift({ name: "jack", age: 22 }) }, updateFirstFriendName() { this.student.friends[0].name = "Kevin" }, addHobby() { this.student.hobbies.push("学习") }, updateHobby() { // this.student.hobbies.splice(0, 1, "开车") // this.set(this.student.hobbies, 0, '开车') this.$set(this.student.hobbies, 0, '开车') } }, }) </script> </html>
-
注
Vue.set()
和vm.$set()
不能给vm和vm中的根数据对象添加数据
收集表单数据
-
若
<input type="text" />
,则v-model收集的是value值,用户输入的就是value值 -
若
<input type="radio" />
,则v-model收集的是value值,且要给标签配置value值 -
若
<input type="checkbox" />
,没有配置input中的value属性,则收集的是checked(勾选为true,未勾选为false);
而配置了input中的value属性:
v-model的初始值是非数组,则收集的是checked(勾选为true,未勾选为false);
v-model的初始值是数组,则收集的是value组成的数组;
注:v-model有三个修饰符
lazy
失去焦点再收集数据number
输入字符串转成有效数字trim
输入首尾空格过滤
过滤器
-
定义
对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑处理)
-
语法
-
注册过滤器
// 全局过滤器 Vue.filter(name,callback) // 局部过滤器 new Vue({ filter:{ } })
-
使用过滤器
{{ xxx | 过滤器名}} v-bind:属性 = "xxx | 过滤器名"
-
-
注
过滤器也可以接收额外参数、多个过滤器也可以串联
并不改变原本数据,而是产生新的对应数据
内置指令
v-bind:xxx
单向绑定解析表达式,可简写为:xxx
v-model:
双向数据绑定
v-for:
遍历数组/对象/字符串
v-on:
绑定事件监听,可简写为@
v-if:
条件渲染(动态控制节点是否存在)
v-else:
条件渲染(动态控制节点是否存在)
v-show:
条件渲染(动态控制节点是否展示)
v-text:xxx
向所在的节点中渲染文本内容(与插值语法{{xxx}}
类似,但区别在于v-text会替换掉节点中的内容,而插值语法则不会)
v-html:xxx
向指定节点中渲染包含html结构的内容(与v-text类似,但区别在于v-html会识别html结构并解析),[安全性问题:网站上动态渲染任意HTML危险,容易被XSS攻击;一定要在可信内容上使用v-html;永远不要用在用户提交的内容上!]
v-cloak
一特殊属性,Vue示例创建完毕并接管容器会自动删掉v-cloak属性;使用css配合v-cloak
可以解决网络慢时页面展示出{{xxx}}
的问题
v-once
所在节点在初次动态渲染后就被视为静态内容,之后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
v-pre
跳过所在节点的编译过程;可用其跳过"没有使用指令的语句、没有使用插值语法的节点",会加快编译
自定义指令
-
语法
-
局部指令
配置对象
new Vue({ directives:{指令名:配置对象} })
配置对象中常用的三个回调
- bind 指令与元素成功绑定时调用
- inserted 指令所在元素被插入页面时调用
- update 指令所在模板结构被重新解析时调用
函数
new Vue({ directives:{指令名:回调函数} })
-
全局指令
配置对象
Vue.directive(指令名,配置对象)
回调函数
Vue.directive(指令名,回调函数)
-
-
注
指令定义时不加
v-
,但使用时需要加v-
;指令名如果是多个单词,要使用
kebab-case
命名方式,而不是camelCase
命名方式
生命周期
-
内容
又名生命周期回调函数、生命周期函数、生命周期钩子;是Vue在关键时刻帮助调用一些特殊名称的函数;生命周期函数的名字不可改变,但函数的具体内容可根据需求编写;其中this指向的是vm或组件实例对象
-
流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8SfvnHpi-1670124487775)(F:\前端\Vue\资料(含课件)\02_原理图\生命周期.png)]
-
过程
-
将要创建 = = => 调用beforeCreate函数
-
创建完毕 = = => 调用created函数
-
将要挂载 = ==> 调用beforeMount函数
-
挂载完毕 = = => 调用mounted函数(常用:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等[初始化操作])
-
将要更新 = = => 调用beforeUpdate函数
-
更新完毕 = = => 调用updated函数
-
将要销毁 = = => 调用beforeDestroy函数(清除定时器、解绑自定义事件、取消订阅消息等[收尾工作])
-
销毁完毕 = = => 调用destroyed函数
销毁后借助Vue开发者工具无法看到任何信息;销毁后自定义事件会失效,但原生DOM事件依旧有效;一般不会在beforeDestroy操作数据(因操作数据后不会再触发更新流程)。
-
Vue-component
模块和组件、模块化和组件化
-
模块
向外提供特定功能的JavaScript程序,一般为一个js文件;便于复用js、简化js的编写、提高js运行效率
-
组件
实现应用中局部/特定功能代码(包括HTML、CSS和JavaScript)和资源(MP3、MP4等)的集合;便于复用编码、简化项目编码、提高运行效率
-
模块化
应用中的js都以模块编写,该应用即为一个模块化的应用。
-
组件化
应用中的功能都是多组件的方式来编写的,该应用即为一个组件化应用。
非单文件组件
-
定义
一个文件中包含有n个组件
-
使用
-
定义组件(创建组件)
Vue.extend(options)
options
和new Vue(options)
传入的options
几乎一样,区别在于:el
不需要写–最终所有组件都要经过一个vm的管理,由vm中的el决定服务于哪个容器data
必须写成函数形式–避免组件被复用,数据存在引用关系
-
注册组件
// 局部注册 new Vue({ components:{ } }) // 全局注册 Vue.component("组件名",组件)
-
使用组件(写组件标签)
<组件名></组件名>
-
-
注意点
-
组件名
-
一个单词组成
首字母小写
school
或首字母大写School
-
多个单词组成
kebab-case命名
my-school
或CamelCase命名MySchool
(需要Vue脚手架支持)组件名尽可能回避HTML中已有的元素名称;可以使用那么配置项指定主键再开发者工具中呈现的名字;
-
-
组件标签
<school></school>
或<school/>
不用使用脚手架时,
<school/>
会导致后续组件不能渲染。 -
简写方式
const school = Vue.extend(options) // 简写为 const school = options
-
VueComponent
- 组件本质是一个名为
VueComponent
的构造函数,是Vue.extend
生成的 - 只需要写组件标签
<school></school>或<school>
,Vue解析是会创建school组件的实例对象,即自动执行new VueComponent(options)
- 每次调用
Vue.extend
,返回的都是一个全新的VueComponent
- this的指向问题:组件配置中,data函数、methods中的函数、watch中的函数、computed中的函数,其this均是VueComponent实例对象(简称vc);
new Vue()
配置中,data函数、methods中的函数、watch中的函数、computed中的函数,其this均是Vue实例对象(简称vm)。
- 组件本质是一个名为
-
内置关系
VueComponent.prototype.__proto__ === Vue.prototype
目的:让组件实例对象(vc)可以访问到Vue原型上的属性和方法
-
单文件组件
一个文件中只包含一个组件
<template>
<!-- 组件的结构 -->
<div></div>
</template>
<script>
// 组件交互相关的代码(数据、方法)
</script>
<style>
/* 组件的样式 */
</style>
Vue-cli
准备工作
-
全局安装
npm install -g @vue/cli
仅执行第一次即可
-
创建项目
vue create xxx
切换到需要创建项目的目录下,创建项目
-
启动项目
npm run serve
文件结构分析
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git 版本管制忽略的配置
├── babel.config.js: babel 的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
ref属性
-
被用来给元素或子组件注册引用信息(HTML中ID的替代者)
-
应用在HTML标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
-
使用
<template> <!-- 打标识 --> <h1 ref="xxx">……</h1> 或 <school ref="xxx"></school> </template> <script> // 获取 this.$refs.xxx </script>
配置项props
-
功能
让组件接收外部传来的数据
-
使用
-
传递数据
<template> <Demo name="xxx"/> </template>
-
接收数据
-
只接受类型
<script> props:['name'] </script>
-
限制类型]
<script> props:{ name:String } </script>
-
限制类型+限制必要性+指定默认值
<script> props:{ name:{ type:String, // 类型 required:true, // 必要性 default:'张三' // 默认值 } } </script>
-
-
-
注
props是只读的,Vue底层会监测对props的修改,如果修改就会发出警告;
若业务需求确定需要修改,则复制props的相关内容到data中一份再去修改data中的数据。
mixin混入
-
功能
可以把多个组件共用的配置提取成一个混入对象
-
使用
-
定义混入
{ data(){ …… },methods:{ …… } …… }
定义名称为
xxx.js
-
使用混入
-
全局混入
import xxx from '...' Vue.mixin(xxx)
在main.js中使用
-
局部混入
<script> import xxx from '...' mixins:['xxx'] </script>
在局部文件中使用
-
-
插件
-
功能
用于增强Vue
-
本质
包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
-
使用
-
定义插件
Obj.install = function (Vue,options){ // 添加全局过滤器 Vue.filter(……) // 添加全局指令 Vue.directive(……) // 配置全局混入 Vue.mixin(……) // 添加实例方法 Vue.prototype.$myMethod = function () { } Vue.prototype.$myMethod = xxx }
定义为
xxx.js
-
使用插件
// 引入插件 import xxx from '' // 使用插件 Vue.use(xxx)
在main.js中使用
-
scoped样式
-
功能
让样式在局部生效,防止冲突
-
使用
<style scoped> xxx{ } </style>
总结TodoList案例
组件化编码流程
- 实现静态组件:抽取(拆分)组件,组件要按照功能点拆分,使用组件实现静态页面效果,命名不要和html元素冲突
- 展示动态数据:数据类型、名称是什么?数据保存在哪个组件中(考虑数据的存放位置,数据是一个组件在用还是一些组件在用)
- 一个组件在用:放在组件自身即可
- 一些组件在用:放在他们共同的父组件上[状态提升]
- 交互:从绑定事件监听开始
props适用于
- 父组件 ===> 子组件 通信
- 子组件 ===> 父组件 通信(要求父先给子一个函数)
v-model使用
v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传值
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐
webStorage
-
存储内容大小一般支持5MB左右
-
浏览器端通过
Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制 -
相关API:
-
xxxStorage.setItem('key','value')
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
-
xxxStorage.getItem('person')
该方法接受一个键名作为参数,返回键名对应的值
-
xxxStorage.removeItem('key')
该方法接受一个键名作为参数,并把该键名从存储中删除
-
xxxStorage.clear()
该方法会清空存储中的所有数据
-
-
备注
- SessionStorage存储的内容会随着浏览器窗口关闭而消失
- LocalStorage存储的内容需要手动清除才会消失
xxxStorage.getItem('xxx')
如果xxx对应的value获取不到,那么getItem
的返回值是null
JSON.parse(null)
的结果依然是null
组件的自定义事件
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景
A是父组件,B是子组件,B需要给A传递数据,那么就要在A中给B绑定自定义事件(事件回调在A中)
-
使用
-
绑定自定义事件
方法一:在父组件中:
<Demo @demo="test" />
或<Demo v-on:demo="test" />
方法二:在父组件中:
<template> <Demo ref="demo"> </template> <script> export default { mounted(){ this.$refs.xxx.$on('demo',this.test) } } </script>
若想让自定义事件只能触发一次,可以使用修饰符
once
或方法$once
-
触发自定义事件
this.$emit('demo','数据')
-
解绑自定义事件
this.$off('demo')
-
-
组件上也可以绑定元素DOM事件,需要使用修饰符
native
-
注意
通过
this.$refs.xxx.$on('demo',callback(){})
绑定自定义事件时,回调函数要么配置methods中,要么使用箭头函数,否则this指向会出现问题
全局事件总线(GlobalEventBus)
-
一种组件间通信的方式,适用于任意组件间通信
-
安装全局事件总线
new Vue({ …… beforeCreate(){ Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm } …… })
-
使用
-
接收数据:A组件想接收数据,则A组件中给
$bus
绑定自定义事件,事件的回调留在A组件自身<script> methods(){ demo(data){ …… } }, …… mounted(){ this.$bus.$on('xxx',this.demo) } </script>
-
提供数据:
this.$bus.$emit('xxx',数据)
-
-
注意
最好在
beforeDestory(){}
钩子中,用$off
却解绑当前组件所用到的事件
消息订阅与发布
-
一种组件间通信的方式,适用于任意组件间通信
-
使用步骤
-
安装pubsub:
npm i pubsub-js
-
引入pubsub:
import pubsub from 'pubsub-js'
-
接受数据:A组件需要接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
<script> methods(){ demo(data)P{ …… } }, …… mounted(){ this.pId = pubsub.subscribe('xxx',this.demo) // 订阅消息 } </script>
-
提供数据:
pubsub。publish('xxx',数据)
-
取消订阅:
使用
beforeDestroy
钩子取消订阅,用pubsub.unsubscribe(pId)
取消订阅
-
-
注意
nextTick
-
场景:
当改变数据后要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
-
语法:
this.$nextTick(callback)
-
作用:
在下一次DOM更新结束后执行器指定的回调
封装的过渡与动画
-
作用
在插入、更新或移除DOM元素时,在适合的时候给元素添加样式类名
-
图示
-
用法
-
准备好样式
-
元素进入的样式
v-enter: 进入的起点
v-enter-active: 进入的过程中
v-enter-to: 进入的终点
-
元素离开的样式
v-leave: 离开的起点
v-leave-active: 离开的过程中
v-leave-to: 离开的终点
-
-
使用
<transition>
包裹需要过渡的元素,并配置name
属性<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
备注:如果有多个元素需要过度,则需要使用
<transition-group>
,且每个元素都要指定key
值
-
Vue-Ajax
配置代理
-
方法一
在
vue.config.js
中添加如下配置:devServer:{ proxy:"http://localhost:5000" }
- 优点:配置简单,请求资源时直接发给前端(8080)即可
- 缺点:不能配置多个代理,不能灵活地控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先配置前端资源)
-
方法二
编写
vue.config.js
配置具体代理规则:module.export = { devServer: { proxy: { '/api1': { //匹配所有以'/api1'为开头的请求路径 target: 'http://localhost:5000', //代理目标的基础路径 pathRewrite: { '^api1': '' }, ws: true, //用于支持websocket changeOrigin: true //用于控制请求头中的host值 }, '/demo': { //匹配所有以'/api2'为开头的请求路径 target: 'http://localhost:5001', //代理目标的基础路径 pathRewrite: { '^/demo': '' }, ws: true, //用于支持websocket changeOrigin: true //用于控制请求头中的host值 }, } /* changeOrigin设置为true时,服务器收到地请求头中的host为:'localhost:5000' changeOrigin设置为false时,服务器收到地请求头中的host为:'localhost:8080' changeOrigin默认值为true */
- 优点:可以配置多个代理,且可以灵活地控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
axios
vue_resource
插槽
-
作用
让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方法,适用于父组件===>子组件
-
分类
默认插槽、具名插槽、作用域插槽
-
使用方式
-
默认插槽
<!-- 父组件中 --> <Category> <div>html结构1</div> </Category>
<!-- 子组件中 --> <template> <div> <!-- 定义一个插槽 --> <slot>插槽默认内容</slot> </div> </template>
-
具名插槽
<!-- 父组件中 --> <Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category>
<!-- 子组件中 --> <template> <div> <!-- 定义一个插槽 --> <slot name="center">插槽默认内容</slot> <slot name="footer">插槽默认内容</slot> </div> </template>
-
作用域插槽
<!-- 父组件中 --> <Category> <template slot-scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> <template v-slot:footer> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category>
<!-- 子组件中 --> <template> <div> <!-- 定义一个插槽 --> <slot :games="games">插槽默认内容</slot> </div> </template> <script> export default{ name:'Category', props:['title'], data(){ return{ games:['lol',] } } } </script>
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
例:games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定
-
Vuex
vuex
-
概念
在Vue中实现集中式状态/数据管理的一个Vue插件,在vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
-
何时用
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
-
原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXH7hDRo-1670124487779)(F:\前端\Vue\资料(含课件)\02_原理图\vuex.png)]
-
搭建vuex环境
-
下载安装
npm i vuex
-
创建文件
src/store/index.js
// 该文件用于创建vuex中的store // 引入Vue import Vue from 'vue' // 引入Vuex import Vuex from 'vuex' // 使用Vuex插件 Vue.use(Vuex) // 准备actions--用于响应组件的动作 const actions = {}; // 准备mutations--用于操作数据(state) const mutations = {}; // 准备state--用于存储数据 const state = {}; // 准备getters--用于将state中的数据进行加工 // 当state中的数据需要经过加工后再使用时,可以使用getters加工。 const getters = {}; // 创建并暴露store export default new Vuex.Store({ actions, mutations, state, getters })
-
在
main.js
中创建vm时传入store
配置项…… // 引入store import store from './store' …… // 创建vm new Vue({ el:'#app', render: h => h(App), store, }).$mount('#app')
-
-
基本使用
-
配置部分
store.js
文件:初始化数据statue
、配置actions
和mutations
、追加getters
配置const actions = { increment(context, value) { context.commit('INCREMENT', value) } }; const mutations = { INCREMENT(state, value) { state.sum += value }, }; const state = { sum:0 }; const getters = { bigSum(state){ return state.sum *10 } };
-
组件中读取vuex中的数据:
$store.state.sum
和$store.getters.bigSum
-
组件中修改vuex中的数据:
$store.dispatch('action中的方法名','数据')
或
$store.commit('mutations中的方法名','数据')
备注:若没有网络请求,组件中可以越过actions,即不写
dispatch
,直接编写commit
-
-
四个map方法的使用
-
mapState方法:帮助映射
state
中的数据为计算属性<script> computed:{ /*借助mapState生成计算属性,从state中读取数据(对象写法)*/ ...mapState({ sum: 'sum', school: 'school', subject: 'subject' }), /*借助mapState生成计算属性,从state中读取数据(数组写法)*/ ...mapState(['sum', 'school', 'subject']), } </script>
-
mapGetters方法:帮助映射
getters
中的数据为计算属性<script> computed:{ /*借助mapGetters生成计算属性,从getters中读取数据(对象写法)*/ ...mapGetters({ bigSum: 'bigSum', }), /*借助mapGetters生成计算属性,从getters中读取数据(数组写法)*/ ...mapGetters(['bigSum']), } </script>
-
mapActions方法:帮助生成与
actions
对话的方法,即包括$store.dispatch(xxx)
的函数<script> methods:{ // 借助mapActions生成对应的方法,方法会调用dispatch去联系actions(对象写法) ...mapActions({ incrementOdd: 'incrementOdd', incrementWait: 'incrementWait' }), // 借助mapActions生成对应的方法,方法会调用dispatch去联系actions(数组写法) ...mapActions([ 'incrementOdd', 'incrementWait' ]), } </script>
-
mapMutations方法:帮助生成与
mutations
对话的方法,即包括$store.commit(xxx)
的函数<script> methods:{ // 借助mapMutations生成对应的方法,方法会调用commit去联系mutations(对象写法) ...mapMutations({ increment: 'INCREMENT', decrement: 'DECREMENT' }), // 借助mapMutations生成对应的方法,方法会调用commit去联系mutations(对象写法) ...mapMutations([ 'INCREMENT', 'DECREMENT' ]), } </script>
备注:mapActions与mapMutations使用时,若需要传递参数需要:模板中绑定事件时传递好参数,否则参数是事件对象。
-
模块化
-
目的:
让代码更好维护,让多种数据分类更加明确
-
使用:
-
修改
store.js
const countAbout = { namespaced: true, // 开启命名空间 state: {}, actions: {}, mutations: {}, getters:{}, } const personAbout = { namespaced: true, // 开启命名空间 state: {}, actions: {}, mutations: {}, getters:{}, } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
-
开启命名空间后,组件中读取
state
数据<script> computed:{ // 方式一:自己直接读取 this.$store.state.personAbout.list // 方式二:借助mapState读取 ...mapState('countAbout',['sum','school','subject']), } </script>
-
开启命名空间后,组件中读取
getters
数据<script> computed:{ // 方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] // 方式二:借助mapState读取 ...mapGetters('countAbout',['bigSum']), } </script>
-
开启命名空间后,组件中调用
dispatch
<script> methods:{ // 方式一:自己直接读取 this.$store.dispatch['personAbout/addPersonWang',person] // 方式二:借助mapState读取 ...mapActions('countAbout',['incrementOdd','incrementWait']), } </script>
-
开启命名空间后,组件中调用
commit
<script> methods:{ // 方式一:自己直接读取 this.$store.commit['personAbout/ADD_PERSON',person] // 方式二:借助mapState读取 ...mapMoutations('countAbout',['INCREMENT','DECREMENT']), } </script>
-
Vue-router
路由
-
基本概念
一个路由就是一组映射关系(key-value),key是路径,value可能是function或component
-
分类
-
后端路由
value是function,用于处理客户端请求的路由
工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。
-
前端路由
value是component,用于展示页面内容的路由
工作过程:当浏览器的路径改变时,对应的组件就会显示。
-
-
vue-router是vue的一个插件库,专门实现SPA应用
-
SPA应用(单页面Web应用),整个应用只有一个完整的页面(点击页面中的导航链接不会刷新页面,只会做页面的局部更新),数据需要通过ajax请求获取
基本使用
-
安装
npm i vue-router@3
vue2需要采用vue-router3版本
-
应用
// 引入VueRouter import VueRouter from 'vue-router' // 引入路由器 import router from './router/index' Vue.use(VueRouter)
在main.js中编写
-
配置
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' // 引入组件 import About from '../components/About.vue' import Home from '../components/Home.vue' // 创建并暴露一个路由器 export default new VueRouter({ routes: [{ path: '/about', component: About }, { path: '/home', component: Home } ] })
-
实现切换
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
active-class可配置高亮样式
-
指定展示位置
<router-view></router-view>
-
几个注意点
- 路由组件通常放在
pages
文件夹下,一般组件通常存放在components
文件夹下。 - 通过切换路由,“隐藏”了的路由组件,默认是被销毁的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,内部存储着自己的路由信息。 - 整个应用只有一个router,可有通过组件的
$router
属性获取到。
- 路由组件通常放在
嵌套路由(多级路由)
-
配置路由规则
routes: [{ path: '/about', component: About }, { path: '/home', component: Home, children: [{ path: 'news', // 此处一定不要写'/news' component: News, }, { path: 'message', // 此处一定不要写'/message' component: Message, } ] } ]
在
/router/index.js
下配置使用children进行配置
-
跳转
<router-link to="/home/news">News</router-link>
要写完整路径
路由传参
query参数
-
传递query参数
<!-- 跳转路由并携带query参数,to的字符串写法 --> <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{id:m.id,title:m.title}} ">{{m.title}}</router-link>
-
接受query参数
$route.query.id $route.query.title
params参数
-
配置路由,声明params参数
{ path: '/home', component: Home, children: [{ path: 'news', component: News, }, { path: 'message', component: Message, children: [{ name: 'xiangqing', path: 'detail/:id/:title', // 使用占位符声明接收params参数 component: Detail, }] } ] }
-
传递参数
<!-- 跳转路由并携带params参数,to的字符串写法 --> <router-link :to="`/home/message/detail/${m.id}&${m.title}`">{{m.title}}</router-link> <!-- 跳转路由并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:m.id, title:m.title}} ">{{m.title}}</router-link>
注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
-
接收参数
$route.params.id $route.params.title
接收参数
props配置
-
配置路由,声明props
{ name: 'xiangqing', path: 'detail/:id/:title', component: Detail, // 第一种写法,值为对象,该对象中的所有key-value都以props形式传入Detail组件 // props: { money: 'the money'} // 第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props形式传给Detail组件 // props: true // 第三种写法,值为函数 props($route) { return { id: $route.query.id, title: $route.query.title } } }
-
接收参数
props:['id','title']
命名路由
-
作用
简化路由的跳转
-
使用
-
给路由命名
{ path:'/demo', component:Demo children:[ { path:'test', component:Test, children:[ { name:'hello' // 给路由命名 path:'welcome', component:Hello, } ] } ] }
-
简化跳转
<!-- 简化前,需要写完整的路径 --> <route-link to="/demo/test/welcome">跳转</route-link> <!-- 简化后,直接通过名字跳转 --> <route-link :to="{name:'hello'}">跳转</route-link> <!-- 简化写法配合传递参数 --> <route-link :to="{ name:'hello', query:{ id:666, title:'你好' }}" >跳转</route-link>
-
<router-link>
的replace
属性
-
作用
控制路由跳转时操作浏览器历史记录的模式
-
写入方式
-
push
是追加历史记录,类似于压栈 -
replace
是替换当前记录路由跳转时候默认为
replace
-
-
开启
replace
模式<router-link replace>Home</router-link> <router-link :replace="true">Home</router-link>
编程式路由导航
-
作用
不借助
<router-link>
实现路由跳转,让路由跳转更灵活 -
语法
<script> // $router的两个API this.$router.push({ name: 'xiangqing', query: { id: m.id, title: m.title } }) this.$router.replace({ name: 'xiangqing', query: { id: m.id, title: m.title } }) this.$router.back() // 前进 this.$router.forward() // 后退 this.$router.go() // 可前进可后退 </script>
缓存路由组件
-
作用
让不展示的路由组件保持挂载,不被销毁
-
语法
<!-- 缓存一个路由组件 --> <keep-alive include="News"> <router-view></router-view> </keep-alive> <!-- 缓存多个路由组件 --> <keep-alive include="['News','Message']"> <router-view></router-view> </keep-alive>
生命周期钩子
-
作用
路由组件所独有的两个钩子,用于捕获路由组件的激活状态
-
具体实现
activated
–路由组件被激活时触发deactivated
–路由组件失活时触发
路由守卫
-
作用
对路由进行权限控制
-
分类
全局守卫、独享守卫、组件内守卫
-
全局路由守卫
<Script> // 全局前置路由守卫————初始化时候被调用、每次路由切换之前被调用 router.beforeEach((to, from, next) => { console.log('前置路由守卫', to, from); if (to.meta.isAuth) { // 判断是否需要鉴权 if (localStorage.getItem('school') === 'atguigu') { // 权限控制的具体规则 next() // 放行 } else { alert('暂无权查看!') } } else { next() // 放行 } }); // 全局后置路由守卫————初始化时候被调用、每次路由切换之后被调用 router.afterEach((to, from) => { console.log('后置路由守卫', to, from); document.title = to.meta.title || "vue系统" // 修改网页title }) </Script>
-
独享路由后卫
<script> beforeEnter: (to, from, next) => { console.log('前置路由守卫', to, from); if (to.meta.isAuth) { // 判断是否需要鉴权 if (localStorage.getItem('school') === 'atguigu') { next() } else { alert('学校名不对,无权查看!') } } else { next() } } </script>
-
组件内路由守卫
<script> // 进入组件路由守卫,通过路由规则,进入该组件时被调用 beforeRouteEnter(to, from, next) { } // 离开组件路由守卫,通过路由规则,离开该组件时被调用 beforeRouteLeave(to, from, next) { } </script>
路由器的两种工作模式
-
hash值
对于一个URL来说,hash值是指
#及其后面的内容是hash值
hash值不会包含在HTTP请求中,即:hash值不会带给服务器
-
hash模式
- 地址中永远带着
#
,不美观 - 若后期将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
- 地址中永远带着
-
history模式
- 地址干净,美观
- 兼容性和hash模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务器404问题
UI组件库
移动端UI组件库
- Vant–Vant 3 - 轻量、可靠的移动端组件库 (youzan.github.io)
- Cube UI–cube-ui Document (didi.github.io)
- Mint UI–Mint UI (mint-ui.github.io)
PC端UI组件库
- Element UI–Element - 网站快速成型工具
- IView UI–iView - A high quality UI Toolkit based on Vue.js (iviewui.com)
扩展
-
Object.defineProperty()方法
-
关于不同版本的Vue
-
vue.js与vue.runtime.xxx.js区别
vue.js是完整版的Vue,包含:核心功能+模板解析器
vue.runtime.xxx.js是运行版的Vue,只包括:核心功能,没有模板解析器
vue.runtime.xxx.js没有模板解析器,不能使用template配置项,需要使用render函数接收到createElement函数去指定具体内容。
-
-
关于vue.config.js配置文件
- 使用
vue inspect > output.js
可以查看Vue脚手架的默认配置 - 编写
vue config.js
文件可以对脚手架进行个性化定制,详情参考:Vue CLI 配置参考
- 使用