⭐️Vue
vue3基础入门参考文章!必看
Vue 2
是一套用于构建用户界面的框架
Vue 的特性
- 数据驱动视图
- 双向数据绑定
- MVVM- MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分 - Model 表示当前页面渲染时所依赖的数据源。- View 表示当前页面所渲染的 DOM 结构。- ViewModel 表示 vue 的实例,它是 MVVM 的核心。
- 数据代理> 通过vm对象来代理data对象中属性的操作(读/写)- 更加方便的操作data中的数据- 基本原理- 通过Object.defineProperty()把data对象中所有属性添加到vm上。- 为每一个添加到vm上的属性,都指定一个getter/setter。- 在getter/setter内部去操作(读/写)data中对应的属性。
Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
2. 如何监测对象中的数据?
通过setter实现监视,且要在newVue时就传入要监测的数据。(1).对象中后追加的属性,Vue默认不做响应式处理(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)3. 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:(1).调用原生对应的方法对数组进行更新。(2).重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
1 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()2 Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
Vue 的基本使用
- 使用步骤- 1 导入 vue.js 的script 脚本文件- 2 在页面中声明一个将被 Vue 所控制的 DOM 区域- 3 创建 vm 实例对象
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app">{{ username }}</div><!--1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!--2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm =newVue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el:'#app',// data 对象就是要渲染到页面上的数据data:{username:'zhangsan'}})</script>
指令
指令 是 Vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构
按照不同用途可分为:
1 内容渲染指令
v-text
- 示例:<p v-text="gender">性别:</p>
- ❗️v-text 指令会覆盖
元素内默认的值- ⭐️
{{ }}
-<p>性别:{{ gender }}</p>
- 不会覆盖元素内默认的值 v-html
- 把包含 HTML 标签的字符串渲染为页面的 HTML 元素-<div v-html="info"></div>
- ❗️v-html有安全性问题- (1).在网站上动态渲染任意HTML是非常危险的,容易导致 XSS 攻击。- (2).一定要在可信的内容上使用v-html,不要用在用户提交的内容上!(和eval()有点像)v-cloak
- 1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。- 2.使用css(display:'none'
)配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。v-once
- 1.v-once所在节点在初次动态渲染后,就视为静态内容了。- 2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。v-pre
- 1.跳过其所在节点的编译过程。- 2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
2 属性绑定
- v-bind:- v-bind: 指令可以简写为
:
-<input type="text" :placeholder="tips">
-<img :src="photo" alt="">
-<div title="'box'+index"></div>
- 动态绑定 class / style / checked-:checked="isChecked"
-isChekced
是 Boolean值- 字符串:- 适用于:类名不确定,要动态获取。-:class="classes"
- classes是一个计算属性- 对象语法:- 要绑定多个样式,个数确定,名字也确定,但不确定用不用-:class="{active:isActive}"
-:class="{active:isActive,line:isLine}"
-class="title":class="{active:isActive,line:isLine}"
- 数组语法:- 要绑定多个样式,个数不确定,名字也不确定。-:style='[styleobj,overridingStyles]'
使用 JavaScript 表达式 的运算
3 事件绑定
- v-on :简写形式:@- eg: @click=‘addCount’ @keyup=‘count += 1’
- 需要在 methods 节点中进行声明
- 事件参数对象- $(event) 指原生的事件参数对象 event- 绑定事件并
传参``````<button @click="add($event, 1)">+N</button>
- 事件修饰符事件修饰符说明.prevent阻止默认行为(eg:阻止 a 链接的跳转、阻止表单的提交等).stop阻止事件冒泡.capture以捕获模式触发当前的事件处理函数.once绑定的事件只触发一次.self只有在 event.target 是当前元素自身时触发事件处理函数-
<a href="http://www.baidu.com" @click.prevent="show">跳转到百度首页</a>
- 按键修饰符-
<input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
-.enter
-.delete
-.esc
-.space
-.up``````.down``````.left``````.right
-tab
换行 (必须配合 keydown去使用) - 可以执行少量代码
count++
- 可以写函数传参(可以获取事件对象,没写参数默认有,写参数要
(a,$event)
- 给调用函数+个()eg:@change="handelClick()"
- 如果只有一个 e 不加 ()-e.target.value/checkded
4 双向绑定
- v-model- 实现
表单
与数据
的双向绑定- 用于获取表单
(输入类)的数据- 场景- 表单(value / checked)、全选(状态在 computed )反选(状态在 data )-收集表单数据: 若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。 若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。 若:<input type="checkbox"/>1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值) 2.配置input的value属性:(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)(2)v-model的初始值是数组,那么收集的的就是value组成的数组(多选)
- v-model 修饰符修饰符作用示例.number自动把用户输入的值转为数值类型<input type="number" v-model.number="age">
.trim自动过滤用户输入 的首尾空白字符<input v-model.number="msg">
.lazy在“change”时而非“input”时更新<input v-model.number="msg">
5 条件渲染
- 条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏-
v-if
-<p v-if="flag">这是被 v-if 控制的元素</p>
- 会动态地创建或移除 DOM 元素-v-else
、v-else-if=“ ”
- 必须相邻- 只能在与 v-if 平级的时候使用 优秀 良好 一般 差 - 移除时 dom 不存在-v-show
-<p v-show="flag">这是被 v-show 控制的元素</p>
- 会动态为元素添加或移除 style=“display: none;” 样式- 移除时 dom 存在v-ifv-show动态地创建或移除 DOM 元素动态为元素添加或移除 style="display: none;"样式支持多条件显示不支持多条件显示有更高的开销有更高的初始渲染开销使用场景:切换频率较低、判断条件较多的场景使用场景:非常频繁地切换
6 列表渲染
- v-for- 基于一个数组来循环渲染一个列表结构- 需要使用 item in items 形式的特殊语法
<tr v-for="(item, index) in list":key="item.id"><td>{{ index }}</td><td>{{ item.id }}</td><td>{{ item.name }}</td></tr>
- 1 用于 循环 数组(item,index) in array
- 2 用于循环 对象(item,key) in obj
- 3 用于循环 数字item in num
- 使用 key 维护列表的状态 - Vue 复用已存在的DOM元素提升渲染性能 但导致有状态的列表无法被正确更新- key 注意事项 - key 的值只能是字符串或数字类型- key 的值必须 具有唯一性- 建议把数据项的 id 属性值作为 key 的值- 使用 index 的值当做 key 的值没有意义( index 不具唯一性)- 建议使用 v-for 指令时要指定 key 的值(既提升性能,又防止列表状态紊乱)- label 的 for 属性 -:for="'cb' + item.id"
input里面:id="'cb' + item.id"
过滤器
过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。本质是 js 函数
- 过滤器放在 js 表达式的尾部 由“管道符”进行调用- 插值表达式 -
<p>message 的值是:{{ message | capi }}</p>
- 不能给属性用,直接“ ”- v-bind 属性绑定 -<div v-bind:id="rawId | formatId"></div>
- 定义过滤器- 在创建 vue 实例期间,可以在 filters 节点中定义过滤器
filters:{// 注意:过滤器函数形参中的 val,永远都是“管道符”前面的那个值capi(val){// 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来// val.charAt(0)const first = val.charAt(0).toUpperCase()// 字符串的 slice 方法,可以截取字符串,从指定索引往后截取const other = val.slice(1)// 强调:过滤器中,一定要有一个返回值return first + other }}
- 私有过滤器> 在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。- 全局过滤器// 使用 Vue.filter() 定义全局过滤器 Vue.filter('capi',function(str){const first = str.charAt(0).toUpperCase()const other = str.slice(1)return first + other +'~~~'})
- 连续调用多个过滤器-
<p>message 的值是:{{ message | capi | maxLength }}</p>
- 过滤器传参- 本质是 js 函数
<p>{{ message |filterA(arg1,arg2)}</p>Vue.filter('filterA',(mesg,arg1,arg2)=>{})
- 兼容性- 仅在 Vue1、2中受支持- vue3 不支持 - 官方建议使用 计算属性 或 方法 代替过滤器功能- 参考
侦听器
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。
当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 1 在 watch 节点进行声明
const vm =newVue({el:'#app',data:{username:'admin'},// 所有的侦听器,都应该被定义到 watch 节点下watch:{// 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可// 新值在前,旧值在后username(newVal){if(newVal ==='')return// 1. 调用 jQuery 中的 Ajax 发起请求,判断 newVal 是否被占用!!!
$.get('https://www.escook.cn/api/finduser/'+ newVal,function(result){
console.log(result)})}}})
- 2 使用 watch 检测用户名是否可用> 监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:
watch:{// 监听 username 值的变化asyncusername(newVal){if(newVal ==='')return// 使用 axios 发起请求,判断用户名是否可用const{data: res }=await axios.get('https://www.escook.cn/api/finduser/'+ newVal)}}
- 3 immediate选项> 默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使 用 immediate 选项。
watch:{// 让被监听的对象指向一个 配置对象username:{// handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数handler:asyncfunction(newVal){if(newVal ==='')returnconst{data: res }=await axios.get('https://www.escook.cn/api/finduser/'+ newVal) console.log(res)},// 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器immediate:true}}
- 4 deep 选项> 如果 watch 侦听的是一个>
> 对象>
> ,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项- 在上面的基础上加一个deep:true
- 5 监听对象单个属性的变化
watch:{info:{handler(newVal){ console.log(newVal)},deep:true,}// 如果要侦听的是 子属性 的变换,则必须包裹一层单引号'info.username'(newVal){ console.log(newVal)}// 配置对象'info.username':{asynchandler(newVal){const{data:res }=await axios.get('https://www.escook.cn/api/finduser/'+ newVal.username) console.log(res)}}}
监视的属性必须存在,才能进行监视!!
- 应用场景- 本地存储
subjectList(){// 要侦听的属性 localStorage.setItem('scoreMsg',JSON.stringify(this.subjectList))}
- 当监听 对象数组 时,数组的长度变化时,不用 deep 也可以,但是监听不到对象内部的 变化- 数据变化时,发起 ajax 请求
计算属性
计算属性指的是通过一系列运算之后,最终得到一个属性值
- 个动态计算出来的属性值可以被模板结构或 methods 方法使用。
<div id="app"><!-- 专门用户呈现颜色的 div 盒子 --><!-- 在属性身上,: 代表 v-bind: 属性绑定 --><!--:style 代表动态绑定一个样式对象,它的值是一个 {} 样式对象 --><!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 --><div class="box":style="{ backgroundColor: rgb }">{{ rgb }}</div><button @click="show">按钮</button></div><script>// 创建 Vue 实例,得到 ViewModelvar vm =newVue({el:'#app',data:{// 红色r:0,// 绿色g:0,// 蓝色b:0},methods:{// 点击按钮,在终端显示最新的颜色show(){ console.log(this.rgb)}},// 所有的计算属性,都要定义到 computed 节点之下// 计算属性在定义的时候,要定义成“方法格式”computed:{// rgb 作为一个计算属性,被定义成了方法格式,// 最终,在这个方法中,要返回一个生成好的 rgb(x,x,x) 的字符串rgb(){return`rgb(${this.r}, ${this.g}, ${this.b})`}}}); console.log(vm)</script>
- 所有的计算属性,都要定义到 computed 节点之下- 计算属性在定义的时候,要定义成“方法格式” - 计算属性的特点- 1 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性- 2 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算
- 好处:- 代码复用- data变化,计算属性也变化
- 应用场景- 反选 -
return this.list.every(item => item.checked === true)
- total -return this.subjectList.reduce((pre, current) => (pre += current.score), 0)
全选:v-model="allChecked" 反选:computed
computed:{allChecked:{get(){returnthis.list.every(item=> item.checked ===true)},set(allChecked){this.checked =!allChecked
this.list.forEach(item=>(item.checked = allChecked))}}}
计算属性:
1.定义:要用的属性不存在,要通过`已有属性`计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?(1).初次读取时会执行一次。(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
计算属性 vs 侦听器
1 计算属性侧重于
监听多个值的变化
,最终计算并
返回
一个新值
2 侦听器侧重于监听
单个
数据的变化,最终执行
特定的业务处理
,不需要有任何返回值
3 computed能完成的功能,watch都可以完成。watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
axios
axios 是一个专注于网络请求的库
- 调用 axios 方法得到的返回值是 Promise对象
const result =axios({method:'GET',url:'',// URL 中的查询参数 GET// params:{},// 请求体参数 POST// data:{}})result.then(res=>{ console.log(res.data)})$('#btnPost').on('click',async()=>{const{data:res }=awaitaxios({ method:'POST',url:'',data:{name:'zs',age:20}})// 返回的是数据对象 可以解构单独拿出 data 把 data 重命名为 res})
如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await
await 只能在被 async “修饰”的方法中
用 jQuery 可以发起
$.ajax()
$.get()
$.post()
请求
axios:
axios() axios.get() axios.post() axios.delete() axios.put() axios.patch()
axios.get()
$('#btnPost').on('click',async()=>{const{data:res }=await axios.get('url',{params:{id:1}})})
axios.post()
$('#btnPost').on('click',async()=>{const{data:res }=await axios.post('url',{name:'zs',gender:'女'})// axios.post()里面的请求体直接写数据对象})
组件中发起axios 请求,不用每个组件都要导入 axios,在 main.js 导入,变成 Vue内置的成员
// main.jsimport axios from'axios'// 配置请求根路径
axios.defaults.baseURL ='http://www.itcbc.com:3006'// 把 axios 挂载到 Vue.prototype上,供每个组件的实例直接使用// 缺点: 不利于接口的 复用
Vue.propotype.$http = axios
// 组件中methods:{asyncgetBooks(){const{data: res}=awaitthis.$http.get('http://www.itcbc.com:3006/api/getbooks')}}
$mount方法
const vm =newVue({data:{username:'admin'}})
vm.$mount('#app')
vue/cli
vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程
下载:
npm i -g @vue/cli
查找安装:
vue -V
- 在指定目录的 终端下 创建指定名称的项目-
vue create demo-first
- 项目名称不能有空格、中文、大写字母- 创建冻结 按 ctrl + c- 创建成功cd 项目名称``````npm run serve
不要关掉终端 - vue 项目中 src 目录的构成
- assets 放:图片、css 样式等静态资源
- components :放封装好的 组件
- main.js :是项目的入口文件,整个项目的运行,要先执行 main.js
![在这里插入图片描述](https://img-blog.csdnimg.cn/721187076eeb40748414a0f0fa998f01.png#pic_center)
- app.vue:是项目的根组件(render渲染的组件就是根组件)
- vue 项目运行流程- 通过 main.js 把 App.vue 渲染到 HTML 页面
单页面应用程序
单页面应用程序(英文名:Single Page Application)简称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。
vue组件
组件是对 UI 结构的复用
组件化开发
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。
组件的后缀名是 .vue
一个重要的内置关系
- VueComponent.prototype.proto === Vue.prototype
为什么要有这个关系
- 让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
this.$refs.xx(ref=xx).属性/方法()
定义组件
组成部分:
- 1 template -> 组件的模板结构(必须包含)-
<template> 当前组件的 DOM 结构,需要被定义到 template 标签的内部 </template>
- template 中只能包含唯一的根节点 - 2 script -> 组件的 JavaScript 行为> 在
组件配置中: data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】
new Vue(options)配置中: data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
使用组件
- 1 使用 import 语法导入需要的组件 - @ 表示定位到 src-
import MyCount from 'xxx'
- 2 使用 components 节点注册组件
- 3 以标签的形式使用刚才注册的组件
组件间的父子关系
- 组件封装之后,彼此之间是相互独立的,不存在父子关系
- 在使用组件的时候,根据彼此的嵌套关系构成 父子、兄弟关系
私有组件
- 使用 components 注册的是 私有组件
- 在组件 A 的 components 节点下,注册了组件 F。 则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
component:{ MyCount }
- ⭐️
component:{ 'my-count', MyCount }
注册全局组件
- 在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。 - 1
import
- 2Vue.component('MyCount',MyCount)
组件的 name 会显示在 devtools 上
组件的 props
props 是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性
exports default{props:['自定义属性A','自定义属性B','...'],data(){return{}}}
- props 是只读的- 修改:把 props 的值 转存 到 data 中,data 中的数据都是可读写的- 不要直接在 子组件里修改 props 的值 (会报错,父组件没跟着变)
props:['init'],data(){return{count:this.init }}
- props 的 default 默认值-
props:{ init: { default: 0 }}
- 用户没传 init 的值时,default 的值生效- 对象或数组默认值必须从一个工厂函数获取default:function() { return {message:'hello'}}
- type 值类型- eg:
type: Number
-:init="9"
v-bind: 加上 js 的数字- 写在 props 里面-props:{name:String}
- required 必填项-
required: true
使用:
<my-count :init="9"></my-count>
组件之间的样式冲突问题
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
- 根本原因- 1 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的- 2 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
- 解决- 1 为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域
<style>.container[data-v-0001]{border: 1px solid red;}</style>
- 2 style 的 scoped 属性- 防止组件之间的样式冲突问题-<style scoped> </style>
- 则当前组件的样式对其子组件是不生效的- 3 /deep/ 样式穿透- 让某些样式对子组件生效 - 当使用第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要用到 /deep/
组件的生命周期
生命周期
生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
生命周期函数
是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行,强调的是时间点
- 组件生命周期函数的分类- 组件创建阶段、组件运行阶段- 生命周期图示
- beforeCreate()- 创建阶段的第一个生命周期函数- 组件的 props/data/methods 尚未被创建,都处于 不可用 状态
- ⭐️created()- 组件的 props/data/methods 已创建好,都处于 可用 状态,但是组件的模板结构未生成- 在里面调用 methods 方法,请求服务器的数据,并且,把
请求
到的数据,转存到 data 中,供 template 使用- 有些bus.$on()
写在 created 里面 - beforeMount- 浏览器还没当前组件的 DOM结构
- ⭐️mounted- 已渲染 HTML,第一次取到 DOM 结构-
启动定时器
、绑定自定义事件
、订阅消息
等【初始化操作】 - beforeUpdate- 将要 根据变化、更新后的数据,重新渲染组件的模板结构
- ⭐️updated- 已根据最新的数据,完成了组件 DOM 结构 的重新渲染- 当数据变化之后,为了能够操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中- 但是一般不在这里做什么,因为一个属性变化就会触发 updated
- beforeDestroy- 尚未销毁组件,还处于 正常工作状态-
清除定时器
、解绑自定义事件
、取消订阅消息
等【收尾工作】 - destroyed- 组件已被销毁,DOM 结构已被完全移除- 销毁后自定义事件会失效,但原生DOM事件依然有效。- 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
⭐️组件之间的数据共享
1 父组件向子组件共享数据
- 需要使用自定义属性
- 也就是 使用自定义属性的方法
2 子组件向父组件共享数据
- 使用自定义事件(以传参的形式 去共享数据)
- 父:函数 - 函数接收数据
- 子:props:[‘函数名称’] - 调用函数,传递参数数据
3 兄弟组件之间的数据共享
- 在 vue2 中,兄弟组件之间的数据共享的方案是 EventBus
- EventBus 的使用步骤- 1 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象- 2 在数据发送方,调用
bus.**$emit(**'事件名称', 要发送的数据)
方法触发自定义事件- 3 在数据接收方,调用bus.**$on**('事件名称', 事件处理函数)
方法注册一个自定义事件-bus.$off('xxx')
解绑
4 ⭐️全局事件总线:任意组件间通信
main.js
- 在vue 实例beforeCreate(){ Vue.prototype.$bus = this}
- 发送方-
this.$bus.$emit('deleteTodo', 参数)
- 接收方
mounted(){this.$bus.$on('checkTodo',this.checkTodo)this.$bus.$on('deleteTodo',this.deleteTodo)},beforeDestroy(){this.$bus.$off('checkTodo')this.$bus.$off('deleteTodo')}
5 消息订阅与发布 实现任意组件间通讯
使用步骤:
- 安装pubsub:
npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){demo(data){......}=》或者写在回调函数里}......mounted(){this.pid = pubsub.subscribe('xxx',this.demo)//订阅消息 (msgName,data)=>{...} 写箭头函数}beforeDestroy(){ pubsub.unsunscribe(this.pid)}
- 提供数据:
pubsub.publish('xxx',数据)
- 最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。
ref 引用
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。
默认情况下, 组件的 this.$refs 指向一个空对象。
- 使用 ref 引用 DOM 元素
- 使用 ref 引用组件实例
ref=' '
写在 组件使用标签上- 引用到组件的实例后,就可以调用组件上的 methods 方法
- 控制文本框和按钮的按需切换> 通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。
- 让文本框自动获得焦点> 当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可。
- this.$nextTick(cb) 方法> 组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。> 即等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。>
@ 从 src 源目录从外往里找
❗️一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
动态组件
动态组件指的是动态切换组件的显示与隐藏。
动态组件的渲染
vue 提供了一个内置的
<component>
组件,专门用来实现动态组件的渲染。
data(){// 函数 避免组件被复用时,数据存在引用关系。return{comName:'Left'}}<componet :is="comName"></componet><button @click="comName = 'Left'">切换 Left 组件</button><button @click="comName = 'Right'">切换 Right 组件</button>
使用 内置的 keep-alive 保持状态
解决默认情况下,切换动态组件时无法保持组件的状态的问题。
<keep-alive><component :is="comName"></component></keep-alive>
keep-alive 对应的生命周期函数
- 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
- 当组件被激活时,会自动触发组件的 activated 生命周期函数。 - 当组件第一次被创建是,即会触发created 也会触发 actived- 当组件被激活,只会触发 actived,不再触发 created,因为组件没有被重新创建
keep-alive 的 include 属性
include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:
- 与 include 相对的是 exclude(指定哪些组件不需要被缓存)只能二选一
<keep-alive include="MyLeft,MyRight"><component :is="comName"></component></keep-alive>
组件的name属性:
当提供了 name 属性之后,组件的名称,就是 name 属性的值
exportdefault{name:'MyRight'}
对比:
- name - 1 调试的时候 出现的 组件名称- 2 与
<keep-alive>
结合 指定被缓存与不被缓存 - 注册名称 - 应用场景:以标签的形式,把注册好的组件,渲染和使用到页面结构之中
mixin(混入)
可以把多个组件共用的配置提取成一个混入对象
独立的 js 文件
使用:
1 定义混合
exportconst xxx ={data(){...}methods(){...}...}exportconst yyy ={data(){...}methods(){...}...}
2 使用混合
- 全局混入 - 在 main.js 中
Vue.mixin(xxx)
- 局部混入 // App.vue or 某一组件中 -
import {xxx,yyy} from '/mixin'
-mixins:[xxx,yyy]
插件
独立的 js 文件
// 1 定义插件 plugins.jsexportdefault{install(Vue,x,y,z)// 1. 添加全局过滤器
Vue.filter('mySlice',function(value){return value.sclice(0,4)})// 2. 添加全局指令
Vue.directive('fbind',{bind(el,binding){ el.value = binding.value},insert(el,binding){el.focus()},update(el,binding){el.value = binding.value}})// 3. 配置全局混入(合)
Vue.mixin({data(){return{x:100,y:200}}})// 4. 添加实例方法Vue.prototype.$myMethod=function(){...}Vue.prototype.$myProperty = xxxx
// 5. 添加全局组件
Vue.component('myButton',{})}// 2 引入插件 main.jsimport myBtn from'@/plugin/plugins.js'// 3 使用自定义插件<my-button/>
插槽
插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
基础用法:
在
<template>
中 通过
<slot></slot>
,为用户预留内容占位符
<my-com-1> <p>用户自定义的内容 </p> </my-com-1>
- 默认情况下,在使用组件时,提供的内容会被填充到名字为
default
的插槽中
没有预留插槽的内容会被丢弃
后备内容
即
<slot>
标签里面的 默认内容
具名插槽
如果在封装组件时需要预留多个插槽节点,则需要为每个 插槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。
<slot name="header"></slot>
如果省略了 slot 的 name 属性就是
<slot name="default"></slot>
为具名插槽提供内容
在向具名插槽提供内容的时候,我们可以在一个 元素上使用
v-slot
指令,并以 v-slot 的参数的形式提供其名称。
v-slot:header
只能加给
<template>
只起到包裹作用
<my-com-2><template v-slot:header><h1>ittle<h1></template><template v-slot:default><h1>ittle<h1></template></my-com-2>
- 具名插槽的简写形式-
v-slot:
=>#
- 外面没有 template包裹只能写slot="slotName"
作用域插槽
在封装组件的过程中,可以为预留的 插槽绑定 props 数据,这种带有 props 数据的 叫做“作用域插槽”。
组件(用
slot
传)传数据给插槽使用者(用
template
接收)
插槽使用者接收
- 匿名插槽 -
<template scope="xx{games}">
- 只能有一个 - 具名插槽 -
<tempalte #header="{msg,user}">
<slot v-for="item in list" :user="item" msg="hello"></slot>
使用作用域插槽
可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据。
<template #header="scope"> {{scope}} // 使用作用域插槽的数据 </template>
直接接受一个对象 scope
解构插槽 Prop
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。(子传父)
<template #author="{msg,user}"><h3>一行</h3><p>{{msg}}</p><p>{{user.name}}</p></template>
自定义指令
私有自定义指令
当指令第一次被绑定到元素上的时候,会立即触发 bind()
1 在 directives 节点下声明私有自定义指令。
directives:{color:{bind(el){// el 是绑定了此指令的、原生的 DOM 对象
el.style.color ='red'}}}
2 使用指令
<h1 v-color> APP 组件 </h1>
为自定义指令 动态绑定参数值
data(){return{color:'red'}}<h1 v-color="color">App 组件</h1>
通过 binding 获取指令的参数值
在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值:
directives:{color:{bind(el,binding){// 通过 binging 对象的 .value 属性,获取动态的参数值// 标签上写 v-color="'red'"/ v-color="color" 都可
el.style.color = binding.value
}}}
update 函数
bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函数会在每次 DOM 更新时被调用。
directives:{color:{// 当指令第一次被绑定到元素时被调用(必写)bind(el,binding){// 通过 binging 对象的 .value 属性,获取动态的参数值
el.style.color = binding.value
},// 每次 DOM 更新时被调用update(el,binding){
el.style.color = binding.value
}}}
函数简写
如果 bind 和 update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:
directives:{color(el,binding){// 'big-number'(el,binding)
el.style.color = binding.value
}}}
全局自定义指令
全局共享的自定义指令需要通过“Vue.directive()”进行声明
Vue.directive('color',function(el,binding){
el.style.color = binding.value
})
//定义全局指令
Vue.directive('fbind',{//指令与元素成功绑定时(一上来)bind(element,binding){
element.value = binding.value
},//指令所在元素被插入页面时inserted(element,binding){
element.focus()},//指令所在的模板被重新解析时update(element,binding){
element.value = binding.value
}})newVue({el:'#root',data:{name:'尚硅谷',n:1},directives:{//big 函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。/* 'big-number'(element,binding){
// console.log('big')
element.innerText = binding.value * 10
}, */big(element,binding){
console.log('big',this)//注意此处的 this 是 window// console.log('big')
element.innerText = binding.value *10},fbind:{//指令与元素成功绑定时(一上来)bind(element,binding){// 此处的 this 为 window
element.value = binding.value
},//指令所在元素被插入页面时inserted(element,binding){
element.focus()},//指令所在的模板被重新解析时update(element,binding){
element.value = binding.value
}}}})
参数:
el:是绑定了此指令的、原生的 DOM 对象
bing: 指令核心对象,描述指令全部信息属性。通过 bing 对象的 .value 属性,获取动态的参数值 {value}= bing
name:指令名
value:指令的绑定值
oldValue:指令绑定的前一个值,仅在 update和 componentUpdated钩子中可用。
expression:绑定值的字符串形式。
arg:传给指令的参数
modifers:modifiers:一个包含修饰符的对象。
vnode 虚拟节点
oldVnode:上一个虚拟节点(更新钩子函数中才有用)
ESLint
约束代码风格,可组装的JavaScript 和 JSX检查工具
ctrl + F 可以 查找规则
路由1
前端路由的概念与原理
路由(英文:router)就是对应关系。
SPA 与前端路由
在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成
前端路由
Hash 地址与组件之间的对应关系。
前端路由:key是路径,value是组件。
锚链接 #
- 都属于Hash地址
<a href="#b1">b1</a>``````<div id="b1"></div>
前端路由的工作方式
① 用户点击了页面上的路由链接
② 导致了 URL 地址栏中的 Hash 值发生了变化
③ 前端路由监听了到 Hash 地址的变化
④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
实现简易的前端路由
1 通过
<component>
标签,结合 comName 动态渲染组件
<component :is="comName"></component>exportdefault{name:'App',data(){return{comName:'Home'}}}
2 在 App.vue 组件中,为 链接添加对应的 hash 值:
<a href="#/home">Home</a><a href="#/movie">Home</a><a href="#/about">Home</a>
3 在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:
created(){
window.onhashchange=()=>{switch(location.hash){case'#home':// 点击了首页链接this.comName ='Home'breakcase'#movie':this.comName ='Moive'breakcase'#about':this.comName ='About'break}}}
vue-router
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
vue-router 安装和配置的步骤
① 安装 vue-router 包
- npm i vue-router@3.5.2 -S
② 创建路由模块
- 在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码:
import Vue from'Vue'import VueRouter from'vue-router'// 调用 Vue.use() 函数,把 VueRouter 安装为 Vue 插件Vue.use(VueRouter)const router =newVueRouter()exportdefault router
③ 导入并挂载路由模块
- 在 src/main.js 入口文件中,导入并挂载路由模块。
import Vue from'vue'import App from'./App.vue'// 1 导入路由模块// 在进行模块化导入时,如果给定的是文件夹,则默认导入这个文件夹,名字叫做 index.js 的文件import router from'@/router'newVue({render:h=>h(App),// 2 挂载路由模块router:router}).$mount('#app')
④ 声明路由链接和占位符
- 在 src/App.vue 组件中,使用 vue-router 提供的
<router-link>
和<router-view>
声明路由链接和占位符:
<template>
<div class='app-container'>
<h1> App 组件 </h1>
// 定义路由链接
<router-link to="/home">首页</router-link>
<router-link to="/movie">电影</router-link>
<router-link to="/about">关于</router-link>
<hr>
// 定义路由的占位符
<router-view></router-view>
</template>
声明路由的匹配规则
- 在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。
// 导入需要使用路由切换展示的组件import Home from'@/components/Home.vue'import Home from'@/components/Movie.vue'import Home from'@/components/About.vue'// 创建路由的实例对象const router =newVueRouter({routers:[// 在 routes 数组中,声明路由的匹配规则// 路由规则// path 表示要匹配的 hash 地址;component表示要展示的路由组件{path:'/home',component:Home},{path:'/movie',component:Movie},{path:'/about',component:About}]})
vue-router 的常见用法
路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
const router =newVueRouter({routers:[// 当用户访问 / 时,通过 redirect 属性跳转到 /home 对应的路由规则 {path:'/',redirect:'/home'}{path:'/home',component:Home},{path:'/movie',component:Movie},{path:'/about',component:About}]})
嵌套路由
通过路由实现组件的嵌套展示,叫做嵌套路由。
- 点击父级路由链接显示模板内容
1 声明子路由链接和子路由占位符
- 在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。
<template><div class="about-container"><h3>About 组件</h3>// 在关于页面中,声明两个子路由链接<router-link to="/about/tab1">tab1</router-link><router-link to="/about/tab2">tab2</router-link><hr/><router-view></router-view></template>
2 通过 children 属性声明子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则
import Tab1 from'@/component/tabs/Tab1.vue'import Tab1 from'@/component/tabs/Tab2.vue'const router =newVueRouter({routers:[{// about 页面的路由规则(父级路由规则)path:'/about',component:About,children:[// 通过children 属性,嵌套声明子级路由规则{path:'tab1',component:Tab1},// 访问 /about/tab1{path:'tab2',component:Tab2}]}]})
动态路由匹配
动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。
< path:'/movie/:id',component:Movie
$route.params 路由参数对象
在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值。
在 <template>中使用 <h3>{{this.$route.params.id}}
在 <script> 中写 exportdefault{name:'Movie'}
路由的query参数
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --><router-link :to="`/about/message/detail?id=${item.id}&title=${item.title}`">{{ item.title }}</router-link><!-- 跳转并携带query参数,to的对象写法 --><router-link
:to="{path:'/home/message/detail',query:{id:666,title:'你好'}}"
>跳转</router-link>
接收查询参数:
$route.query.id
$route.query.title
为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参。
< path:'/movie/:id',component:Movie, props:true
在 Movie 组件中写 props:[‘id’]
直接使用 props 中接收的路由参数
<h3>{{ id }}</h3>
或者
$route.params.id
注意:
1 ‘/’ 后面的参数叫做 路径参数
- 使用
$route.params.id
访问 路径参数
2 ‘?’ 后面叫查询参数
- 使用
$route.query.z
访问 查询参数
3 在 this.$route 中 path 只是路径的一部分, fullPath 是完整的地址
4 params 与 query 的区别
- params传参:是在内存中传参,刷新会丢失
- query传参:是在地址栏传参,刷新还在
声明式导航 & 编程式导航
在浏览器中,点击链接实现导航的方式,叫做声明式导航。
- 普通网页中点击 链接、vue 项目中点击 都属于声明式导航
在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。
- 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
vue-router 中的 编程式导航 API
① this.$router.push(‘hash 地址’)
- 跳转到指定 hash 地址,并增加一条历史记录,展示对应的组件页面。
exportdefault{mehtods:{getoMovie(){this.$router.push('/movie/1')}}}
② this.$router.replace(‘hash 地址’)
- 跳转到指定的 hash 地址,不会增加历史记录并替换掉当前的历史记录
③ this.$router.go(数值 n)
- 实现导航历史前进、后退
exportdefault{props:['id'],methods:{goBack(){this.$router.go(-1)}}}
- 如果后退的层数超过上限,则原地不动
- $router.go 的简化用法- ① $router.back()- ② $router.forward()
导航守卫
导航守卫可以控制路由的访问权限。
全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制:
const router =newVue({...})
router.beforeEach(fn)
守卫方法的 3 个形参
const router =newVue({...})
router.beforeEach((to,from,next)=>{// to 是将要访问的路由的信息对象// from 是将要离开的路由的信息对象// next 是一个函数,调用 next() 表示放行,允许这次路由导航})
next 函数的 3 种调用方式
控制后台主页的访问权限
router.beforeEach((to,from,next))=>{if(to.path ==='/main'){const token = localStorage.getItem('token')if(token){next()// 访问的是后台主页,且有 token 的值}else{next('/login')}}else{next()// 访问的不是后台主页,直接发行}}
路由2
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
1.基本使用
- 安装vue-router,命令:
npm i vue-router
- 应用插件:
Vue.use(VueRouter)
- 编写router配置项:
// router/index.js//引入VueRouterimport VueRouter from'vue-router'//引入Luyou 组件import About from'../components/About'import Home from'../components/Home'//创建router实例对象,去管理一组一组的路由规则const router =newVueRouter({routes:[{path:'/about',component:About },{path:'/home',component:Home }]})//暴露routerexportdefault router
- 实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
- 指定展示位置
<router-view></router-view>
2.几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
3.多级路由(多级路由)
- 配置路由规则,使用children配置项:
// router/index.jsroutes:[{path:'/about',component:About,},{path:'/home',component:Home,children:[//通过children配置子级路由{path:'news',//此处一定不要写:/newscomponent:News },{path:'message',//此处一定不要写:/messagecomponent:Message }]}]
- 跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
4.路由的query参数
- 传递参数
// Message.vue<!-- 跳转并携带query参数,to的字符串写法 --><router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> // 页面中不需要 <!-- 跳转并携带query参数,to的对象写法 --><router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }">跳转</router-link>
- 接收参数:
// Detail.vue$route.query.id$route.query.title
5.命名路由
- 作用:可以简化路由的跳转。
- 如何使用1. 给路由命名:
{path:'/demo',component:Demo,children:[{path:'test',component:Test,children:[{name:'hello'//给路由命名path:'welcome',component:Hello,}]}]}
2. 简化跳转:<!--简化前,需要写完整的路径 --><router-link to="/demo/test/welcome">跳转</router-link><!--简化后,直接通过名字跳转 --><router-link :to="{name:'hello'}">跳转</router-link><!--简化写法配合传递参数 --><router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }">跳转</router-link>
6.路由的params参数
- 配置路由,声明接收params参数
:
占位符- 页面必需的参数,使用内嵌参数 params
// router/index.js{path:'/home',component:Home,children:[{path:'news',component:News },{component:Message,children:[{name:'xiangqing',path:'detail/:id/:title',//使用占位符声明接收params参数component:Detail }]}]}
- 传递参数
<!-- 跳转并携带params参数,to的字符串写法 --><router-link :to="`/home/message/detail/${{item.id}}/${{item.title}}`">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --><router-link :to="{ name:'xiangqing', //params to的对象写法不能写 path name是路由的名字 params:{ id:666, title:'你好' } }">跳转</router-link>
> 特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置! - 接收参数:
// Detail.vue$route.params.id$route.params.title
7.路由的props配置
作用:让路由组件更方便的
收到参数
// router/index.js{name:'xiangqing',path:'detail/:id',component:Detail,//第一种写法:props值为对象,该对象中所有的`key-value`的组合最终都会通过props传给Detail组件(传递死数据,不常用)// props:{a:900,b:10} // Detail.vue -> props:['a','b']//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有`params`参数通过props传给Detail组件(query参数收不到// props:true//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件 (推荐)props({ query }){// $routereturn{id:route.query.id,title:route.query.title
}}// 连续解构赋值props({params:{ id, title }}){return{id: id,title: title }}}
8.
<router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace>News</router-link>
9.编程式路由导航
- 作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 - 具体编码:
//$router的两个APIthis.$router.push({name:'xiangqing',params:{id:xxx,title:xxx }})this.$router.replace({name:'xiangqing',// 可以任意指定name跳转params:{id:xxx,title:xxx }})this.$router.forward()//前进this.$router.back()//后退this.$router.go()//可前进也可后退$route: 路由规则$router: 路由器
10.缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁。
- 具体编码:
<keep-alive include="News"> <router-view></router-view></keep-alive>
11.两个新的生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字: 1.
activated
路由组件被激活时触发。2.deactivated
路由组件失活时触发。
12.路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫、独享守卫、组件内守卫
- 全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行// 在需要鉴权的路由规则下:meta:{isAuth:true}router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from)if(to.meta.isAuth){//判断当前路由是否需要进行权限控制if(localStorage.getItem('school')==='atguigu'){//权限控制的具体规则next()//放行}else{alert('暂无权限查看')// next({name:'guanyu'})}}else{next()//放行}})//全局后置守卫:初始化时执行、每次路由切换后执行// 作用:点击改变页面 title// routes: meta:{title:'about'}router.afterEach((to,from)=>{ console.log('afterEach',to,from)if(to.meta.title){ document.title = to.meta.title //修改网页的title}else{ document.title ='vue_test'}})
- 独享守卫:
beforeEnter(to,from,next){ console.log('beforeEnter',to,from)if(to.meta.isAuth){//判断当前路由是否需要进行权限控制if(localStorage.getItem('school')==='atguigu'){next()}else{alert('暂无权限查看')// next({name:'guanyu'})}}else{next()}}
- 组件内守卫:
//进入守卫:通过`路由规则`,进入该组件时被调用beforeRouteEnter(to, from, next){},//离开守卫:通过`路由规则`,离开该组件时被调用beforeRouteLeave(to, from, next){next()}
13.路由器的两种工作模式
1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
3. hash模式:
1. 地址中永远带着#号,不美观 。
2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
3. 兼容性较好。
4. history模式:
1. 地址干净,美观 。
2. 兼容性和hash模式相比略差。
3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。// nginx// router:mode:'hash/history'
路由404
{path:'*',component:NotFound}
声明式导航 - 两个类名
router-link会自动给当前导航添加两个类名
router-link-active: 激活的导航链接 模糊匹配
to="/my" 可以匹配 /my /my/a /my/b ....
router-link-exact-active: 激活的导航链接 精确匹配
to="/my" 仅可以匹配 /my
Vue封装的过度与动画
- 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
- 写法:1. 准备好样式:- 元素进入的样式: 1. v-enter:进入的起点2. v-enter-active:进入过程中3. v-enter-to:进入的终点- 元素离开的样式: 1. v-leave:离开的起点2. v-leave-active:离开过程中3. v-leave-to:离开的终点- v 即
<transition>
里的name -.hello-enter-active
2. 使用<transition>
包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1></transition>
3. 备注:若有多个元素需要过度,则需要使用:<transition-group>
,且每个元素都要指定key
值。
vue脚手架配置代理
方法一
在vue.config.js中添加如下配置:
devServer:{proxy:"http://localhost:5000"}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
- 8080 请求 5000
方法二
编写vue.config.js配置具体代理规则:
module.exports ={devServer:{proxy:{'/api1':{// 匹配所有以 '/api1'开头的请求路径target:'http://localhost:5000',// 代理目标的基础路径changeOrigin:true,pathRewrite:{'^/api1':''}},'/api2':{// 匹配所有以 '/api2'开头的请求路径target:'http://localhost:5001',// 代理目标的基础路径changeOrigin:true,pathRewrite:{'^/api2':''}}}}}/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
⭐️Vue3
vue3基础入门参考文章!必看
ES6模块化与异步编程高级用法
ES6模块化
ES6 模块化规范是浏览器端与服务器端通用的模块化开发规范。开发者不需再额外学习 AMD、CMD 或 CommonJS 等模块化规范。
定义:
- 每个 js 文件都是一个独立的模块
- 导入其它模块成员使用 import 关键字
- 向外共享模块成员使用 export 关键字
基于 node.js 学习 ES6需配置:
- v14.15.1以上版本的 node.js
- 在 package.json 的根节点中添加 “type”:“module”
语法:
① 默认导出与默认导入
- 默认导出: - 语法:
export default{ 值类型成员,模块私有方法... }
(❗️export default
只能使用一次) - 默认导入: -
import 接收名称 from '模块标识符'
② 按需导出与按需导入
- 按需导入-
import { s1,say } from '模块标识符'
- 按需导出-
export let s1 = 'ben';export function...
- 注意:① 每个模块中可以使用多次按需导出② 按需导入的成员名称必须和按需导出的名称保持一致③ 按需导入时,可以使用 as 关键字进行重命名④ 按需导入可以和默认导入一起使用
③ 直接导入并执行模块中的代码
只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。
import '模块标识符'
⭐️Promise
回调地狱
多层回调函数的相互嵌套,就形成了回调地狱。
缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,代码的可读性变差
解决回调地狱–promise
基本概念
Promise 是一个构造函数
const p = new Promise()// new实例 代表一个异步操作
Promise.prototype 上包含一个 .then() 方法
- 实例可以通过 原型链 的方式访问到 .then() 方法,p.then()
.then() 方法用来预先指定成功和失败的回调函数
- p.then(成功的回调函数【必选】,失败的回调函数【可选】) - p.then(result => { }, error => { })
基于回调函数按顺序读取文件内容
基于 then-fs 读取文件内容
1
import thenFs from 'then-fs'
2
thenFs.readFile('./1.txt','utf8').then(r1 => {log r1},err1 => {log err1.message})
(重复写)
注意:失败回调可选,无法保证文件读取顺序
基于 Promise 按顺序读取文件的内容
Promise 支持链式调用,从而来解决回调地狱的问题。
import thenFs from'then-fs'
thenFs.readFile('./1.txt','utf8').then((r1)=>{return thenFs.readFile('./2.txt','utf8')}).then((r2)=>{return thenFs.readFile('./3.txt','utf8')}).then((r3)=>{
console.log(r3)}).catch(err=>{
console.log(err.message)})
通过 .catch 捕获错误
(...).catch(err=>{
console.log(err.message)})
如果不希望前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前
thenFs.readFile('./1.txt','utf8').catch(err=>{ console.log(err.message)}).then...
- 捕获前面发生的错误,并输出错误的信息
- 由于错误已被及时处理,不影响后面 .then 的正常进行
Promise.all()方法
Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)。
Promise.race() 方法
Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制)。
基于 Promise 封装读文件的方法
functiongetFile(fpath){returnnewPromise(function(resolve,reject){
fs.readFile(fpath,'utf8',(err,dataStr)=>{if(err)returnreject(err)resolve(dataStr)})})}getFile('./1.txt').then(resolve,reject)
async/await
async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。
import thenFs from'then-fs'asyncfunctiongetAllFile(){const r1 =await thenFs.readFile('./1.txt')const r2 =await thenFs.readFile('./2.txt')const r3 =await thenFs.readFile('./3.txt')}getAllFile()
❗️注意
① 如果在 function 中使用了 await,则 function 必须被 async 修饰
② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
⭐️EventLoop
JavaScript 是单线程的语言
也就是说,同一时间只能做一件事情
问题:
- 如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。
同步任务和异步任务
JavaScript 把待执行的任务分为了两类,防止某个耗时任务导致程序假死
① 同步任务(synchronous)
- 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
- 只有前一个任务执行完毕,才能执行后一个任务
② 异步任务(asynchronous)
- 又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行
- 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数
同步任务和异步任务的执行过程
EventLoop 的基本概念
JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这 个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)。
宏任务和微任务
异步任务的两种类别
js 主线程先执行完 宏任务 之后再判断有无微任务,没有再执行下一个 宏任务
① 宏任务(macrotask)
- 异步 Ajax 请求、
- setTimeout、setInterval、
- 文件操作
- 其它宏任务
② 微任务(microtask)
- Promise.then、.catch 和 .finally
- process.nextTick
- 其它微任务
宏任务和微任务的执行顺序
前端工程化
组件化、模块化、规范化、自动化
- 前端工程化的解决方案 - webpack(打包构建)- parcel
webpack
- 解决 浏览器不兼容 ES6 语法
Vue的基本使用
Vue全家桶
- vue(核心库)
- vue-router(路由方案)
- vuex(状态管理方案)
- vue 组件库(快速搭建页面 UI 效果的方案)
工具:
vue-cli、vite、vue-devtools、vetur
#mermaid-svg-VlEUNr8F6Z7pFXYA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .error-icon{fill:#552222;}#mermaid-svg-VlEUNr8F6Z7pFXYA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VlEUNr8F6Z7pFXYA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .marker.cross{stroke:#333333;}#mermaid-svg-VlEUNr8F6Z7pFXYA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VlEUNr8F6Z7pFXYA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .cluster-label text{fill:#333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .cluster-label span{color:#333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .label text,#mermaid-svg-VlEUNr8F6Z7pFXYA span{fill:#333;color:#333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .node rect,#mermaid-svg-VlEUNr8F6Z7pFXYA .node circle,#mermaid-svg-VlEUNr8F6Z7pFXYA .node ellipse,#mermaid-svg-VlEUNr8F6Z7pFXYA .node polygon,#mermaid-svg-VlEUNr8F6Z7pFXYA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VlEUNr8F6Z7pFXYA .node .label{text-align:center;}#mermaid-svg-VlEUNr8F6Z7pFXYA .node.clickable{cursor:pointer;}#mermaid-svg-VlEUNr8F6Z7pFXYA .arrowheadPath{fill:#333333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VlEUNr8F6Z7pFXYA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-VlEUNr8F6Z7pFXYA .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-VlEUNr8F6Z7pFXYA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VlEUNr8F6Z7pFXYA .cluster text{fill:#333;}#mermaid-svg-VlEUNr8F6Z7pFXYA .cluster span{color:#333;}#mermaid-svg-VlEUNr8F6Z7pFXYA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VlEUNr8F6Z7pFXYA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
构建用户界面
指令
数据驱动视图
事件绑定
前端框架
构建用户界面的一整套解决方案
vue全家桶
配套工具
Vue 特性
- 数据驱动视图
- 双向数据绑定
Vue3 新增功能:
- 组合式API、多根节点组件、更好的TypeScript支持
废弃的旧功能:
- 过滤器、不再支持 o n 、 on 、 on、off 和 $once 实例方法等
- 迁移指南
过滤器(已剔除)
过滤器(Filters)常用于文本的格式化。
可以用在:
- 插值表达式
{{msg | capitalize}}
- v-bind 属性绑定
:id="rawId | formatId"
组件基础
单页面应用程序(SPA)
一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。
SPA 特点:
- 将所有的功能局限于一个 web 页面中,仅在该 web 页面初始化时加载相应的资源( HTML、JavaScript 和 CSS)。
- 一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。而是利用 JavaScript 动态地变换 HTML 的内容,从而实现页面与用户的交互。
SPA 优点:
- 1 良好的交互体验 - 单页应用的内容的改变不需要重新加载整个页面- 获取数据也是通过 Ajax 异步获取- 没有页面之间的跳转,不会出现“白屏现象”
- 2 良好的前后端工作分离模式 - 后端专注于提供 API 接口,更易实现 API 接口的复用- 前端专注于页面的渲染,更利于前端工程化的发展
- 3 减轻服务器的压力 - 服务器只提供数据,不负责页面的合成与逻辑的处理,吞吐能力会提高几倍
SPA 缺点:
- 1 首屏加载慢 - 路由懒加载- 代码压缩- CDN 加速- 网络传输压缩
- 2 不利于 SEO - SSR 服务器端渲染
两种快速创建工程化的 SPA 项目的方式:
vitevue-cli支持版本仅支持 vue3支持 3.x 和2.x基于 webpack?noyes运行速度快较慢功能完整度小而巧(逐渐完善大而全建议在企业级开发中使用?目前不建议建议
组件化开发思想
根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。
好处:
- 提高了前端代码的复用性和灵活性
- 提升了开发效率和后期的可维护性
vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。
vue 组件组成结构
- template -> 组件的模板结构(必选)
- script -> 组件的 JavaScript 行为(可选) -
<script> export default { name,data数据、methods方法...} </script>
- name 节点为当前组件定义一个名称(建议每个单词首字母大写 -name:'MyApp'
- 在注册组件时:app.component(Swiper.name,Swiper)- data必须是一个函数,不能直接指向一个数据对象 -data(){return {}}
-methods:{ 事件处理函数 }
-components:{ 'my-search':Search,}
- style -> 组件的样式(可选) -
<style lang="css"> h1{...}</style>
-lang="css"
(默认)还有 less(npm i less -D)、scss
在 template 中定义根节点:
- 在 vue2 中,节点内最外层必须由 唯一 的单个根节点包裹
- 在 vue3 中,中支持定义多个根节点
组件的基本使用:
注册组件
- 全局注册-
app.component('my-swiper',Swiper)
- 直接在template中以标签形式使用<
my-swiper>- 使用频率高 - 局部注册-
<script>import Search for''exportdefault{components:{'my-search':Search,}}
- 只有特点情况下会被用到
组件注册时名称
- kebab-case 命名法(俗称短横线命名法,例如 my-swiper)
- ⭐️PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper) - 可以转化成 短横线命名法
组件之间的样式冲突问题
- 为每个组件分配唯一的自定义属性,在编写样式时,通过属性选择器来控制样式的作用域
<template><div class="container" data-v-001><h3 data-v-001>轮播图组件<h3></div></template><style>.container[data-v-001]{border:1px solid red;}</style>
- style 节点的 scoped 属性> 自动为当前组件的 DOM 标签和 style 样式应用这个自定义属性,防止组件样式的冲突问题
<style scoped>.container{border:1px solid red;}</style>
- /deep/样式穿透> 解决scoped 当前组件的样式对子组件是不生效的问题
<style lang="less" scoped>/deep/.title{color:blue;}</style>
注意:/deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代 /deep/。
全局样式:
1 另外写一个
index.css
文件
2 在
main.js
中引入
组件的 props
为了提高组件的复用性,封装组件时要遵守:
- 组件的 DOM 结构、Style 样式要尽量复用
- 组件中要展示的数据,尽量由组件的使用者提供
props 是组件的自定义属性,组件的使用者可以通过 props 把数据传递到子组件内部,供子组件内部进行使 用。
props 的作用:父组件通过 props 向子组件传递要展示的数据。
props 的好处:提高了组件的复用性。
在组件中声明 props
在封装 vue 组件时,可以把动态的数据项声明为 props 自定义属性。自定义属性可以在当前组件的模板结构中被直接使用。
动态绑定 props 的值
可以使用 v-bind 属性绑定的形式,为组件动态绑定 props 的值
props 的大小写命名
组件中如果使用“camelCase (驼峰命名法)”声明了 props 属性的名称,则有两种方式为其绑定属性的值:
1
pubTime = '1889'
2
pub-time = '1889'
(推荐)
Class 与 Style 绑定
动态绑定 HTML 的 class
1 可以通过三元表达式,动态的为元素绑定 class 的类名。
:class = "isItalic ? 'isItalic' : ''"
- 一定要记得带
''
2 以
数组
语法绑定 HTML 的 class
:class = "[isItalic ? 'isItalic' : '',isDelete ? 'delete' : '']"
3 以
对象
语法绑定 HTML 的 class
语法:
:class="{类名:布尔值}"
:class = "classObj"
classObj:{italic:true,delete:false}
4 以对象语法绑定内联的 style
语法:
:style="样式属性名:样式的值"
- 样式名要用 小驼峰 命名or 引号
:style="{color:active},fontSize:fsize+'px'"
:style= "styleObj"
props 验证
对象类型的 props 节点
① 基础的类型检查
- Srting Number Boolean Array Object Date Function Symbol
② 多个可能的类型
- [String,Number,…]
③ 必填项校验
- required:true
④ 属性默认值
- defult:‘’/0 (注意类型要和 type 的相同)
⑤ 自定义验证函数
计算属性
本质上就是一个 function 函数,它可以实时监听 data 中数据的变化,并 return 一个计算后的新值,供组件渲染 DOM 时使用
声明计算属性
① 计算属性必须定义在 computed 节点中
② 计算属性必须是一个 function 函数
③ 计算属性必须有返回值
④ 计算属性必须当做普通属性使用
计算属性 vs 方法
相对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算。因此计算属性的性能更好
自定义事件
在封装组件时:
① 声明自定义事件
- 在 emits 节点中声明
emits:['change']
② 触发自定义事件
methods:{onBtnClick(){this.$emit('change')}}
在使用组件时:
③ 监听自定义事件
@change='getCount'
自定义事件传参
this.$emit('change',this.count)
自定义事件解绑
- 一个事件解绑: -
this.$off('自定义事件名称')
- 多个事件解绑 -
this.$off('xxx','yyy')
-this.$off()
组件上的 v-model
步骤:
父 => 子
- 父组件 -
:number='count'
- 子组件 -
props:['number']
子 => 父
- 父组件 -
v-model:number='count'
(不行) - 子组件 -
props:['number']
-emits:['update:number']
-this.$emit('update:number',this.number+1)
或者:
// 父组件<aa-input v-model="aa"></aa-input>// 等价于<aa-input :value="aa" @input="aa=$event.target.value"></aa-input>(父组件不用自己拿子组件传过来的数据进行赋值了)// 子组件:<input :value="aa" @input="onmessage"></aa-input>props:['value']methods:{onmessage(e){this.$emit('input',e.target.value)}}
组件高级
组件的生命周期
生命周期函数执行时机所属阶段执行次数应用场景beforeCreate在内存中开始创建组件之前创建阶段唯一1次-⭐️created组件在内存中创建完毕后创建阶段唯一1次ajax 请求数据beforeMount在把组件初次渲染到页面之前创建阶段唯一1次⭐️mounted组件初次在页面中渲染完毕后创建阶段唯一1次操作 DOMbeforeUpdate在组件被重新渲染之前运行阶段0 或 多次updated组件在页面中被重新渲染完毕后运行阶段0 或 多次beforeUnmount在组件被销毁之前销毁阶段唯一1次⭐️unmounted组件被销毁后(页面和内存)销毁阶段唯一1次
组件之间的数据共享
1. 组件之间的关系
① 父子关系
② 兄弟关系
③ 后代关系
2. 父子组件之间的数据共享
① 父 -> 子共享数据
- 父 -
:msg="massage" :user='userinfo'
- 子 -
props:['msg','user']
② 子 -> 父共享数据
- 父 -
:change='getNum'
- 子 -
emits:['change']
-this.$emit('change',this.num)
③ 父 <-> 子双向数据同步
- 父 -
v-model:number='count'
- 子 -
props:['number']
-emits:['update:props的名字']
-this.emits('update:props的名字',this.number+1)
3. 兄弟组件之间的数据共享
兄弟组件之间实现数据共享的方案是 EventBus。可以借助于第三方的包 mitt 来创建 eventBus 对象,从而实现兄弟组件之间的数据共享。
1 安装 mitt 依赖包
npm install [email protected] -S
2 创建公共的 eventBus 模块
import mitt from'mitt'const bus =mitt()exportdefault bus
3 在数据接收方自定义事件
import bus from'./eventBus.js'exportdefault{data(){return{count:0}}created(){
bus.on('countChange',(num)=>this.count = num)}}
4 在数据接发送方触发事件
bus.emit('countChange',this.count)
4. 后代关系组件之间的数据共享
指的是爷节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂,可以使用 provide 和 inject 实现后代关系组件之间的数据共享。
- 爷节点- 通过 provide 共享数据-
provide(){ return { color: this.color } }
- 子孙节点- 通过 inject 接收数据-
inject:['color']
- 爷节点对外共享响应式的数据- 结合 computed 函数向下共享响应式的数据-
import{ computed }from'vue'exportdefault{data(){return{color:'red'}},provide(){returncolor:computed(()=>this.color)}}
- 子孙节点使用响应式的数据-
inject:['color']
- 必须以.value
的形式进行使用 -{{ color.value }}
5. vuex
vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中,vuex 可以让组件之间的数据共享变得高效、清晰、且易于维护。
- 全局数据共享
全局配置 axios
在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios
axios.defaults.baseURL ='http://api.com'
app.config.globalProperties.$http = axios
ref 引用
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的
DOM
元素或
组件的实例
。默认情况下, 组件的 refs 指向一个空对象。
标签上写:
ref=mybtn
this.$refs.mybtn.style.color = 'red'
引用到组件的实例之后,就可以调用组件上的 methods 方法
this.$refs.mybtn.add()
控制文本框和按钮的按需切换
通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。
v-if="inputVisible "
v-else
让文本框自动获得焦点
this.$nextTick(()=>{this.$ref.ipt.focus()// 异步})
this.$nextTick(cb)
方法
把 cb 回调推迟到下一个 DOM 更新周期之后执行,从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
动态组件
<component is="要渲染的组件的名称"></component>
使用 keep-alive 保持状态
默认情况下,切换动态组件时无法保持组件的状态。
<keep-alive><component is="要渲染的组件的名称"></component></keep-alive>
插槽
// 封装组件<template><slot></slot></template>// 注册组件<myCom><p>用户自定义内容</p></myCom>
自定义指令
路由
Vuex
在Vue中实现
集中式
状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(
读/写
),也是一种组件间通信的方式,且适用于任意组件间通信。
何时使用?
多个组件需要共享
数据
时
原理图:
搭建vuex环境
- 创建文件:
src/store/index.js``````//引入Vue核心库import Vue from'vue'//引入Vueximport Vuex from'vuex'//应用Vuex插件Vue.use(Vuex)//准备actions对象——响应组件中用户的动作const actions ={}//准备mutations对象——修改state中的数据const mutations ={}//准备state对象——保存具体的数据const state ={}//创建并暴露storeexportdefaultnewVuex.Store({ actions, mutations, state})
- 在
main.js
中创建vm时传入store
配置项......//引入storeimport store from'./store'......//创建vmnewVue({el:'#app',render:h=>h(App), store})
4.基本使用
- 初始化数据、配置
actions
、配置mutations
,操作文件store/index.js``````//引入Vue核心库import Vue from'vue'//引入Vueximport Vuex from'vuex'//引用VuexVue.use(Vuex)const actions ={//响应组件中加的动作jia(context,value){// console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value)},}const mutations ={//执行加JIA(state,value){// console.log('mutations中的JIA被调用了',state,value) state.sum += value }}//初始化数据const state ={sum:0}//创建并暴露storeexportdefaultnewVuex.Store({ actions, mutations, state,})
- 组件中读取vuex中的数据:
$store.state.sum
1 在模板里面写不用加 this {{}} 2 在脚本里面写要加 this - 组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
> 备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写>> dispatch>
> ,直接编写>> commit>
5.getters的使用
- 概念:当state中的数据需要经过
加工
后再使用时,可以使用getters加工。 - 在
store.js
中追加getters
配置const getters ={bigSum(state){return state.sum *10}}
- 组件中读取数据:
$store.getters.bigSum
6.四个map方法的使用
- mapState方法:用于帮助我们映射
state
中的数据为计算属性computed:{//借助mapState生成计算属性:sum、school、subject(对象写法)...mapState({sum:'sum',school:'school',subject:'subject'}),简化:sum:state=> state.sum...//借助mapState生成计算属性:sum、school、subject(数组写法)...mapState(['sum','school','subject']),},
- mapGetters方法:用于帮助我们映射
getters
中的数据为计算属性computed:{//借助mapGetters生成计算属性:bigSum(对象写法)...mapGetters({bigSum:'bigSum'}),//借助mapGetters生成计算属性:bigSum(数组写法)...mapGetters(['bigSum'])},
- mapActions方法:用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数methods:{//靠mapActions生成:incrementOdd、incrementWait(对象形式)...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})//靠mapActions生成:incrementOdd、incrementWait(数组形式)...mapActions(['jiaOdd','jiaWait'])}
- mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数methods:{//靠mapActions生成:increment、decrement(对象形式)...mapMutations({increment:'JIA',decrement:'JIAN'}),//靠mapMutations生成:JIA、JIAN(对象形式)...mapMutations(['JIA','JIAN']),}
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
7.Modules 模块化+命名空间
将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
命名空间
namespaced: true
使模块成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
computed:{...mapState('some/nested/module',{a:state=> state.a,b:state=> state.b
})},methods:{...mapActions('moduleA',['foo',// -> this.foo()'bar'// -> this.bar()])}
namespaced: true
+
createNamespacedHelpers
使 …mapState(‘module1’,[‘count’]) --> …mapState([‘count’])
1import{ createNamespacedHelpers }from'vuex'2const{ mapState, mapActions }=createNamespacedHelpers('moduleB')
- 目的:让代码更好维护,让多种数据分类更加明确。
- 修改
store.js``````const countAbout ={namespaced:true,//开启命名空间state:{x:1},mutations:{...},actions:{...},// context 指向当前模块getters:{bigSum(state){return state.sum *10}}}const personAbout ={namespaced:true,//开启命名空间state:{...},mutations:{...},actions:{...}}const store =newVuex.Store({modules:{ countAbout, personAbout }})
- 开启命名空间后,组件中读取state数据:
//方式一:自己直接读取this.$store.state.personAbout.list//方式二:借助mapState读取:...mapState('countAbout',['sum','school','subject']),
- 开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取this.$store.getters['personAbout/firstPersonName']//方式二:借助mapGetters读取:...mapGetters('countAbout',['bigSum'])
- 开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatchthis.$store.dispatch('personAbout/addPersonWang',person)//方式二:借助mapActions:...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
- 开启命名空间后,组件中调用commit
//方式一:自己直接committhis.$store.commit('personAbout/ADD_PERSON',person)//方式二:借助mapMutations:...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
路由
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
1.基本使用
- 安装vue-router,命令:
npm i vue-router
- 应用插件:
Vue.use(VueRouter)
- 编写router配置项:
//引入VueRouterimport VueRouter from'vue-router'//引入Luyou 组件import About from'../components/About'import Home from'../components/Home'//创建router实例对象,去管理一组一组的路由规则const router =newVueRouter({routes:[{path:'/about',component:About },{path:'/home',component:Home }]})//暴露routerexportdefault router
- 实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
- 指定展示位置
<router-view></router-view>
2.几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
3.多级路由(多级路由)
- 配置路由规则,使用children配置项:
routes:[{path:'/about',component:About,},{path:'/home',component:Home,children:[//通过children配置子级路由{path:'news',//此处一定不要写:/newscomponent:News },{path:'message',//此处一定不要写:/messagecomponent:Message }]}]
- 跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
4.路由的query参数
- 传递参数
<!-- 跳转并携带query参数,to的字符串写法 --><router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --><router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }">跳转</router-link>
- 接收参数:
$route.query.id$route.query.title
5.命名路由
- 作用:可以简化路由的跳转。
- 如何使用1. 给路由命名:
{path:'/demo',component:Demo,children:[{path:'test',component:Test,children:[{name:'hello'//给路由命名path:'welcome',component:Hello,}]}]}
2. 简化跳转:<!--简化前,需要写完整的路径 --><router-link to="/demo/test/welcome">跳转</router-link><!--简化后,直接通过名字跳转 --><router-link :to="{name:'hello'}">跳转</router-link><!--简化写法配合传递参数 --><router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }">跳转</router-link>
6.路由的params参数
- 配置路由,声明接收params参数
{path:'/home',component:Home,children:[{path:'news',component:News },{component:Message,children:[{name:'xiangqing',path:'detail/:id/:title',//使用占位符声明接收params参数component:Detail }]}]}
- 传递参数
<!-- 跳转并携带params参数,to的字符串写法 --><router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --><router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }">跳转</router-link>
> 特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置! - 接收参数:
$route.params.id$route.params.title
7.路由的props配置
作用:让路由组件更方便的收到参数
{name:'xiangqing',path:'detail/:id',component:Detail,//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件// props:{a:900}//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件// props:true//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件props(route){return{id:route.query.id,title:route.query.title
}}}
8.
<router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
9.编程式路由导航
- 作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 - 具体编码:
//$router的两个APIthis.$router.push({name:'xiangqing',params:{id:xxx,title:xxx }})this.$router.replace({name:'xiangqing',params:{id:xxx,title:xxx }})this.$router.forward()//前进this.$router.back()//后退this.$router.go()//可前进也可后退
10.缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁。
- 具体编码:
<keep-alive include="News"> <router-view></router-view></keep-alive>
- 注意❗️1. 找到最根的组件 加2. include 里面的名字为
组件名
11.两个新的生命周期钩子
由于组件没有被销毁,所以。。。
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字: 1.
activated
路由组件被激活时触发。 - 相当于 mounted 的功能- 设置定时器- …2.deactivated
路由组件失活时触发。 - 相当于 beforeDestroy 的功能- 清除定时器- …
12.路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫、独享守卫、组件内守卫
- 全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from)if(to.meta.isAuth){//判断当前路由是否需要进行权限控制if(localStorage.getItem('school')==='atguigu'){//权限控制的具体规则next()//放行}else{alert('暂无权限查看')// next({name:'guanyu'})}}else{next()//放行}})//全局后置守卫:初始化时执行、每次路由切换后执行router.afterEach((to,from)=>{ console.log('afterEach',to,from)if(to.meta.title){ document.title = to.meta.title //修改网页的title}else{ document.title ='vue_test'}})
- 独享守卫:
beforeEnter(to,from,next){ console.log('beforeEnter',to,from)if(to.meta.isAuth){//判断当前路由是否需要进行权限控制if(localStorage.getItem('school')==='atguigu'){next()}else{alert('暂无权限查看')// next({name:'guanyu'})}}else{next()}}
- 组件内守卫:
//进入守卫:通过路由规则,进入该组件时被调用beforeRouteEnter(to, from, next){},//离开守卫:通过路由规则,离开该组件时被调用beforeRouteLeave(to, from, next){}
13.路由器的两种工作模式
1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
3. hash模式:
1. 地址中永远带着#号,不美观 。
2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
3. 兼容性较好。
4. history模式:
1. 地址干净,美观 。
2. 兼容性和hash模式相比略差。
3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
UI组件库
移动端常用 UI 组件库
Vant
Cube UI
Mint UI
PC 端常用 UI 组件库
Element UI
IView UI
案例分析
TodoList1
组件化编码流程:
(1) 拆分
静态
组件:组件要按照
功能点
拆分,命名不要与html元素冲突。
(2) 实现动态组件:考虑好
数据
的存放
位置
,数据是一个组件在用,还是一些组件在用:
1) 一个组件在用:放在组件自身即可。
2) 一些组件在用:放在他们共同的
父
组件上(状态提升)。eg: 读 or 写
(3) 实现
交互
:从绑定事件开始。
props适用于:
(1) 父组件 ==> 子组件 通信
(2) 子组件 ==> 父组件 通信(要求父先给子一个函数)
技巧:
1 使用
reduce
累计
computed:{doneTotal(){returnthis.todos.reduce((pre, current)=> pre +(current.isDone ?1:0),0)}}
2 使用
filter
过滤
clearDone(){this.todos =this.todos.filter(todo=>!todo.isDone)}
3 巧用
计算
属性
computed:{isAll(){returnthis.allNum ?this.allChecked :false}}
4 使用
every
判断
allChecked(){returnthis.todos.every(todo=> todo.isDone ===true)}
5
watch
监视数据 进行存储
todos: localStorage.getItem('todos')?JSON.parse(localStorage.getItem('todos')):[...]watch:{todos(newValue){
localStorage.setItem('todos',JSON.stringify(newValue))}}
注意:
使用 v-model 时要切记:v-model 绑定的值
不能是props
传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但
不推荐
这样做。
⭐️杂记
cmtCount:{// 通过数组的形式 , 为当前属性定义多个可能的类型type:[Number, String],default:0}
- 在使用组件时,如果某个属性名是“小驼峰”形式,则绑定属性时,建议改写成“连字符”格式,例如: -
cmtCount => cmt-count
❗️ 修改配置项一定要重新 run 一下项目
EsLint:
'space-before-function-paren': 0
两个重要的原则:
- 由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数, 这样this的指向才是vm 或 组件实例对象。
v-model.number.lazy
当添加了.lazy修饰符后,双向绑定的数据就不同步了,相当于在input输入框失去焦点后触发的change事件中同步。即将数据改到在change事件中发生。
组件化编码流程(通用)
1 实现静态组件
- 抽取组件,使用组件实现静态页面效果
2 展示动态数据
- 2.1 数据类型、名称?
- 2.2 数据保存在哪个组件?
3 交互——从绑定事件监听开始
nanoid
1 安装
npm i nanoid
--force
2
import { nanoid } from 'nanoid'
3
id: nanoid()
bootstrap
1
npm i bootstrap
2 // main.js
import 'bootstrap/dist/css/bootstrap.css'
巧用 boolean
不只是
true or false
,还可以用来
筛选
条件
eg:
:class="{ active: score > 60}"
Vscode 新建文件
// 终端
ni 'xxx'
表单渲染
循环 带有 label标签的 for 和 id 要唯一
:id="'xx'+item.id"
:for="'xx'+item.id"
axios
基本语法:
axios发请求的基本语法:
axios({url:'url',method:'get/post/put/delete',params:{},// 包含 query 参数对象,问号后面的参数data:{},// 包含请求体参数的对象})
axios.get(url,{配置})// { params:{id:1} }
axios.delete(url,{配置})
axios.post(url, data数据对象)
axios.put(url, data数据对象)使用axios发ajax请求携带参数:params参数: 只能拼在路径中:/admin/product/baseTrademark/delete/1query参数:
拼在路径中的?后面:/admin/product/baseTrademark?id=1通过params配置来指定:axios({params:{id:1}})请求体参数:通过data配置或post()/put()的第二个参数指定
循环 带有 label标签的 for 和 id 要唯一
:id="'xx'+item.id"
:for="'xx'+item.id"
babel
- 语法降级
版权归原作者 wendyymei 所有, 如有侵权,请联系我们删除。