admin 管理员组文章数量: 887019
⭐️Vue
vue3基础入门参考文章!必看
Vue 2
是一套用于构建用户界面的框架
Vue 的特性
-
数据驱动视图
-
双向数据绑定
-
MVVM
- MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分
- Model 表示当前页面渲染时所依赖的数据源。
- View 表示当前页面所渲染的 DOM 结构。
- ViewModel 表示 vue 的实例,它是 MVVM 的核心。
- MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分
-
数据代理
通过vm对象来代理data对象中属性的操作(读/写)
-
更加方便的操作data中的数据
-
基本原理
-
通过Object.defineProperty()把data对象中所有属性添加到vm上。
-
为每一个添加到vm上的属性,都指定一个getter/setter。
-
在getter/setter内部去操作(读/写)data中对应的属性。
-
-
Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
2. 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(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 = new Vue({ // 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" @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
- 给调用函数+个()eg:
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-if v-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 = new Vue({
el: '#app',
data: {
username: 'admin'
},
// 所有的侦听器,都应该被定义到 watch 节点下
watch: {
// 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可
// 新值在前,旧值在后
username(newVal) {
if (newVal === '') return
// 1. 调用 jQuery 中的 Ajax 发起请求,判断 newVal 是否被占用!!!
$.get('https://www.escook/api/finduser/' + newVal, function (result) {
console.log(result)
})
}
}
})
-
2 使用 watch 检测用户名是否可用
监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:
watch: { // 监听 username 值的变化 async username(newVal) { if (newVal === '') return // 使用 axios 发起请求,判断用户名是否可用 const { data: res } = await axios.get('https://www.escook/api/finduser/' + newVal) } }
-
3 immediate选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使 用 immediate 选项。
watch: { // 让被监听的对象指向一个 配置对象 username: { // handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数 handler: async function (newVal) { if (newVal === '') return const { data: res } = await axios.get('https://www.escook/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':{ async handler(newVal){ const { data:res } = await axios.get('https://www.escook/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 实例,得到 ViewModel var vm = new Vue({ 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() {
return this.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 } = await axios({ 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.js
import axios from 'axios'
// 配置请求根路径
axios.defaults.baseURL = 'http://www.itcbc:3006'
// 把 axios 挂载到 Vue.prototype上,供每个组件的实例直接使用
// 缺点: 不利于接口的 复用
Vue.propotype.$http = axios
// 组件中
methods:{
async getBooks(){
const {data : res} = await this.$http.get('http://www.itcbc:3006/api/getbooks')
}
}
$mount方法
const vm = new Vue({
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/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 入口文件中,通过 Vueponent() 方法,可以注册全局组件。
- 1
import
- 2
Vueponent('MyCount',MyCount)
- 1
组件的 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}
- eg:
-
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}
- 在vue 实例
-
发送方
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 属性的值
export default{
name:'MyRight'
}
对比:
- name
- 1 调试的时候 出现的 组件名称
- 2 与
<keep-alive>
结合 指定被缓存与不被缓存
- 注册名称
- 应用场景:以标签的形式,把注册好的组件,渲染和使用到页面结构之中
mixin(混入)
可以把多个组件共用的配置提取成一个混入对象
独立的 js 文件
使用:
1 定义混合
export const xxx = {
data(){...}
methods(){...}
...
}
export const yyy = {
data(){...}
methods(){...}
...
}
2 使用混合
- 全局混入
- 在 main.js 中
Vue.mixin(xxx)
- 在 main.js 中
- 局部混入 // App.vue or 某一组件中
import {xxx,yyy} from '/mixin'
mixins:[xxx,yyy]
插件
独立的 js 文件
// 1 定义插件 plugins.js
export default {
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.js
import 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
}
})
new Vue({
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>
export default{
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'
break
case '#movie':
this.comName = 'Moive'
break
case '#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 = new VueRouter() export default router
③ 导入并挂载路由模块
-
在 src/main.js 入口文件中,导入并挂载路由模块。
import Vue from 'vue' import App from './App.vue' // 1 导入路由模块 // 在进行模块化导入时,如果给定的是文件夹,则默认导入这个文件夹,名字叫做 index.js 的文件 import router from '@/router' new Vue({ 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 = new VueRouter({ routers:[ // 在 routes 数组中,声明路由的匹配规则 // 路由规则 // path 表示要匹配的 hash 地址;component表示要展示的路由组件 {path:'/home',component:Home}, {path:'/movie',component:Movie}, {path:'/about',component:About} ] })
vue-router 的常见用法
路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
const router = new VueRouter({
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 = new VueRouter({
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> 中写 export default{ 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 地址,并增加一条历史记录,展示对应的组件页面。
-
export default{ mehtods:{ getoMovie(){ this.$router.push('/movie/1') } } }
② this.$router.replace(‘hash 地址’)
- 跳转到指定的 hash 地址,不会增加历史记录并替换掉当前的历史记录
③ this.$router.go(数值 n)
-
实现导航历史前进、后退
-
export default{ props:['id'], methods:{ goBack(){ this.$router.go(-1) } } }
-
如果后退的层数超过上限,则原地不动
-
$router.go 的简化用法
- ① $router.back()
- ② $router.forward()
导航守卫
导航守卫可以控制路由的访问权限。
全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制:
const router =new Vue({...})
router.beforeEach(fn)
守卫方法的 3 个形参
const router =new Vue({...})
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 //引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default 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.js routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component: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.命名路由
-
作用:可以简化路由的跳转。
-
如何使用
-
给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
-
简化跳转:
<!--简化前,需要写完整的路径 --> <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 }){ // $route
return {
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的两个API this.$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.两个新的生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
activated
路由组件被激活时触发。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元素时,在合适的时候给元素添加样式类名。
-
写法:
-
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- v 即
<transition>
里的name.hello-enter-active
- 元素进入的样式:
-
使用
<transition>
包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
-
备注:若有多个元素需要过度,则需要使用:
<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
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
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 封装读文件的方法
function getFile(fpath){
return new Promise(function(resolve,reject){
fs.readFile(fpath,'utf8',(err,dataStr)=>{
if(err) return reject(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'
async function getAllFile(){
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
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 项目的方式:
vite | vue-cli | |
---|---|---|
支持版本 | 仅支持 vue3 | 支持 3.x 和2.x |
基于 webpack? | no | yes |
运行速度 | 快 | 较慢 |
功能完整度 | 小而巧(逐渐完善 | 大而全 |
建议在企业级开发中使用? | 目前不建议 | 建议 |
组件化开发思想
根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。
好处:
-
提高了前端代码的复用性和灵活性
-
提升了开发效率和后期的可维护性
vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。
vue 组件组成结构
- template -> 组件的模板结构(必选)
- script -> 组件的 JavaScript 行为(可选)
<script> export default { name,data数据、methods方法...} </script>
- name 节点为当前组件定义一个名称(建议每个单词首字母大写
name:'MyApp'
- 在注册组件时:appponent(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 中,中支持定义多个根节点
组件的基本使用:
注册组件
-
全局注册
appponent('my-swiper',Swiper)
- 直接在template中以标签形式使用
<
my-swiper> - 使用频率高
-
局部注册
-
<script> import Search for '' export default{ 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次 | 操作 DOM |
beforeUpdate | 在组件被重新渲染之前 | 运行阶段 | 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 mitt@2.1.0 -S
2 创建公共的 eventBus 模块
import mitt from 'mitt'
const bus = mitt()
export default bus
3 在数据接收方自定义事件
import bus from './eventBus.js'
export default{
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' export default{ data(){ return { color:'red' } }, provide(){ return color: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'
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' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })
-
在
main.js
中创建vm时传入store
配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
4.基本使用
-
初始化数据、配置
actions
、配置mutations
,操作文件store/index.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.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 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
-
组件中读取vuex中的数据:
$store.state.sum
1 在模板里面写不用加 this {{}}
2 在脚本里面写要加 this
-
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$storemit('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
对话的方法,即:包含$storemit(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’])
1 import { createNamespacedHelpers } from 'vuex'
2 const { 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 = new Vuex.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
//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
-
开启命名空间后,组件中调用commit
//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
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() {
return this.todos.reduce((pre, current) => pre + (current.isDone ? 1 : 0), 0)
}
}
2 使用 filter
过滤
clearDone() {
this.todos = this.todos.filter(todo => !todo.isDone)
}
3 巧用 计算
属性
computed: {
isAll() {
return this.allNum ? this.allChecked : false
}
}
4 使用 every
判断
allChecked() {
return this.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/1
query参数:
拼在路径中的?后面: /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
- 语法降级
⭐️Vue3
vue3基础入门参考文章!必看
版权声明:本文标题:Vue2、Vue3知识总结---完整版✨ 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1729002603h1305570.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论