VCcode常用快捷键
生成浏览器文件.html的快捷键:!+回车键
代码格式化:Shift+Alt+F
向上或者向下移动一行:Alt+UP或Alt+down
清除:cls
终止在终端中正在运行的前台命令使用:Ctrl+C
打开终端的方法:
打开文件夹位置,输入cmd这个指令,可以打开终端
windows电脑打开终端 win+R 输入cmd 打开终端
vscode编辑器的配置
插件下载
//标签自动补全(tab键)
"emmet.triggerExpansionOnTab": true,
"emmet.includeLanguages": {
"javascript": "javascriptreact",
"wxml": "html"
}
第一章 vue基本命令
官网:vuejs.org
开发环境:VCcode
创建一个项目的命令:(该方法可以创建Vue2和Vue3
项目)
vue create 项目名
运行项目:
第一步:进入项目根目录
cd 项目名
第二步:运行项目
npm run serve
使用Vite创建Vue3
的项目:(该命令只能创建Vue3的项目)
npm init vue //执行完该命令后,会弹出提示,按照提示需求即可完成项目的创建
npm i
npm run dev
Vue实例
每一个Vue的程序,都应该从一个Vue实例开始
例如我们项目中src/main.js
文件中的以下代码,就是在创建一个Vue实例:
import Vue from 'vue' // 引入 Vue
import App from './App.vue' // 引入 App.vue 组件
Vue.config.productionTip = false // 关闭生产提示
new Vue({ // 创建一个 Vue 实例
render: h => h(App), // 编译解析 App 组件
}).$mount('#app') // 将 App 组件挂载(添加)到 id=app 的 div 中
第二章 模板语法
文本插值
双大括号:{{}}
数据绑定最常见的形式就是使用:{{}} 双大括号语法的文本插值
<span>{{message}}</span>
一般配合js中的data()来设置数据
<template>
<div class="hello">
<h3>2022.11.29</h3>
<h1>{{message}}</h1>
</div>
</template>
export default{
data(){
return{
message:"填写所要显示的文本" //注:此处的变量名message必须和上面双大括号里的一致
}
}
}
v-once
当数据改变时,插值处的内容不会改变。
v-html
原始HTML:双大括号会将文本数据解释为普通文本,而非HTML代码。为了输出真正的HTML,需要使用:v-html命令
<template>
<div class="hello">
<div>{{rawhtml}}</div> //只能以文本的形式显示出来
<div v-html="rawhtml"></div> //以网络连接的形式显示出来
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data(){
return{
rawhtml:"<a href='http://www.itbaizhan.com'>百战</a>"
}
}
}
</script>
注:在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。
请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容(script也属于HTML内容)。
v-bind
属性:双大括号语法不能在HTML属性中使用,然而,可以使用:v-bind指令(动态绑定属性内容)
v-bind可以简写成冒号(v-bind:id = :id)
<template>
<div class="hello">
<div v-bind:id="shuxingming"></div>
<div v-pre>你们遇到这种情况:<span>{{shuxingming}}</span>情况,我们叫做插值语法</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data(){
return{
shuxingming:1001
}
}
}
</script>
<!-- 静态绑定,直接传入字符串 -->
<img src="https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png" >
<!-- 动态绑定,签名添加一个冒号,值一定是表达式 -->
<img :src="url" >
<!-- 全称v-bind -->
<img v-bind:src="url" >
在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。
请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容(script也属于HTML内容)。
为了确保Web应用程序的安全性,我们需要采用多层次的防御策略,
包括但不限于:对用户输入进行严格的校验和转义、使用HTTPS协议加密传输、
限制访问权限和操作权限、采用最新的Web安全协议和技术等。
只有这样,才能有效保护用户隐私和数据安全。
v-pre
v-pre的用处就是告诉vue不需要解析当前行的内容
条件渲染
v-if
v-if:指令用于条件性的渲染一块内容,这块内容只会在指令的表达式返回true值的时候被渲染,返回false值不被渲染
v-else
v-else:可以使用v-else指令来表示v-if的else块
<div>
<h2>条件渲染</h2>
<h3 v-if="isRender">1. 我是通过 v-if 来渲染的</h3>
<h3 v-else-if="true">2. 我是通过 v-else-if 来渲染的</h3>
<h3 v-else>3. 我是通过 v-else 来渲染</h3>
</div>
v-show
v-show:另一个用于条件性展示的指令
注:v-if是真正的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建,如果条件为假时,则什么也不会做,只有条件为真时才开始渲染条件块。
v-show就简单的多,不管初始条件是什么,元素总会被渲染,并且只是简答地基于css进行切换(display:none)
v-if有更高的切换开销(性能消耗),v-show有更高的初始化渲染开销。
因此,如果需要非常频繁的切换,则使用v-show较好;
如果在运行时条件很少改变(初次渲染时),则使用v-if较好。
<template>
<div class="hello">
<p v-if="flag">今天开心</p>
<p v-else>今天不开心</p>
<p v-show="flag">今天是真的开心</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
flag: true
}
}
}
</script>
列表渲染
v-for
v-for:用于把一个数组映射为一组元素
v-for指令基于一个数组来渲染一个列表,v-for指令需要使用item in items
形式的特殊语法,
其中items是源数据数组名,而item则是被迭代的数组元素的别名。
<template>
<div class="hello">
<ul>
<li v-for="item in newsList">
{{ item.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
newsList:[
{
id:1001,
title:"今日新闻"
},
{
id:1002,
title:"今日新闻"
},
{
id:1003,
title:"今日新闻"
}
]
}
}
}
</script>
:key
当更新使用v-for渲染的元素列表时,它不会再把所有元素重新渲染一遍,而是直接渲染所被更新的元素,因此为了确保索引位置正确,需要为每一项提供一个唯一的key
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
或者:
<div v-for="(item.index) in items" :key="item.index">
<!-- 内容 -->
</div>
注意事项:
在vue2中,v-for的优先级更高
在vue3中,v-if的优先级更高
v-for
和v-if
一起使用时,v-if
的优先等级高于v-for
,所有能放在同一标签一起使用。
监听事件
v-on
v-on
:(通常使用缩写:@
)用来监听DOM事件,并在触发事件时执行一些Javascript,用法为:v-on:click="methodName"
或者简写:@click="methodName"
<template>
<div class="hello">
<button v-on:click="counter += 1">点击:counter={{counter}}</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
counter:1
}
}
}
</script>
v-on还可以接收一个需要调用的方法名称
<template>
<div class="hello">
<button v-on:click="counter += 1">点击:counter={{counter}}</button>
<button @click="clickHandle">按钮</button>
<p>{{message}}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
counter:1,
message:"消息通知"
}
},
methods:{
clickHandle(){
this.message="消息被撤回了"
//在事件中,读取data中的属性,是需要通过this.属性
}
/*clickHandle(event){
this.message="消息被撤回了"
event 是原生DOM event
console.log(event);
event.target.innerHTNL = "点击之后"
}*/
}
}
</script>
内联处理器中的方法,也叫事件传递参数
<template>
<div class="hello">
<button @click="say('hai')">say hi</button>
<button @click="say('what')">say what</button>
<ul>
//@click="clickItemHandle(item)"实现内联传递
<li @click="clickItemHandle(item)" v-for="(item,index) in names">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
names:["hai","hello","world"]
}
},
methods:{
say(data){
console.log(data); //通过data函数来传递参数:hai和what
},
clickItemHandle(item){
console.log(item);
}
}
}
</script>
template标签
与v-if搭配使用
<template v-if="true">
<h1>hello</h1>
<h1>你好</h1>
</template>
注意:v-show不能用在template身上(它是一个空标签,本身就无法渲染)
事件修饰符(.stop、.prevent)
.stop
:是阻止事件流的传播,封装了event.stopPropagation()
.prevent
:阻止标签默认行为,封装了event.preventDefault()
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为
<div id="app">
<div class="outer" @click="outclick">
我是外层
<!-- 阻止冒泡事件 -->
<div class="inner" @click.stop="inclick">
我是内层div
</div>
</div>
<hr>
<!-- 阻止默认事件用prevent -->
<a v-on:click.prevent="baidu" href="http://wwww.baidu.com">百度</a>
</div>
表单输入绑定
v-model:可在表单<input>、<textarea>、<select>元素上创建双向数据绑定
<template>
<div class="hello">
<input type="text" v-model="username"> //v-model="username"实现双向绑定
<input type="text" v-model.lazy="password">
<p>{{username}},{{password}}</p>
<button @click="clickGetUserName">获取用户名</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data(){
return{
username:"",
password:""
}
},
methods:{
clickGetUserName(){
console.log(this.username);
}
}
}
</script>
.lazy:在默认情况下,v-model在每次input事件触发后将输入的值与数据进行同步,可添加lazy修饰符,在输入完成后点击回车键才同步数据
.trim:自动过滤用户输入的首尾空白字符
.number:将用户输入的内容,通过 parseFloat() 方法转换为数字,如果无法转换,则保留原来数据
语法:v-model.lazy="message" v-model.trim="message"
第三章 计算属性(computed)
当我们在 HTML中,要通过比较复杂的操作(计算)来得到一条数据,这种情况下,Vue官方建议将这操作(计算)过程放到JS的计算属性中
computed不支持异步,当computed内有异步操作时是无法监听数据的变化。
计算属性擅长处理的场景是:多个数据影响一个数据 ----购物车商品结算。
基础语法
<script>
// 创建应用
const app = Vue.createApp({
// 初始化数据函数
data() { // 返回一个对象,对象中写初始化数据
return {
num: 0, // 初始化一个的数据
num1: 1,
};
},
// computed 计算属性,配置项是一个对象
computed: {
numx2() { // 是一个函数,返回值就是新生成的属性,只读属性
// 如果它依赖的属性每一个都没有变化,就不会重新计算
console.log('=================numx2');
return this.num * 2 + this.num1;
}
},
methods: {
multi2() {
console.log('=================multi2');
return this.num * 2;
},
changeNumx2() {
this.numx2 = 123;
}
},
});
// 挂载应用
app.mount('#app');
</script>
计算属性的缓存
计算属性的缓存,指的是当同一个计算属性被使用多次时,只会执行第一次。因为,当第一次计算出结果后,浏览器会将计算结果保存到内存中
后续再使用时,就可以直接读取内存中之前计算的结果。
但是!当计算属性中依赖的任意一个原数据发生改变时, 计算属性都会重新执行重新计算。
计算属性的修改
默认情况下,计算属性是不能修改的,一但修改,就会出现以下报错:
计算属性的set方法
<script>
// 创建应用
const app = Vue.createApp({
// 初始化数据函数
data() { // 返回一个对象,对象中写初始化数据
return {
firstName: '曾', // 初始化一个的数据
lastName: '阿牛',
};
},
computed: { // 计算属性配置项
// 返回函数的时候,计算属性是只读的,如果要使它可写,一定要用对象格式
// fullName() {
// return this.firstName + this.lastName;
// }
fullName: { // 有两个配置项,一个是get,读(getter),一个是set,写(setter)
get() {
return this.firstName + this.lastName;
},
set(fullName) { // 有一个参数,就是fullName的值
// console.log('=================fullName', fullName);
this.firstName = fullName[0]; // 取第一个字为姓
this.lastName = fullName.slice(1);
}
},
},
methods: { // 函数配置项
changeName() {
this.firstName = '张';
this.lastName = '无忌';
},
changeNameByFullName() {
this.fullName = '张无忌';
}
}
});
// 挂载应用
app.mount('#app');
</script>
computed 和 methods的区别
computed 和 methods 能实现一样的效果:通过旧的数据得到新数据
但是,computed 和 methods 是有区别的:
- computed 有缓存,多次使用同一个计算属性,只会执行一次(属性)
- methods 没有缓存,多次调用同一个方法,会执行多次(方法)
因此,computed 和 methods 的应用场景,分别是:
- computed:当我们希望通过一些旧数据进行计算得到一条新数据,同时并不会修改旧数据
- methods:当我们需要对旧数据进行修改时
第四章 侦听器(watch)
侦听器,用来侦听属性数据的变化。当侦听的数据发生变化时,会执行对应的侦听函数
侦听器可以监听data里面的属性,也可以监听computed的属性
基础语法:
每一个组件对象都有一个watch属性,用来设置组件的侦听器:
<script>
export default {
data() {
return {
num: 1
};
},
watch: { // 配置项是一个对象
//需要侦听谁,就写谁
// 函数的形式, 有两个参数,newValue是变化后的值,oldValue是上一次的值
num(newValue, oldValue) {
console.log("num 发生变化了");
}
},
};
</script>
停止侦听器
如果需要停止侦听器,不能使用选项配置,需要使用实例方法$watch函数。
$watch函数有两个参数,第一个参数是被监听的属性名称,第二个参数是一个函数,和选项配置里面的是一样的。
返回一个停止监听的函数,我们可以调用这个函数停止监听。
this.stopWatch = this.$watch('time', (cur, pre) => {
if (cur >= 45) {
console.log('下课了');
}
});
...
this.stopWatch(); // 停止监听
侦听引用类型数据
默认情况下,引用类型的数据如果没有改变引用地址,watch是无法侦听到数据内部的变化的
但是,我们可以通过以下两种方式来解决:
- 侦听单个属性变化
- 对引用类型数据进行深度侦听
export default {
data() {
return {
person: {
name: "张三",
age: 20,
},
};
},
watch: {
//侦听单个属性变化
"person.age"() {
console.log("person.age 发生变化了");
}
},
};
export default {
data() {
return {
person: {
name: "张三",
age: 20,
},
};
},
watch: {
//s如果侦听器需要额外的配置,需要写成对象类型。
//对引用类型数据进行深度侦听
person: {
handler(newValue, oldValue) {
console.log("person 发生变化了");
},
deep: true, // 深度侦听(主要靠它)
immediate: true, // 立即侦听
},
},
};
立即侦听
默认情况下,侦听器在首页进入时不会执行,如果我们希望侦听器能够在首页一加载就执行一次,那么可以设置“立即侦听”
语法:immediate: true
在所需要执行立即侦听的函数里添加即可
watch和computed的区别
watch监听的函数不用返回值,computed有返回值,并依赖函数里面提到的属性,并且生成了一个新属性
computed更像是一种依赖,别人变化,自己就跟着变
watch更主动,主动取监听某一个属性的变化
computed支持缓存,相依赖的数据发生改变才会重新计算;watch不支持缓存,只要监听的数据变化就会触发相应操作
computed不支持异步,当computed内有异步操作时是无法监听数据变化的;watch支持异步操作
第五章 组件
单文件组件:又名.vue
文件,是一种特殊的文件格式,它允许将vue组件的模板、逻辑与样式封装在单个文件中
在components文件下创建.vue文件
<template>
<h3>单文件组件</h3>
</template>
<script>
export default{
name:"hello"
}
</script>
<!-- scoped:如果在style中添加此属性,就代表着,当前样式只能在当前组件中生效 -->
<style scoped>
h3{
color:red;
}
</style>
加载组件:
在App.vue文件下加载
第一步:引入组件:import 组件名 from '组件目录地址'
第二步:挂载组件:components:{组件名}
第三步:显示组件:<组件名/>
<template>
<MyComponent/> <!--第三步:显示组件 -->
</template>
<script>
import MyComponent from './components/MyComponent.vue'; //第一步:引入组件
export default {
name: 'App',
components:{ //第二步:挂载组件
MyComponent
}
}
</script>
<style>
</style>
组件样式
静态样式
全局样式和局部样式
在Vue组件中,静态样式分为全局样式和局部样式
- 全局样式:在任何组件中定义的样式,都会作用于所有组件
- 局部样式:在当前组件中定义的样式,只能作用与当前组件
<style>
h1 {
color: red;
}
</style>
scoped
:如果在style中添加此属性,就代表着,当前样式只能在当前组件中生效
<style scoped>
h1 {
color: red;
}
</style>
外部样式和内部样式
我们可以将组件的样式写在组件内的style
标签中,也可以在外部创建一个.css
文件,来编写外部样式
外部样式通过引入方式的不同,也可以分为全局样式和局部样式
引入外部的全局样式:
<style>
@import './index.css'
</style>
引入外部的局部样式:
<style scoped src ='./index.css'>
</style>
动态样式
动态class
Vue中提供了对象的形式来设置动态class,基础语法如下:
<h2 :class="{ active: false }">动态样式</h2> //active:为样式。false:为判断,判断它不生效
语法说明:
- class需要通过
v-bind
指令进行动态绑定 - 对象的键,是我们想要使用的class选择器的名称(例如上例中的
active
) - 对象的值,是一个布尔值,通过true或false来控制动态的class
是否生效
动态style
Vue中提供了对象的形式来设置动态style,基础语法如下:
<h2 :style="{ color: fontColor}">动态的 style</h2>
语法说明:
- style需要通过
v-bind
指令进行动态绑定 - 对象的键,是我们需要设置的CSS样式的名称
- 对象的值,是我们需要设置的CSS样式的值,是一个动态的数据(例如上例的fontColor)
设置多样式的变化
组件交互
props
使用props可以实现从App.vue文件中传递参数到自己所创建的组件中(把父文件参数传送到子文件中)
注意:由于Vue中有“单向数据流”的概念,因此在Vue子组件中
,所有的props的值都不能修改
在App.vue文件如下操作:
<template>
<MyComponent :title="title" :names="names"/> //使用子组件:进行静态显示::title="title" :names="names"传递过去的内容
</template>
<script>
import MyComponent from './components/MyComponent.vue'; //引入子组件
export default {
name: 'App',
//填写数据
data(){
return{
title:"我是一个标题",
names:["hai","hello","word"]
}
},
components:{ //挂载子组件
MyComponent
}
}
</script>
<style>
</style>
在自己创建的组件MyComponent.vue中进行如下操作:
<template>
<h3>我是单文件</h3>
<p>{{title}}</p>
<ul>
<li v-for="(item,index) in names" :key="index">{{item}}</li>
</ul>
</template>
<script>
export default{
name:"MyComponent",
//props: ['names', 'title'], //当不需要验证时,通过数组字符串接收,可以使用this.name进行访问
// 通过对象进行接收并配置更多选项0
props:{ //使用props来接收父组件传过来的数据
title:{
type:String, //指定数据类型
default:"", //指定默认值,当父组件没有传递该数据,则子组件可以使用默认值
validator(value) { // 是一个函数, 参数value就是当前的值, 返回true,验证通过,返回false,验证不通过
if (value.length < 3) {
return false;
}
return true;
},
},
names:{
type:Array,
//数组和对象必须使用函数进行返回
default:function(){
return[]
}
}
}
}
</script>
<style>
h3{
color: aqua;
}
</style>
$emit
自定义事件可以在组件中反向传递数据,也就是说可以把子组件中的数据传送到父组件中(也可以进行子组件修改父组件的数据)
在子组件中填入数据:
<template>
<h1>子组件的数据传递到父组件</h1>
<!-- $emit不能直接进行数据传递,需要一个事件来触发它进行数据传递 -->
<button @click="sendClickHandle">点击传递</button>
</template>
<script>
export default{
name:'hello',
data(){
return{
message:"我是MyComponent的数据"
}
},
emits: ['onEvent'],// 通过数组字符串接收,可以使用this.$emit进行发送
methods:{ //声明一个事件
sendClickHandle(){
//$emit来进行数据传递,其内有两个参数
//参数1:字符串:理论上是随便的,但是必须要有意义
//参数2:传递的数据
this.$emit("onEvent",this.message)
}
}
}
</script>
<style scoped>
h1{
color: rebeccapurple;
}
</style>
把数据传递到父组件App.vue中
<template>
<!-- @onEvent是我们自定义的一个事件,onEvent是子组件中$emit函数的第一个参数 -->
<MyComponent @onEvent="getDataHandle" />
<p>传递的数据为:{{message}}</p>
</template>
<script>
import MyComponent from './components/MyComponent.vue';
export default {
name: 'App',
components: {
MyComponent
},
methods: {
getDataHandle(data) {
console.log(data) //在控制台输出数据
this.message = data //输出到桌面
}
},
data(){
return{
message:""
}
}
}
</script>
<style>
</style>
注:在实际开发过程中,我们通常把自定义事件名和方法名命名成一个相同的名称
<MyComponent @getDataHandle="getDataHandle" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.staticfile.org/vue/3.0.5/vue.global.js"></script>
<style>
</style>
</head>
<body>
<div id="app" class="box">
<h1>我是父组件</h1>
<h2>我的财富是{{money}}</h2>
<hr>
<!-- 3、使用子组件 -->
<child :name="name" @requiremoney="requiremoney" :requiremoney="requiremoney"></child>
</div>
<script>
// 1、定义子组件
const Child ={
data() {
return {
money:0,
}
},
props:["name",'requiremoney'], //父组件传到子组件的元素
template:`<h3>我是子组件</h3>
<h3>我叫{{name}}</h3>
<h3>我有{{money}}元</h3>
<button @click="yaoqian">要钱</button>
<button @click="yaoqianFun">函数要钱</button>
`,
emits:["requiremoney"], //子传父
methods: {
yaoqian(){
this.$emit("requiremoney",1000)
this.money += 1000
},
yaoqianFun(){
this.requiremoney(1500)
this.money += 1500
}
},
}
// 定义模块
const app = Vue.createApp({
data() {
return {
money:10000,
name:"千峰",
}
},
components:{
Child, //2、挂载子组件
},
methods:{
requiremoney(data){
// console.log(data)
this.money -= data
}
}
}).mount('#app')
</script>
</body>
</html>
组件间的其它通信方式
组件通信,也就是组件之间的传值
通信方式
- 父组件给子组件传值:props
- 子组件给父组件传值:自定义事件(@emit)
- 非父子组件之间传值:事件总线(中央消息总线传值)、Vuex(状态机)
- ref:用于注册元素或子组件的引用
- 依赖注入:要为组件后代提供数据,需要使用到 provide 选项;子组件使用 inject 选项来注入上层组件提供的数据
ref 通过引用的方式进行传值
获取子组件的引用
ref 用于注册元素或子组件的引用。
使用选项式 API,引用将被注册在组件的 this.$refs 对象里
放在DOM元素上,获取DOM节点,放到组件上,获取子组件的实例,可以直接使用子组件的属性和方法。
<Child ref="child"></Child>
...
<script>
...
this.$refs.child.getName(); // 最好是调用子组件的方法查看或者修改子组件的数据
this.$refs.child.setName('六耳猕猴');
// this.$refs.child.name = '六耳猕猴'; // 这种不推荐,但也得知道,你代码规范,不见得同事也规范。
...
</script>
依赖注入
如果我们有一个数据,需要各级的组件都能访问到,我们可以通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的。
依赖注入提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
相当于提供了一个全局变量的样子,在所有的子组件都可以访问到。实际例子相当于的家谱图,老祖宗留了一本家谱图,想知道姓什么的时候去翻一下就知道了。
要为组件后代提供数据,需要使用到 provide 选项:
{
data(){ return { surname: '朱' } },
// 实际情况二取一
provide: {
surname: '朱',
getSurname: this.getSurname, // 也可以传递方法
},
provide() { // 也可以使用函数的形式
return {
surname: this.surname,
getSurname: this.getSurname, // 也可以传递方法
}
}
}
子组件使用 inject 选项来注入上层组件提供的数据
{
// 实际情况二取一
inject: ['surname', 'getSurname'], // 通过数组的形式获取
inject: {
surname: {
from: 'surname', // 当与原注入名同名时,这个属性是可选的
default: '朱', // 可以设置默认值
},
},
created() {
console.log(this.surname) // injected value
}
}
中央消息总线传值
只有在Vue2中才有中央消息总线的概念,Vue3需要使用event来模拟,我们可以使用mitt
Vue2
中央消息总线相当于我们的电话系统,我们需要鉴定消息的地方放一台电话,给这个电话一个号码,其他地方需要的时候拨打这个号码,我们监听的电话就能收到消息了。
Vue2中Vue自身就是一个消息总线,使用new Vue()生成一个消息总线实例,实例有两个方法on和eimit,使用on进行监听,使用eimit来发送消息。
$on 这个函数有两个参数,第一个参数式消息名称(相当于电话号码),第二个参数式监听到消息后执行的回调函数,参数是发送的数据,发送了多少个就用多少个接收。
$emit 这个函数至少有一个参数,第一个参数式消息名称(相当于电话号码),后面的是参数,参数可以有0个或多个。
Vue.prototype.$bus = new Vue(); // 生成一个消息总线并设置为全域变量,用this.$bus可以访问到
// 其实因为我们已经创建了一个Vue实例,可以直接使用
beforeCreate() {
Vue.prototype.$bus = this; // 用已经创建的Vue实例
}
// 在父组件的created函数里面监听
created() {
this.$bus.$on('requestMoneyByBus', count=>{
...
});
}
// 子组件发送消息
this.$bus.$emit('requestMoneyByBus', 100);
Vue3中中央消息总线(模拟)
Vue3 本事是不支持消息总线,这里我们使用eventemitter3来模拟消息总线。我们还使用了依赖注入i提供了一个全局的$bus;
<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
需要在根组件提供一个全局的$bus
<script>
provide: { // 注入一个全局的$bus
$bus: mitt(),
},
</script>
在父组件中注入$bus,并在created中监听
<script>
inject: ['$bus'],
...
created() {
this.$bus.on('requestMoneyByBus', count=>{ // 注意这里是on,不是$on
...
});
},
</script>
在子组件中注入$bus,并在需要的时候发送请求
<script>
inject: ['$bus'],
...
this.$bus.emit('requestMoneyByBus', 100); // 注意这里是emit,不是$emit
</script>
插槽
插槽就是子组件预留给父组件放入其他组件的一个位置。
插槽类型有:
单个(匿名)插槽
具名插槽:具名插槽(v-slot属性,#简写)
作用域插槽:作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签template中通过v-slot指令来接受数据。
组件的生命周期函数(Vue3)
组件的生命周期函数有八个,八个函数可分为四类:
创建时:beforeCreate、created
渲染时:beforeMount、mounted
更新时:beforeUpdate、updated
卸载时:beforeUnmount、unmounted
<template>
<p>组件生命周期</p>
</template>
<script>
export default{
data(){
return{
message:""
}
},
beforeCreate(){ // 创建前 数据还没有初始化
// 注意点:在此时不能获取data中的数据,也就是说 this.info 得到的是null
console.log("beforCreate:组件创建之前")
},
created(){ // 已创建 数据已经初始化了,可以做一些网络请求,定时器,延时器的初始化
// 这里就可以获取data中的数据
console.log("created:组件创建完成")
},
beforeMount(){ // 挂载前 获取不到dom
console.log("beforMount:组件渲染之前")
},
mounted(){ // 已挂载 获取到dom,对dom进行操作。
console.log("mounted:组件渲染完成")
},
beforeUpdate(){
console.log("beforupdata:组件更新之前");
},
updated(){
console.log("updated:组件更新完成")
},
beforeUnmount(){ // 清理
console.log("beforeUnmount:组件卸载之前")
},
unmounted(){
console.log("unmounted:组件卸载之后")
}
}
</script>
<style>
</style>
其中:
- 我们可以在 created 或 mounted 中来向后端发送网络请求,获取页面的初始数据
- 从created 开始后,才能访问data中的数据
- 从mounted 开始后,才能获取页面中的结点
Vue2.x的区别
unmount -> destroy
beforeUnmount -> beforeDestroy // 销毁前
unmounted -> destroyed // 已销毁
生命周期的执行顺序
加载渲染过程:
1.父组件 beforeCreate
2.父组件 created
3.父组件 beforeMount
4.子组件 beforeCreate
5.子组件 created
6.子组件 beforeMount
7.子组件 mounted
8.父组件 mounted
更新过程:
父组件 beforeUpdate
2.子组件 beforeUpdate
3.子组件 updated
4.父组件 updated
销毁过程:
父组件 beforeUnmount
2.子组件 beforeUnmount
3.子组件 unmounted
4.父组件 unmounted
动态组件
基础语法
Vue中提供了 来实现动态组件
<template>
<div>
<button @click="componentId = 'ChildA'">组件A</button>
<button @click="componentId = 'ChildB'">组件B</button>
<button @click="componentId = 'ChildC'">组件C</button>
<div>
<!-- 动态组件 -->
<component :is="componentId"></component>
</div>
</div>
</template>
<script>
import ChildA from "./ChildA.vue";
import ChildB from "./ChildB.vue";
import ChildC from "./ChildC.vue";
export default {
components: {
ChildA,
ChildB,
ChildC,
},
data() {
return {
componentId: "ChildA",
};
},
};
</script>
<style>
</style>
动态组件的生命周期
默认情况下,动态组件在切换时,切换走的组件,会被销毁;显示的组件,会被创建,组件之间都是不断的销毁和重建。
因此,在组件切换走时,会触发两个生命周期函数:
- beforeUnmount
- unmounted
在显示组件时,会触发四个生命周期函数:
- beforeCreate
- created
- beforMount
- mounted
$nextTick()
绑定在实例上的 nextTick() 函数。
和全局版本的 nextTick() 的唯一区别就是组件传递给 this.$nextTick() 的回调函数会带上 this 上下文,其绑定了当前组件实例。
<div ref="num">{{ count }}</div>
<script>
...
this.count++;
console.log(this.count) // 2
console.log(this.$refs.num.innerHTML) // 1
this.$nextTick(() => { // 可以获取真实的DOM节点的值,瀑布流布局
console.log(this.$refs.num.innerHTML) // 2
})
...
</script>
第六章、数据变更检查
在Vue中,当页面中使用的数据发生变化时,页面会重新自动渲染
这是因为,当一个Vue组件被创建时,Vue会将data中的所有数据都加入到“响应式系统”中。后继,当data中的数据发生变化时,“响应式系统”就会通知页面进行更新
但是,有一些数据的变化,是响应式系统检测不到的,一旦数据变化后,响应式系统没有检测到,那么页面就不会更新
无法检测到的数据变化
Vue的响应式系统无法检测到数组和对象的以下四种变化
- 无法检测到对象属性的新增
- 无法检测到对象属性的删除
- 无法检测到对数组长度的修改
- 无法检测到通过下标操作数组
解决方法
对象属性的新增
//新增一个age属性时:
//ES6方法:
this.person = {
...this.person,
age: 20
}
//官方方法:($set:只有Vue2支持,Vue3已经移除)
this.$set(this.person, 'age', 20) //this.$set(要修改的对象, '新增的属性名', 属性值)
对象属性的删除
//删除name属性
//官方方法:
this.$delete(this.person, 'name') //this.$delete(要修改的对象, '要删除的属性名')
修改数组的长度
//想要将数组长度设为:0
this.students = [] //直接赋值一个空数组
//修改数组长度
this.students.splice(1) //使用splice()方法
通过下标操作数组
//把下标为0的元素修改为'王五'
this.students.splice(0, 1, '王五') //使用splice()方法
//官方方法
thsi.$set(this.students, 0, '王五')
第七章、Vue全家桶(新版)
Vue全家桶创建项目(使用VueCLI来创建项目)
VueCLI:
官网地址
Vue2 时期官方的一款脚手架工具。不再推荐。
虽然不推荐了,但是还是可以使用它来创建 Vue2、Vue3 的项目。)
内部使用的构建工具是 Webpack
接着下载第三方插件:
下载axios插件
下载element插件
样式穿透
当我们对Element UI框架的组件样式,进行局部样式(scoped)
的设置时,可能会出现的问题是:
局部样式无法作用到UI框架内部的标签身上。
这种情况下,我们可以使用样式穿透来解决: >>>
<style scoped>
.avatar-uploader >>> .el-upload { /* >>> 这是加上了样式穿透效果 */
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader >>> .el-upload:hover {
border-color: #409eff;
}
</style>
安装时的常见报错:
解决方法:
- 先卸载:npm uninstall node-sass sass sass-loader
- 再重新安装:npm install node-sass sass sass-loader -D
解决方法:
- 以管理员方式打开windows powershell:输入下列代码
- set-ExecutionPolicy RemoteSigned (接下来选择全是:A)
1、路由与SPA
SPA的概念
SPA指的是“单页应用”,指的是在整个项目中,只有一个.html
页面,例如:Vue项目中的public/index.html
在SPA中,是通过切换组件,来达到类似于多页应用中页面跳转的效果
路由(Vue Router)
因为SPA中只有一个页面,那么在组件进行切换时,我们需要一个工具来进行辅助管理,帮助我们管理组件与浏览器之间的关系,所有Vue Router
路由插件,就为我们提供了这样的功能:
- 可以通过路由来管理路径和组件之间的对应关系
- 当路径发送改变时,路由会切换页面中对应的组件
2、路由的配置(router)
基础配置
在src/router/index.js
文件中,有一个数组routes
,专门来配置项目中所有页面路由:
//引入对应组件的路径
import LoginView from '../views/login/LoginView.vue'
import HomeView from '../views/home/HomeView.vue'
import RegisterView from '../views/register/RegisterView.vue'
Vue.use(VueRouter)
// 用于配置项目中所有需要页面(组件和路径)
const routes = [
{
path: '/',
redirect: '/home' //配置路由重定向:
},
{
path: '/home',
name: 'home',
component: HomeView
},
//这种写法是直接在配置中引入路径
//{
// path: '/home',
// component: () => import('../views/home/HomeView.vue')
// },
{
path: '/login',
name: 'Login',
component: LoginView
},
]
数组中每一个对象,就对应着项目中一个页面,其中,每一个路由对象都有两个必须属性:
- path:路由路径,指的是浏览器中每一个页面的路径(名字是自定义:路径建议首字母不需要大小)
- component:组件路径,指的是浏览器中需要渲染的组件(首字母必须大小)
- name :(可选属性),路由自定义名称(建议首字母大写)
redirect(路由重定向)
:指的是我们可以将一个路由重定向到另外一个路由。这样,每当用户访问原路由时,都会直接跳转到重定向的新路由
路由出口
当组件加载完成后,我们需要通过路由出口<router-view>
来告诉浏览器组件渲染在什么位置
通常,我们会将最外层的路由出口配置在App.vue
中:
<template>
<router-view></router-view> <!-- 设置路由显示出口 -->
</template>
<script>
</script>
<style>
/* 引入外部单独创建的styles文件下的scss文件 */
@import './styles/global.scss';
</style>
有了路由的基本配置和路由出口,我们就可以在浏览器中手动切换路径,来实现页面组件的切换
嵌套路由(设置子路由)
在src/router/index.js
文件中
//引入对应组件的路径
import LoginView from '../views/login/LoginView.vue'
import HomeView from '../views/home/HomeView.vue'
import RegisterView from '../views/register/RegisterView.vue'
//引入子路由:
import StudentsList from '../views/home/students/StudentsList.vue'
import StudentsAdd from '../views/home/students/StudentsAdd.vue'
import NotFound from '../views/404/NotFound.vue'
Vue.use(VueRouter)
// 用于配置项目中所有需要页面(组件和路径)
const routes = [
{
path: '/login',
name: 'Login',
component: LoginView
},
{
path: '/register',
name: 'Register',
component: RegisterView
},
{
path: '/home',
//name:'Home', //删除掉此属性
component: HomeView,
//挂载子路由:
children: [
// 默认子路由
{
path: '', //path 文空字符串
name: 'Home', //把父级路由的 name 属性,设置在默认子路由身上,删除掉父级name
component: Charts,
},
{
// 子路由的 path 不能以 / 开头
path: 'studentsList',
name: 'StudentsList',
component: StudentsList,
},
{
path: 'studentsAdd',
name: 'StudentsAdd',
component: StudentsAdd,
},
},
// * 表示可以匹配任何路径,放在所有的路由配置最后,当前面的路由都没能匹配上后,就会匹配它,
//在它对应的组件内编写路由加载信息:提示报错信息:404
{
path: '*',
name: 'NotFound',
component: NotFound
}
]
以上两个子路由配置成功后,我们就可以在浏览器中通过/home/studentslist
和 /home/studentsAdd
来访问对应的路由
最后,在对应的父组件中显示配置的子路由,也需要配置一个路由出口:<router-view>
<router-view></router-view> <!-- 设置路由显示出口 -->
路由的跳转及传参
Vue Router 中提供了两种方式来实现路由的跳转
1、组件跳转(标签跳转)
2、API跳转(方法跳转)push()、replace()
组件跳转
Vue Router中提供了一个<router-link>
组件来实现路由之间的跳转:(相当于a标签)只能在html内使用
<router-link to="/所要跳转到的页面的路径:path">没有账号?去注册</router-link>
如果要跳转到某一个二级路由,路径必须配置完整的路径:
<router-link to="/home/studentsList">没有账号?去注册</router-link>
API 跳转:push()、replace()
<template>
<div>
//方法一:直接引入
//<button @click="$router.push("/login")">注册</button>
//方法二:调用方法:register()
<button @click="register">注册</button>
</div>
</template>
<script>
export default {
methods: {
register() {
// 注册成功后跳转到登录页
this.$router.push("/login"); //this.$router.push("/所要跳转到的页面的路径:path");
},
},
};
</script>
push()
跳转完成后,用户可以通过浏览器的返回按钮,返回到上一页。
methods: {
register() {
// 注册成功后跳转到登录页
this.$router.replace("/login"); //this.$router.replace("/所要跳转到的页面的路径:path");
},
},
replace()
跳转后,会将上一个页面的访问记录删除掉,因此用户无法再返回到上一个页面
路由的返回
返回上一级
this.$router.back()
重新回到下一级
this.$router.forward()
横跨历史
this.$router.go(n)
// 向前移动一条记录,与 this.$router.forward() 相同
this.$router.go(1)
// 返回一条记录,与 this.$router.back() 相同
this.$router.go(-1)
// 前进 3 条记录
this.$router.go(3)
// 如果没有那么多记录,静默失败
this.$router.go(-100)
this.$router.go(100)
路由的传参(主要有两种方式:query、params)
如果要携带参数时:
<!--字符串形式-->
<router-link to="/cart?day=30">显示最近30天添加的物品</cart-link>
<!-- 对象形式-->
<router-link :to="{path: '/cart', query: {day:30}}">显示最近30天添加的物品</cart-link>
query传参:
形成的路径:/cart?day=30
路由配置文件中(route.js)的格式:{path: ‘/cart’, component: Cart}
传递的方式:
//对象的形式并且带参数
this.$router.push('/cart?day=30');
// 等价于 this.$router.push({path:'/cart?day=30'});
this.$router.push({path: '/cart', query: {day: 30}});
参数的接收:在 Vue 实例中,可以通过 this.$route 获取路由的参数。
console.log(this.$route.query.day); // 30
params传参:
形成的路径:/cart/30
路由配置文件中(route.js)的格式: { path: “/cart/:day”,component: Cart, name: “cart”}
传递的方式:
<!--字符串形式,在 path 后面跟上对应的值 格式: 路由地址/参数1/参数2 -->
<router-link to="/cart/30">显示最近30天添加的物品</cart-link>
<!--对象形式-->
<router-link :to="{name:'cart',params:{day:30}}">显示最近30天添加的物品</cart-link>
<!--在传递params参数并目使用对象的写法时,不再是path属性了,而是name配置项,而且必须是name,如果是path配置项,会报错-->
使用API跳转时,解决重复点击触发同一路由跳转报错问题(Vue2)
- 方法一:vue-router 降级处理(不推荐)
npm i vue-router@3.0.7
- 方法二:直接在push方法最后添加异常捕获 .catch(err=>{} ,例如:
<li
@click="$router.push('/home').catch(err=>{})">首页
</li>
- 方法三:直接修改原型方法push
//直接把这段代码复制粘贴到 router/index.js中的 vue.use(VueRouter)之前
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function(location){
return originalPush.call(this, location).catch(err => {})
};
3、动态路由
大部分时候,我们项目中的路由都普通路由,用户在浏览器中访问一个页面时,浏览器的路径必须和路由配置的路径一致,
才能匹配成功,才能进入到对应的页面,
但是,我们在实际研发中,也需要使用“动态路由”。动态路由,指的就是路由的路径中,有一部分内容是可以动态发生变化的。
1.动态路由跳转
export default{
//跳转到学生修改页面,同时将要修改的学生的 _id 通过路由进行传递
handleEdit(_id) {
// this.$router.push("/home/studentsUpdate/" + _id); //有两种写法
this.$router.push(`/home/studentsUpdate/${_id}`);
},
}
2.配置动态路由
const routes = [
path: '/home',
component: HomeView,
children: [
//动态路由的配置
{
path: 'studentsUpdate/:_id', //path: '路径名 /: 任意变量名'
name: 'StudentsUpdate',
component: StudentsUpdate,
},
]
说明:动态路由中,:_id
用来匹配路径中动态的内容,其中 _id
可以是任意变量名
有了第一二步,我们已经可以完成两个页面之间的跳转了
3.获取动态路由的参数
通常,我们会用动态路由中动态的那一部分,来实现路由之间的传参。因此,我们需要在组件中来接收路由路径中的参数
console.log(this.$route.params) //this.$route.属性名
$router 和 $route 的区别
- $router 是当前整个项目的路由实例对象,主要用于提供一些路由方法,例如路由跳转
- $route 是当前页面的路由信息对象,主要用于提供当前路由的相关信息,例如路径、参数等
4、路由的其它配置
路由重定向
指的是我们可以将一个路由重定向到另外一个路由。这样,每当用户访问原路由时,都会直接跳转到重定向的新路由
404路由
当用户访问的路由,和我们项目中的任何一个路由都不匹配时,我们希望能给用户显示一个404
的提示
我们可以在所有路由配置的最后,设置一个 *
路由。当用户访问的路径和前面所有的路由都匹配不上时,就会匹配到 *
路由
路由懒加载
指的是当用户访问当前路由时,才开始加载对应的组件。
通常,我们会将一些用户访问率比较低,且页面结构比较简单的组件,用来做路由懒加载。例如:注册页面、404页面
import Vue from 'vue'
import VueRouter from 'vue-router'
import { MessageBox } from 'element-ui';
import api from '@/api';
import store from '@/store';
//引入对应组件的路径
import LoginView from '../views/login/LoginView.vue'
import HomeView from '../views/home/HomeView.vue'
import StudentsList from '../views/home/students/StudentsList.vue'
import StudentsAdd from '../views/home/students/StudentsAdd.vue'
import StudentsUpdate from '../views/home/students/StudentsUpdate.vue'
import MajorsList from '../views/home/majors/MajorsList.vue'
import Charts from '../views/home/Charts.vue'
// import RegisterView from '../views/register/RegisterView.vue'
// import NotFound from '../views/404/NotFound.vue'
//路由懒加载的写法
const RegisterView = () => import('../views/register/RegisterView.vue');
const NotFound = () => import('../views/404/NotFound.vue');
Vue.use(VueRouter)
// 用于配置项目中所有需要页面(组件和路径)
const routes = [
{
//配置路由重定向
path: '/',
redirect: '/home'
},
{
path: '/login',
name: 'Login',
component: LoginView
meta:{
noAuth:true //用来判断是否需要登录后才显示,true:不需要登录就能显示
}
},
{
path: '/register',
name: 'Register',
component: RegisterView
meta:{
noAuth:true //用来判断是否需要登录后才显示,true:不需要登录就能显示
}
},
{
path: '/home',
component: HomeView,
meta:{
noAuth:false, //需要登录才能显示
},
children: [
// 设置默认子路由
{
path: '',
name: 'Home',
component: Charts,
},
{
// 子路由的 path 不能以 / 开头
path: 'studentsList',
name: 'StudentsList',
component: StudentsList,
meta: {
title: ['首页', '学生管理', '学生列表'],
}
},
{
path: 'studentsAdd',
name: 'StudentsAdd',
component: StudentsAdd,
meta: {
title: ['首页', '学生管理', '新增学生'],
isKeepAlive: true
}
},
// 动态路由的配置
{
path: 'studentsUpdate/:_id', //path: '路径名 /: 任意变量名'
name: 'StudentsUpdate',
component: StudentsUpdate,
meta: {
title: ['首页', '学生管理', '修改学生']
}
},
{
path: 'majorsList',
name: 'MajorsList',
component: MajorsList,
meta: {
title: ['首页', '专业管理', '专业列表']
}
}
]
},
{
path: '*', // * 表示可以匹配任何路径,当前面的路由都没能匹配上后,就会匹配它,提示报错信息
name: 'NotFound',
component: NotFound
}
]
//路由模式:history模式
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
路由模式
Vue Router 中提供了两种路由模式:
- hash:浏览器路径中会自动添加 #
- history:浏览器路径中没有 #
//路由模式:history模式
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
如果需要切换到 hash 模式,直接将 mode 和 base 去掉即可
5、路由元信息
配置路由对象时,除了常规的 path、component
等属性外,还有一个重要的属性:路由元信息———— meta
1.设置路由元信息
meta 属性的值,是一个对象,对象中可以定义任意属性。当我们需要给不同的路由对象添加一些不同的数据时,就可以使用路由元信息
{
path: 'studentsList',
name: 'StudentsList',
component: StudentsList,
meta: {
//任意数据
}
}
2.获取路由元信息
在组件中,可以通过 this.$route 获取当前页面的路由对象,因此,我们可以通过以下方式来获取路由对象身上的元信息:
this.$route.meta
3.应用场景
- 面包屑
- 路由组件的缓存
Vue中提供了一个 组件,用来包裹其他组件,所有被 包裹的组件,在切换时都会缓存组件自己的状态(不会被销毁)
例如,我们可以用 组件将所有子路由包裹起来:
//Vue(2)中的使用方式
<keep-alive>
<router-view v-if="isKeepAlive"></router-view> //添加一个判断,是否需要缓存
</keep-alive>
通过上述代码处理后,项目中所有的子路由组件都会被缓存下来,但在实际开发过程中,我们可能只需要一部分子组件为缓存状态
在Vue3中,现在必须通过 v-slot API 在 RouterView 内部使用
//Vue(3)中的使用方式
<router-view v-slot="{Component}">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
{
path: 'studentsAdd',
name: 'StudentsAdd',
component: StudentsAdd,
meta: {
title: ['首页', '学生管理', '新增学生'],
//用来设置在 keep-alive 是否需要进行组件缓存
isKeepAlive: true
} //然后在通过 this.$route.meta 来获取它,最后做一个 if 判断,如上面代码
}
keep-alive
组件,用来包裹其他组件,所有被 包裹的组件,在切换时都会缓存组件自己的状态(不会被销毁)
例如:我们可以使用 搭配动态组件 ,或者搭配路由出口 来使用
生命周期:
因为被包裹的组件,不会被销毁,那么也就意味着重新进入时,也不会重新创建,
也就意味着,组件销毁阶段和创建、挂载阶段的生命周期函数都不会执行。
但是,被包裹的组件,默认会新增两个生命周期函数:
- activated:组件激活时,即进入组件时
- deactivated:组件失效时,即离开组件时
属性:
标签身上,提供了一些属性,来对缓存数组进行筛选:
- include:设置需要缓存的组件名称
- exclude:设置不需要缓存的组件名称
<keep-alive include="ChildA">
<component :is= "Tab"> <component>
</keep-alive>
6、导航守卫
指的是用来控制路由跳转时,能否正常通过,完成跳转。
导航守卫的分类
我们可以将Vue Router 路由中导航守卫大致分为三类:
- 全局守卫:作用于所有路由(beforeEach(全局前置守卫)、afterEach(全局后置守卫))
- 路由独享守卫:作用于指定的路由(beforeEnter:只有前置)
- 组件内的守卫:作用于组件所在的路由(分别有前置守卫,后置守卫,路由改变守卫)
1.路由独享守卫
const routes = [
path:'/home',
component:HomeView,
//添加导航守卫
beforeEnter:(to,from,next) => {
},
children:[...]
]
语法说明:
beforeEnter
:是路由独享守卫中的前置守卫
每一个守卫函数都有三个参数,分别是:to(进入的路由对象),from(离开的路由对象),next(操作路由跳转的方法)
2.全局守卫
//路由模式:history模式
const router = new VueRouter({
......
})
// 添加全局守卫,判断页面是否需要登录后才能显示
router.beforeEach((to,from,next)=>{
if (to.meta.noAuth) { //如果noAuth为true,直接进入
next()
}else if(!localStorage.userId){ //如果未登录,跳转到登录页面
router.push('/login')
}else{ //把个人信息存储到全局状态vuex中
axios.post('/api/detail/member',{id:localStorage.userId}).then(res=>{
const data = res.data
if (!data.success) {
router.push('/login')
}else {
store.commit('setPersonal',data.result) //把个人信息存储到全局状态vuex中
next()
}
})
}
网络请求:axios
网络请求,指前端向后端服务器发送请求,来对数据库的数据进行操作
在现在的前端技术中,可以用来发送网络请求的方式有很多种:
- AJAX(ES5)
- axios(第三方插件)
- Fetch(ES6)
下载
axios是一个第三方的插件,因此我们在任何项目中,如果需要使用axios,都得先进行下载
npm i axios
使用
1、引入axios
在所需要使用的页面进行引入:
import axios from "axios";
2、基本用法
//第二步:再确定什么时候调用该方法来获取数据
// 生命周期函数:组件创建完成,通过调用 getStudents() 来获取学生数据
created() {
this.getStudents();
},
methods:{
//第一步:先写获取数据的方法
async getStudents(){ //也就是说:await 和 async 必须同时搭配使用
const res = await axios({ //await:等待获取的结果(await等待的方法必须是异步操作,因此方法名前必须得加:async)
url:"http//:nocat.life:3008/students", //请求地址
method:"GET", //请求类型
//需要传参时:设置如下
params: {
phone:this.phone, //参数名:参数值
password:this.password
},
});
console.log(res); //输出后端返回的结果
}
}
created() {
//axios.获取参数的方法('网络请求的地址',{需要传递的参数}).then(res => {})
axios.get('http://kumanxuan1.f3322.net:8881/cms/products/recommend').then(res => {
console.log(res)
})
},
3、请求参数
axios的属性中,除了url 和 method 外,还有一个属性,用来向后端发送参数:
- data:当 method 是除了GET 以外的其他类型时,用data来发送参数给后端
- params:当 method 是GET 类型时,用params来发送参数给后端
const studentsApi = {
// 获取学生数据
get() {
return axios({
url: '/students',
method: 'GET',
params:{
//参数
}
})
},
// 删除学生数据
delete() { //当需要传递参数时,把形参设置成data
return axios({
url: '/students',
method: 'DELETE',
//data: data //当对象的键和值相同时,可以简写为:data
data:{
//参数
}
})
},
跨域
由于浏览器中同源策略的限制,在浏览器中默认是不允许跨域访问的,一旦跨域访问,浏览器中就会抛出报错
域,可以理解为域名,每一个域至少都有三部分组成:
1.协议
2.IP地址
3.端口号
例如:http//:nocat.life:3008/students域中,http
就是协议,nocat.life
就是ID地址,3008
就是端口号
跨域的概念
当两个域之间,协议、IP、端口
三者中有任意一个不一致,则为“不同源”的域
跨域的解决方案
网络请求跨域的解决方案,常用的有以下几种方法:
1.JSONP:只能解决GET请求的跨域
2.CORS:纯后端处理
3.proxy(代理服务器):项目开发过程中最常用的方式
4.Nginx 发向代理:项目上线后最常用的方式
proxy
方法:
在Vue项目的根目录中,都会有一个vue.config.js
文件,我们在该文件中添加如下配置代码,来解决跨域问题:
// 当前文件中任何配置发生改变,项目都需要重新启动
module.exports = defineConfig({
publicPath: '/mobile/', // 顶级路由(用于打包时使用)
// 开发服务器配置
devServer: {
proxy: {
// 匹配项目中所有路径以 /api 开头的请求
'/api': {
target: 'http://nocat.life:3008', // 目标服务器地址
changeOrigin: true, //设置允许跨域 解决跨域
// 将所有请求中的 /api 替换成空字符串
pathRewrite: {
'/api': ''
}
}
}
}
})
以上配置表示,会匹配到项目中所有以 /api
开头的请求URL,然后将这些请求路径中的 /api
换成空字符串,然后转发到target
目标服务器
附加说明:如果采用以上配置,项目中所有的网络请求地址,都要以/api
开头:
methods: {
// 获取学生数据
async getStudents(){ //也就是说:await 和 async 必须同时搭配使用
const res = await axios({ //await:等待获取的结果(await等待的方法必须是异步操作,因此方法名前必须得加:async)
//url:"http//:nocat.life:3008/students", //请求地址
url:'/api/students' //处理了跨域后的请求地址
method:"GET" //请求类型
});
console.log(res); //输出后端返回的结果
//在控制台查看返回数据后,来一个判断,然后取出自己所需要的数据
// if(res.data.code){
// // console.log(res.data.data.rows)
// this.tableData = res.data.data.rows //把取出的数据放到自定义的 tableData[] 数组中
// }
// },
},
}
网络请求封装
通常,在一个前端项目中,我们会对网络请求进行两个步骤的封装:
1、封装axios,对axios的一些公共配置进行处理
2、封装请求API,对项目中所有的axios请求API同一管理
1、封装axios
-
1、创建封装文件
在项目src
目录中,创建一个utils
目录,用来存放项目中的工具文件。
在utils
目录中,创建一个request.js
文件,用来作为axios的封装文件 -
2、配置axios
在request.js
文件中进入如下配置
import axios from 'axios';
// 基础路径:用来配置所有 axios 请求 url 前面相同的一部分
axios.defaults.baseURL = "/api";
// 请求超时时长:当请求超过 5s 都还没有结束,会自动断开连接并抛出报错(看个人是否需要该配置)
axios.defaults.timeout = 5000;
// 响应拦截器:当后端将请求结果返回到前端组件之前,会被“响应拦截器”拦截下来
axios.interceptors.response.use((res) => {
// 将 return 的数据返回给前端组件
return res.data;
});
- 3、引入axios配置文件
得在main.js
文件中进行引入,让 axios 的配置文件运行生效
import './utils/request.js';
- 4、使用axios
当完成了以上的axios封装后,我们在组件中,在去使用axios发送请求,以及接收请求结果时,会变成如下代码:
methods: {
// 获取学生数据
async getStudents(){ //也就是说:await 和 async 必须同时搭配使用
const res = await axios({ //await:等待获取的结果(await等待的方法必须是异步操作,因此方法名前必须得加:async)
//url:"http//:nocat.life:3008/students", //请求地址(未做任何处理时的请求地址)
//url:'/api/students' //处理了跨域后的请求地址
url:'/students' //封装后的请求地址
method:"GET" //请求类型
});
console.log(res); //输出后端返回的结果
//在控制台查看返回数据后,来一个判断,然后取出自己所需要的数据
// if(res.code){
// // console.log(res.data.rows)
// this.tableData = res.data.rows //把取出的数据放到自定义的 tableData[] 数组中
// }
// },
},
}
2、封装API
实际开发中,我们不会将每一个请求过程全都分散在各个组件中。更常用的方式,是根据请求数据来进行分类,对不同数据类型的请求进行统一的管理
- 1、创建封装文件
src
|----api //存放项目中所有的axios请求
|----|----modules //根据请求的数据对所有请求进行模块划分
|----|------|-------studentsApi.js
|----|------|-------majorsApi.js
|----|------|-------...
|----|----index.js //将所有模块的 api 进行汇总
- 2、封装api
以学生模块的请求为例:studentsApi.js
import axios from "axios"; //引入axios
const studentsApi = {
// 获取学生数据
get() {
return axios({
url: '/students',
method: 'GET',
})
},
// 删除学生数据
delete(data) { //当需要传递参数时,把形参设置成data
return axios({
url: '/students',
method: 'DELETE',
//data: data //当对象的键和值相同时,可以简写为:data
data
})
},
}
export default studentsApi; //最后把studentsApi暴露出去
- 3、合并api
我们针对每一个数据模块都创建了一个单独的.js
文件来处理相关请求
但是,为了后继合并使用,我们还需要将每个请求模块在index.js
中做一个汇总
//汇总所有的网络请求
//首先引入
import students from './modules/studentsApi.js';
import majors from './modules/majorsApi';
//汇总:
const api = {
students,
majors
}
export default api; //暴露出去
- 4、全局挂载 api 对象(vue2中的方法)全局挂载后,不需要引入,就可以直接使用
经过前面的处理,所有的请求都已经汇总在了api对象中,组件内如果需要调用方法来发送请求,就必须先引入api对象,为了避免每个组件
使用时都要单独引入一次api对象,所有我们直接将api对象挂载到全局。
在main.js
中添加如下配置
// 引入封装好的 api 对象
import api from './api/index.js';
// 全局挂载 api 对象
Vue.prototype.$api = api;
- 5、使用api(vue2中的方法)
在组件中,可以通过this.$api
获取到全局挂载的api对象:
export default {
components: {
Breadcrumb, //挂载面包屑
},
data() {
return {
tableData: [], //定义一个空数组,用来存放后端取到的数据
},
// 生命周期函数:组件创建完成,通过调用 getStudents() 来获取学生数据
created() {
this.getStudents();
},
methods: {
// 获取学生数据
async getStudents() {
const res = await this.$api.students.get(); //通过 this.$api 来获取所需要的数据
if (res.code) {
this.tableData = res.data.rows;
}
},
// 删除学生数据
async handleDelete(_id) {
const res = await this.$api.students.delete({ _id }); //当需要传递参数时,这里也是进行了简写:_id: _id
if (res.code) {
// 删除成功后,需要重新获取最新的数据库的学生数据
this.getStudents();
}
},
};
- 4 全局挂载 api 对象(vue3中的方法) 全局挂载后,不需要引入,就可以直接使用
经过前面的处理,所有的请求都已经汇总在了api对象中,组件内如果需要调用方法来发送请求,就必须先引入api对象,为了避免每个组件
使用时都要单独引入一次api对象,所有我们直接将api对象挂载到全局。
在vue.config.js
中添加如下配置
//全局挂载api(第一步:添加以下两行代码)
const webpack = require("webpack")
const path = require("path")
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
// 全局挂载 api 对象(第二步:配置如下文件)
configureWebpack: {
plugins: [
new webpack.ProvidePlugin({ // 注册为全局变量
'api': [path.resolve('src/api/index.js'), 'default'], //'全局变量名':[path.resolve('api文件路径'),'default']
}),
]
},
// 解决跨域
// 开发服务器配置
devServer: {
proxy: {
// 匹配项目中所有路径以 /api 开头的请求
'/api': {
target: 'http://localhost:5000', // 目标服务器地址
changeOrigin: true, //设置允许跨域 解决跨域
// 将所有请求中的 /api 替换成空字符串
pathRewrite: {
'/api': ''
},
},
'/uploadFile': { // 工程的接口以/api开头
target: 'http://localhost:5000', // 被代理服务
changeOrigin: true, // 改变源
},
}
}
})
- 5、使用api(vue3中的方法)
在组件中,可以通过api.
获取到全局挂载的api对象:
methods: {
// 获取学生数据
async getStudents() {
const res = await api.students.get(); //通过 api. 来获取所需要的数据
if (res.code) {
this.tableData = res.data.rows;
}
},
网络请求的封装模板:方法一:(直接复制粘贴使用)
utils/request.js
文件下对axios进行封装
// axios的封装网络请求
import axios from "axios"; //引入axios
const instance = axios.create({
//baseURL:"http://kumanxuan1.f3322.net:8881/cms", //网络请求公共地址
baseURL:process.env.NODE_ENV === 'production' ? "/" : "/api", //(用于需要打包时)判断是否是生产模式?如果是生产模式,不用要代理,不然就代表它是开发模式,需要使用代理
timeout:5000 //网络请求超时
})
//主要有两个东西:1、请求拦截器: 在每个请求发出去之前执行该代码
instance.interceptors.request.use( config => {
//config是一个对象,记录了本次请求的相关信息(可以用来做一些请求前的准备工作。例如:添加请求头)
// console.log(config)
return config
}),
//请求失败时
err => {
return Promise.reject(err)
}
//2、响应拦截器:当后端将请求结果返回到前端组件之前,会被“响应拦截器”拦截下来
instance.interceptors.response.use( res => {
//res是一个对象,对服务器响应回来的数据做统一的处理
// console.log(res)
return res
}),
//请求失败时
err => {
return Promise.reject(err)
}
// 暴露出去
export default instance
api/index.js
文件下对API进行封装
// 进行API封装
//data:当 method 是除了GET 以外的其他类型时,用data来发送参数给后端
//params:当 method 是GET 类型时,用params来发送参数给后端
import instance from "@/utils/request";
//首页精品推荐请求
//封装方法一:
// export const JinpinAPI = () => (instance.get("/products/recommend"))
//封装方法二:
export const JinpinAPI = () => {
return instance({
url:'/products/recommend', //请求地址
method:"GET" //get方法可以省略不写
})
// 发送短信验证码请求(要传参数时)
export const SendSMSAPI =(data) => (instance.post('/sendSMS',data))
}
在各组件中调用
<template>
<div class="home">
</div>
</template>
<script>
import axios from 'axios'
import instance from '../utils/request'
import {JinpinAPI} from '../api/index'
export default {
data() {
return {
}
},
async created() {
// this.getJinpin()
// 最原始的(未封装):方法一
axios.get('http://kumanxuan1.f3322.net:8881/cms/products/recommend').then(res => {
// console.log(res)
})
// request封装后
instance.get('/products/recommend').then(res => {
// console.log(res)
})
// API封装(第一种读取方法)
// JinpinAPI().then(res =>{
// console.log(res)
// })
// await 后面一般放 Promise对象 如果看见这种:await Xxxx() 就是对Promise进行了封装:await new Promise()
//API封装后的异步读取方法
let res = await JinpinAPI();
// console.log(res)
this.getGoods()
},
methods: {
// 网络请求原始(未封装):方法二:
async getJinpin() {
const res = await axios({
url: 'http://kumanxuan1.f3322.net:8881/cms/products/recommend',
method: 'GET'
})
// console.log(res)
},
// API封装(第二种读取方法:异步读取)
async getGoods(){
const res = await JinpinAPI();
console.log(res)
}
}
}
</script>
<style lang="less" scoped>
</style>
网络请求的封装方式二:
import axios from 'axios';//引入axios
import { showDialog } from 'vant';
import { showLoadingToast } from 'vant';//引入ui组件
//使用回调函数
function post(url,data={},wait){//定义我们的post接口
if(process.env.NODE_ENV=='production'){
//如果是生产模式,才去掉/api
url=url.replace('/api','');
}
return new Promise(resolve=>{//使用Promise封装异步
console.log(`${url}发送:`,data);
const toast=wait && showLoadingToast({
message:'数据获取中,请稍后...',
loadingType:'spinner',
forbidClick:true,
duration:0,//让他一直显示
});
data.userId=localStorage.userId;//每一个接口要跟一个userId
axios.post(url,data).then(res=>{
const data=res.data;//接受返回值
if(!data.success){//如果登录失败,显示失败的信息
resolve();//返回不成功,返回控制回去
wait && toast.close();//关闭等待窗口
return showDialog({message:data.message});
}
console.log(`${url}接受:`,data.result);
wait && toast.close();//关闭等待窗口
resolve(data.result);//网络成功的处理
}).catch(e=>{//异常处理
console.log(`${url}错误:`,e);
resolve();//服务器错误,返回空值回去
wait && toast.close();//关闭等待窗口
showDialog({message:'服务器错误'});
});
});
}
//暴露出去
export default post;
Vuex 状态机
Vuex,Vue官方推荐的“状态管理模式”,又称为:状态机
状态,实际上指的就是数据。状态机,就是针对项目中的公共数据,以及数据相关的公共方法,来进行统一管理
基础配置
在store文件下新建自己所需要的模块:JS文件
//进行模块化
export default{
namespaced: true, //表示真正的化为模块化
//state:保存公共数据
state: {
},
getters: {
},
//mutations:保存修改 state 的同步方法,也是唯一修改 state 的途径
mutations: {
},
actions: {
}
}
在 src/store/index.js
文件中,来进行状态机的相关配置
import Vue from 'vue'
import Vuex from 'vuex'
//import 模块名(文件名) from '模块名(文件名)路径'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
//将 store 仓库中的各个模块挂载到这里
modules: {
//模块名(文件名)
}
})
五大核心属性
Vuex中有五大核心属性,分别是:
- state:保存公共数据
- getters:保存公共的计算属性
- mutations:保存修改 state 的同步方法,也是唯一修改 state 的途径
- actions:保存公共的异步方法
- modules:将 store 仓库划分为多个模块
状态机的使用
1.配置状态机
(1)配置数据的初始值
在状态机中,通过 state 来定义公共数据的初始值:
(2)配置异步请求方法
在状态机中,通过 actions 来定义公共的异步方法
(3)配置修改state的方法
因为 actions 中请求到的专业数据,无法直接修改到 state 中,所以,我们需要创建一个 mutations 的方法
(4)actions 中调用 mutations
在 actions 中的方法中,通过 commit
方法来调用 mutations ,同时进行传值
调用mutation里的方法用commit,dispatch
import api from '@/api/index';
export default new Vuex.Store({
state: {
// 专业数据的初始值(第一步)
majors: [],
},
mutations: {
SET_MAJORS(state, payload) { //state:为默认参数,指state对象;payload:指用来接收外部传递的数据(第三步)
state.majors = payload.rows;
state.total = payload.total;
}
},
actions: {
// 发送请求获取后端的专业数据(第二步)
async getMajorsAsync(context, payload) {
const res = await api.majors.get(payload);
if (res.code) {
// 想要将 action 中请求到的数据,保存到 state 中,但是 action 不能直接修改 state,因此:
// 1. action 方法内调用 mutations 的方法:context.commit('mutations的方法名')
// 2. 调用时将数据传递给 mutations 的方法
// 3. mutations 的方法接收到数据后,去修改 state
context.commit('SET_MAJORS', res.data); //(第四步)
}
}
},
})
2.组件中使用状态机
(1)获取 state 数据
在组件中,我们通常会用 computed
来接收仓库中的数据:
export default {
computed:{
majors(){
return this.$store.state.majors;
}
}
}
后继,在组件中我们可以直接使用计算属性 majors
(2)调用actions 的方法
在组件中,需要调用状态机内 actions的方法,来发送请求获取最新的数据库的数据
export default {
created() {
this.getMajors();
},
methods: {
getMajors() {
// 调用主仓库的 action 方法
this.$store.dispatch("getMajorsAsync");
}
辅助函数
辅助函数是Vuex为了方便我们在组件中操作状态机,而提供的一组方法
- mapState: 获取state数据
- mapGetters: 获取getters数据
- mapMutations: 获取mutation方法
- mapActions:获取actions方法
使用辅助函数
1、获取辅助函数
组件中如果要通过辅助函数来获取状态机中的数据和方法,那么需要先获取辅助函数。
引入辅助函数时,也要分为两种情况:
- 获取主仓库的数据和方法
- 获取仓库模块的数据和方法
//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {mapState,mapGetters,mapMutations,mapActions} = createNamespacedHelpers('majorsModule')
2、使用辅助函数
不管是操作主仓库,还是操作仓库模块,只要获取到了辅助函数,后继的使用没有任何区别
export default{
computed:{
//获取仓库中的 state
...mapState(['majors']) //调用辅助函数
},
created(){
this.getMajors();
},
methods:{
...mapActions(['getMajorsAsync']),
getMajors(){
this.getMajorsAsync();
}
}
}
注意事项
如果在一个组件中,需要同时获取主仓库和仓库模块的辅助函数,或者需要同时获取多个仓库模块的辅助函数时,
那么这种情况下,我们需要对辅助函数进行重命名。
例如:我们要同时获取主仓库和仓库模块函数
//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {
mapState: majorsState, //函数名:重新命名
mapGetters: majorsGetters,
mapMutations: majorsMutations,
mapActions: majorsActions
} = createNamespacedHelpers('majorsModule')
这样处理后,我们在组件中使用专业模块的辅助函数时,就直接调用 majorsModule
等即可
export defualt{
computed:{
...majorsState(['majors', 'xxx']),
...majorsGetters(['xxx'])
},
methods:{
...majorsMutations(['xxx']),
...majorsActions(['getMajorsAsync'])
}
}
状态机的使用模板
第一步:新建模块
在store
文件下,创建每个模块所需要的文件夹
,在文件里创建index.js
文件
//进行模块化
export default{
namespaced: true, //表示真正的化为模块化
//state:保存公共数据
state: {
// 用来表示登录模态窗口的显示true或者隐藏false
isShowLoginModal:false
},
//保存公共的计算属性
getters: {
},
// mutations:保存修改 state 的同步方法,也是唯一修改 state 的途径
//state:为默认参数,指state对象;payload:指用来接收外部传递的数据
mutations: {
// 修改isShowLoginModal的值
chanIsShowLoginModal(state,payload){
//state.变量名 = payload
state.isShowLoginModal = payload
}
},
//保存公共的异步方法
actions: {
},
}
第二步:引入到index.js文件
import Vue from 'vue'
import Vuex from 'vuex'
// 引入各个模块
//import 模块文件名 from '模块文件名/index.js'
import showLoginModal from './showLoginModal/index.js'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
//将 store 仓库中的各个模块挂载到这里
modules: {
//模块文件名
showLoginModal
}
})
// 状态,实际上指的就是数据。状态机,就是针对项目中的公共数据,以及数据相关的公共方法,来进行统一管理
第三步:在各组件中调用状态机里的数据或者方法
<script>
//引入状态机的辅助函数
import {mapState,mapMutations} from 'vuex'
export default {
data() {
return {
}
},
// 使用computed来接收状态机中的数据
computed:{
// 获取状态机中:state的公共数据:isShowLoginModal
...mapState({
// 状态机中的 数据名:state => state.文件名.数据名
isShowLoginModal:state => state.showLoginModal.isShowLoginModal
})
},
methods: {
// 使用 mapMutations 来获取状态机中的方法
...mapMutations({
// 状态机中的方法名:"文件名/方法名"
chanIsShowLoginModal:'showLoginModal/chanIsShowLoginModal'
})
}
}
</script>
用户身份认证
用户身份认证,主要分为两部分:
1、判断用户的登录状态
2、判断登录用户的角色权限
判断用户的登录状态
当用户访问项目中的一些权限页面时,首先需要先判断当前用户是否登录,如果没有登录,我们会直接提示用户去登录,并强制跳转到登录页面
1、用户登录
当用户登录成功后,后端会返回一个包含用户信息的 token 给前端,前端需要将 token 保存在本地存储中
export default {
data() {
return {
form: {
username: "admin",
password: "123",
},
};
},
methods: {
async login() {
const res = await this.$api.users.login(this.form);
if (res.code) {
// 将后端返回的 token 保存在本地存储中
localStorage.user_token = res.token;
this.$message({
message: "恭喜你,登录成功",
type: "success",
});
this.$router.replace("/home");
} else {
this.$message.error("账号或密码错误,请重新登录。");
}
},
},
};
2、判断用户的登录状态
在权限页面中,我们并不知道用户是登录后才访问的权限页面,还是未登录就直接访问权限页面。
因此,我们会在用户接入权限页面之前,对用户的登录状态进行验证。
给home 路由配置一个“路由独享守卫”
import { MessageBox } from 'element-ui';
const routes = [
path:'/home',
component:HomeView,
//添加导航守卫
beforeEnter:(to,from,next) => {
const token = localStorage.user_token; //获取本地存储的 token
if(token){
next() //如果获取成功,就进入页面
}else{
MessageBox.alert('你还未登录,去登录','警告',{
confirmButtonText:'确定',
callback:action => {
next('/login') //登录失败后,强制跳转到登录页面
}
})
}
},
children:[...]
]
设置全局前置守卫(推荐)
//路由模式:history模式
const router = new VueRouter({
......
})
//全局守卫
router.beforeEach((to, from, next) => {
if(to.path.includes('/home')){
//进入if,在表明用户访问的页面需要权限
const token = localStorage.user_token; //获取本地存储的 token
if(token){
next() //如果获取成功,就进入页面
}else{
MessageBox.alert('你还未登录,去登录','警告',{
confirmButtonText:'确定',
callback:action => {
next('/login') //登录失败后,强制跳转到登录页面
}
})
}else{
//进入else,则表明用户访问的页面不需要权限
next()
}
})
判断用户角色权限
大部分时候,在一个项目中,登录的用户会分成多种角色,不同的角色拥有不同的权限
不同权限的用户,登录成功后,能够访问的菜单是不一样的,因此,在用户登录成功符,我们需要向后端发送请求,来获取当前用户能访问的菜单数据。
但是,后端需要拿到前端存储的 token ,才能根据token来判断用户的角色。因此,我们前端需要将token添加到前端请求头中
,然后随着请求一起发送给后端
1.配置请求头的token
我们找到项目中的 src/utils/request.js
文件,在该文件中,
给 axios 配置请求拦截器,在请求拦截器中,统一给项目中所有请求的 header 中添加 token
//将token添加到前端请求头中(用于判断角色权限)
// 请求拦截器:当前端将请求发送给后端之前,会被请求拦截器拦截下来
axios.interceptors.request.use((config) => {
// 将 token 添加到请求头中,其中 Authorization 是后端定义的
config.headers.Authorization = localStorage.user_token;
return config;
})
其中,Authorization 是由后端决定的,通常在接口文档中都会有说明
2.发送请求获取菜单数据
我们在全局的前置守卫中,当判断用户登录成功后,我们就可以开始发送请求来向后端获取菜单数据
router.beforeEach(async (to, from, next) => {
if (to.path.includes('/home')) {
// 判断用户的登录状态
const token = localStorage.user_token;
if (token) {
// 登录成功后,在来获取菜单数据
const res = await api.users.getMenus();
if (res.code) {
console.log(res.data);
// 将菜单数据保存到状态机(将菜单数据传递到主仓库中)
store.commit('SET_MENUS', res.data);
}
next();
} else {
MessageBox.alert('你还未登录,请先登录。', '警告', {
confirmButtonText: '确定',
callback: action => {
next('/login');
}
});
}
} else {
next();
}
})
3.保存菜单数据到状态机
用户相关的数据,通常会直接导出在主仓库中:
export default new Vuex.Store({
state: {
menus: [] //菜单数据初始值
},
mutations: {
SET_MENUS(state, payload) {
state.menus = payload;
}
},
modules: {
.....
}
})
然后在请求成功后,调用mutations 的方法,将菜单数据传递到主仓库中:
4.菜单组件渲染数据
在菜单组件中,通过辅助函数,获取到主仓库中的菜单数据,并渲染:
import { mapState } from "vuex";
export default {
computed: {
...mapState(["menus"]),
},
};
身份认证过期处理
通常,后端在生成 token 时,都会设置一个token的有效期,每一次请求的时候,前端都会将token 携带在请求头中发送给后端,后端会对token的有效期进行判断
在 src/utils/request.js
文件中,在axios的响应拦截器中来处理401的报错
// 响应拦截器:当后端将请求结果返回到前端组件之前,会被“响应拦截器”拦截下来
axios.interceptors.response.use((res) => {
// 将 return 的数据返回给前端组件
return res.data;
},
//报错处理
(err) => {
if (err.response?.status == 401) {
MessageBox.alert('登录已过期,请重新登录', '警告', {
confirmButtonText: '确定',
callback: action => {
router.replace('/login');
}
});
}
return Promise.reject(err.message);
});
Vue全家桶
Swiper滑动插件
Swiper:是开源、免费、强大的触摸滑动插件
是纯JavaScript打造的滑动特效插件,面向手机、平板电脑等移动终端
是能实现触屏焦点图、触屏Tab切换、触屏轮番图切换等常用效果
vue中的官方文档:https://swiperjs.com/vue
安装到指定的package.json文件中的命令:npm install --save swiper
安装指定版本命令:npm install --save swiper@版本数
<template>
<div class="hello">
<!-- 第三步:显示 -->
<swiper>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
</swiper>
</div>
</template>
<script>
//第一步:引入swiper
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
export default {
name: 'HelloWorld',
//第二步:挂载
components:{
Swiper,
SwiperSlide
}
}
</script>
<style scoped>
img{
width:100%
}
</style>
添加指示器:
<template>
<div class="hello">
<!-- 第三步:显示 -->
<swiper :modules="modules" :pagination="{clickable:true}">
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
</swiper>
</div>
</template>
<script>
//第一步:引入swiper
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import {Pagination} from 'swiper'; //添加指示器
import 'swiper/css/pagination';
export default {
name: 'HelloWorld',
//第二步:挂载
components:{
Swiper,
SwiperSlide
},
//添加指示器是所需的固定语法
data(){
return{
modules:[Pagination]
}
}
}
</script>
<style scoped>
img{
width:100%
}
</style>
Axios网络请求库
Axios是一个基于promise的网络请求库
安装指令:npm install --save axios
引入
组件中引入:import axios from "axios"
全局引入:
1.第一步在main.js文件中引入且进行挂载:
import axios from "axios" //引入
//将axios挂载到全局
const app = createApp(App)
app.config.globalProperties.$axios = axios
app.mount('#app')
2.在组件中调用
this.$axios + 内容
网络请求基本示例
get请求
<template>
<div class="hello">
</div>
</template>
<script>
import axios from "axios"; //组件中导入axios
export default {
name:"helloworld",
//输出到桌面
data(){
return{
chengpin:{}
}
},
mounted(){
//get请求方式
axios({
method:"get",
url:"http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php"
}).then(res =>{
//console.log(res.data);
this.chengpin = res.data.chengpinDetails[0] //输出到桌面
})
}
}
</script>
--------------------------------------------------
get快捷请求方式:
axios.get("http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php").then(res =>{
console.log(res.data)
}),
post请求
温馨提示:post请求参数需要额外处理
1.安装依赖:npm install --save querystring
2.转换参数格式:ps.stringify({})
<template>
<div class="hello">
</div>
</template>
<script>
import axios from "axios"; //组件中导入axios
import QueryString from "querystring"; //导入querystring
export default {
name:"helloworld",
//post请求方式
axios({
method:"post",
url:"http://iwenwiki.com/api/blueberrypai/login.php",
data:QueryString.stringify({ //转换参数格式
user_id:"iwen@qq.com",
password:"iwen123",
verification_code:"crfvw"
})
}).then(res =>{
console.log(res.data);
})
}
}
</script>
---------------------------------------------------------
//post快捷请求方式
axios.post("http://iwenwiki.com/api/blueberrypai/login.php",QueryString.stringify({
user_id:"iwen@qq.com",
password:"iwen123",
verification_code:"crfvw"
})).then(res =>{
console.log(res.data);
})
Axios网络请求封装
在src目录下创建文件夹utils文件,并创建文件http.js或者request.js,用来存储网络请求对象axios的请求方法
然后安装axios:npm install --save axios
在安装querystring:npm install --save querystring
//导入
import axios from "axios"
import { Promise } from "core-js/shim"
import querystring from "querystring"
//参考文档:搜索:axios看云
const errorHandle = (status, info) => {
switch (status) {
case 400:
console.log("语义错误");
break;
case 401:
console.log("服务器认证失败");
break;
case 403:
console.log("服务器拒绝访问");
break;
case 404:
console.log("地址错误");
break;
case 500:
console.log("服务器遇到意外");
break;
case 502:
console.log("服务器无响应");
break;
default:
console.log(info);
}
}
const instance = axios.create({
//存放网络请求的公共配置
timeout: 5000
})
//最常用的是拦截器
//1.发送数据之前
instance.interceptors.request.use(
//成功时
config => {
if (config.methods === "post") {
config.data = querystring.stringify(config.data) //post请求时需要进行格式转换
}
//config:包含网络请求的所有信息
return config;
},
//失败时
error => {
return Promise.reject(error)
}
)
//2.获取数据之前
instance.interceptors.response.use(
//成功时
response => {
return response.status === 200 ? Promise.resolve(response) : Promise.reject(response)
},
error => {
const { response } = error;
//错误才是我们需要关注的重点
errorHandle(response.status, response.info)
}
)
export default instance;
再在src目录下创建文件夹api文件(存放网络请求),并创建两个文件path.js和index.js,
path.js文件下:
const base = {
//存放所有的公共地址
baseUrl:"http://iwenwiki.com",
chengpin:"/api/blueberrypai/getChengponDetails.php"
}
export default base;
index.js文件下
//存放所有的网络请求
import axios from "../utils/request";
import path from "./path"
const api = {
//成品详细地址
getChengpin(){
return axios.get(path.baseUrl + path.chengpin)
}
}
export default api
网络请求跨越解决方案
JS采取的是同源策略
同源策略是浏览器的一项安全策略,浏览器只允许js代码请求和当前所在服务器域名,端口,协议相同的数据接口上的数据,这就是同源策略
也就是说,当协议,域名,端口任意一个不相同时,都会产生跨越问题
目前主流的跨越解决方案有两种:
1.后台解决:cors
2.前台解决:proxy
//前台解决方案:在vue.config.js文件中添加如下代码
devServer:{
proxy:{
'api':{
target:'所要跨越的地址'
changeOrigin:true
}
}
}
//配置完成后,需要重启服务器
解决跨域的详尽办法
vue引入路由配置
页面加载的配置
Vue Router(未导入自己进行配置)
在Vue中,我们可以通过vue-router路由器管理路径与页面之间的关系
Vue Router是Vue.js的官方路由。它与Vue.js核心深度集成,让用Vue.js构建单页应用变得轻易而举
第一步:安装路由
npm install --save vue-router
第二步:配置独立的路由文件(在src目录下新建一个文件router,在router文件里新建index.js文件)
import { createRouter,createWebHashHistory } from "vue-router";
//引入所需要的显示页面
import HomeView from "../views/HomeView"
import AboutView from "../views/AboutView"
//在路由配置信息中需要页面的相关配置
const ruotes = [
{
name:'',
path:"/", //path:指定访问时的路径
component:HomeView //component:页面所对应的组件名
},
{
name:'',
path:"/about",
component:AboutView
}
]
//创建路由
const router = createRouter({
history:createWebHashHistory(),
/**
* createWebHashHistory
* home:http://localhost:8080/#/
* about:http://localhost:8080/#/about
* 原理:a标签锚点连接
*/
/**
* creatWebHistory
* home:http://localhost:8080/
* about:http://localhost:8080/about
* 此种方式,需要后台配合做重定向,否则会出现404问题
* 原理:H5 pushState()
*
*/
routes
})
//导出
export default router;
第三步:引入路由到项目
//在main.js文件下引入router文件
import router from './router'
createApp(App).use(router).mount('#app') //通过添加.use(router)来明确安装路由功能
第四步:指定路由显示入口
第五步:指定路由跳转
//在App.vue组件进行编辑
<template>
<!-- 指定路由跳转:to="跳转的路径" -->
<router-link to="/">首页</router-link>|
<router-link to="/about">关于</router-link>
<!-- 路由的显示入口 -->
<router-view></router-view>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
</style>
路由传递参数
第一步:在路由配置中指定参数的key
{
path: '/newsdetails/:name', //此处指定参数的key值为:name
name: 'newsdetails',
component: () => import("../views/NewsDetailsView.vue")
}
第二步:在跳转过程中携带参数
<template>
<ul>
<li><router-link to="/newsdetails/百度">百度新闻</router-link></li> //参数为:百度
<li><router-link to="/newsdetails/网易">头条新闻</router-link></li> //参数为:网易
<li><router-link to="/newsdetails/腾讯">腾讯新闻</router-link></li> //参数为:腾讯
</ul>
</template>
第三步:在详情页面读取路由携带的参数
<template>
<p>详细信息来自:{{ $route.params.name }}</p>
</template>
嵌套路由配置
路由的跳转
第一步:创建子路由要加载显示的页面
第二步:在路由配置文件中添加子路由配置
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
//重定向(首页指向)
redirect:'/about/us',
//嵌套添加二级导航
children:[
{
//二级导航的路径不要加 /
path:'us',
component:() => import('../views/AboutViews/AboutUs.vue')
},
{
path:'info',
component:() => import('../views/AboutViews/AboutInfo.vue')
}
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
第三步:指定子路由显示位置
第四步:添加子路由跳转链接
第五步:重定向配置(指定首页显示内容)
Vue状态管理(Vuex)
Vuex是一个专为Vue.js应用程序开发的状态管理模式+库。
简单来说,状态管理可以理解成为了更方便的管理组件之间的数据交互,提供了一个集中式的管理方案,任何组件都可以按照指定的
方式读取和改变数据。
引入Vuex的步骤
第一步:安装Vuex
npm install --save vuex
第二步:配置Vuex文件(在src目录下新建store文件,在此文件里创建index.js文件,进行如下操作)
import { createStore } from "vuex";
//Vuex的核心作用就是帮我们管理组件之间的状态
const store = createStore({
//所有的状态都放在这里(数据)
state:{
counter:0
}
})
export default store;
第三步:在主文件中引入Vuex(main.js文件下添加)
import store from './store'
app.use(store)
第四步:在组件中读取状态(任何组件中都可以读取)
<template>
<div>
<p>第一种方式:在app.vue中读取counter = {{$store.state.counter}}</p>
<p>第二种读取方式:在app.vue中读取counter ={{counter}}</p>
</div>
</template>
<script>
//第二种方式:
import { mapState } from 'vuex';
export default {
//专门来读取Vuex的数据
computed: {
...mapState(["counter"])
}
}
</script>
<style>
</style>
Vuex状态管理的核心
最常用的核心概念包括:State、Getter、Mutation、Action
Getter:对Vuex中的数据进行过滤
//store文件下的index.js文件
import { createStore } from 'vuex'
export default createStore({
//存放公共数据
state: {
counter:0
},
//对数据进行筛选(保存公共的计算属性)
getters: {
getCounter(state){
return state.counter > 0 ? state.counter :"counter数据异常"
}
},
//保存修改state的同步方法,也是唯一修改state的途径
mutations: {
},
//保存公共的异步方法
actions: {
},
//将store仓库划分为多个模块
modules: {
}
})
在任意组件中读取:
<template>
<div class="home">
<p>state读取数据count={{ $store.state.counter }}</p>
<p>getter筛选数据={{ $store.getters.getCounter }}</p>
<p>getter第二种读取方式{{getCounter}}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'HomeView',
//getter第二种读取方式
computed:{
...mapGetters(["getCounter"])
}
}
</script>
Mutation
更改Vuex的store中的状态的唯一方法是提交mutation。Vuex中的mutation非常类似于事件:每个mutation都有一个字符串的
事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且他会接受state作为第一个参数。
ACtoin
Action类似于mutation,不同在于:
Action提交的是mutation,而不是直接变更状态
Action可以包含任意异步操作
Element-plus
官网:https://element.eleme.cn
Element,一套为开发者准备的基于Vue2.0的桌面端组件库
Element Plus基于Vue3.0,面向开发者的组件库
安装Element-Plus:
npm install element-plus --save
完整引用
在main.js文件下添加,如果对打包后的文件大小不是很在乎,那么使用完整导入会更方便
import Elementplus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).mount('#app')
apply.use(Elementplus)
App.mount('#app')
//简写:
//createApp(App).use(Element).mount('#app')
按需导入
首先需要安装unplugin-vue-components和unplugin-auto-import则两款插件
安装: npm install -D unplugin-vue-components unplugin-auto-import
然后修改vue.config.js配置文件
const {defineConfig} = require('@vue/cil-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const {ElementPlusResolver} = require('unplugin-vue-components/resolvers')
module.exports=defineConfig({
transpileDependencies: true,
configureWebpack:{
plugins:[
AutoImport({
resolvers:[ElementPlusResolver()]
}),
Components({
resolvers:[ElenentPlusResolver()]
})
]
}
})
样式穿透(::v-deep)
<style scope lang="scss">
::v-deep 需要修改的element组件类名{
color:red
}
</style>
Vuex 状态机
辅助函数
辅助函数是Vuex为了方便我们在组件中操作状态机,而提供的一组方法
- mapState: 获取state数据
- mapGetters: 获取getters数据
- mapMutations: 获取mutation方法
- mapActions:获取actions方法
使用辅助函数
1、获取辅助函数
组件中如果要通过辅助函数来获取状态机中的数据和方法,那么需要先获取辅助函数。
引入辅助函数时,也要分为两种情况:
- 获取主仓库的数据和方法
- 获取仓库模块的数据和方法
//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {mapState,mapGetters,mapMutations,mapActions} = createNamespacedHelpers('majorsModule')
2、使用辅助函数
不管是操作主仓库,还是操作仓库模块,只要获取到了辅助函数,后继的使用没有任何区别
export default{
computed:{
//获取仓库中的 state
...mapState(['majors'])
},
created(){
this.getMajors();
},
methods:{
...mapActions(['getMajorsAsync']),
getMajors(){
this.getMajorsAsync();
}
}
}
注意事项
如果在一个组件中,需要同时获取主仓库和仓库模块的辅助函数,或者需要同时获取多个仓库模块的辅助函数时,
那么这种情况下,我们需要对辅助函数进行重命名。
例如:我们要同时获取主仓库和仓库模块函数
//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {
mapState: majorsState, //函数名:重新命名
mapGetters: majorsGetters,
mapMutations: majorsMutations,
mapActions: majorsActions
} = createNamespacedHelpers('majorsModule')
这样处理后,我们在组件中使用专业模块的辅助函数时,就直接调用 majorsModule
等即可
export defualt{
computed:{
...majorsState(['majors', 'xxx']),
...majorsGetters(['xxx'])
},
methods:{
...majorsMutations(['xxx']),
...majorsActions(['getMajorsAsync'])
}
}
第八章、Vue3(使用组合式API编程)
项目构建工具
我们要创建Vue3的项目,有两个项目构建工具可选
1、Vue CLI 脚手架工具(基于webpack)
2、Vite(推荐)
Vite 构建 Vue3 项目
1、创建项目
npm init vue || npm create vue
//执行完该命令后,会弹出提示,按照提示需求即可完成项目的创建
2、下载依赖
npm i
3、启动项目
npm run dev
注:使用Vite创建的Vue3项目也可以使用选项式编程
选项式API和组合式API的区别
选项式API
选项式 API,具有相同功能的放在一起,可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted等等Vue的配置项。
选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。
组合式API
一个功能逻辑的代码组织在一起(包括数据,函数等等)。
vue2和vue3响应式区别
Vue2响应式
vue2 的响应式原理是利es5 的个 API ,Object.defineProperty()对数据进劫持结合发布订阅模式的式来实现的。
优点:
兼容性好,支持 IE9。
缺点:
只能劫持对象的属性(key值.Object.key()),因此需要对每个对象的每个属性进行遍历。
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。是通过重写数据的操作方法(push、unshift…)来对数组进行监听的。
不能对 es6 新产生的 Map,Set 这些数据结构做出监听。
Vue3响应式
vue3 中使了 es6 的 proxy API 对数据代理,通过 reactive() 函数给每个对象都包层 Proxy,通过 Proxy 监听属性的变化,从⽽ 实现对数据的监控。
简而言之,Proxy 可以劫持整个对象,并返回一个新的对象。
优点:
可以直接监听对象而非属性;
可以直接监听数组的变化;
有多种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等是Object.defineProperty 不具备的;
返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改。
Proxy的优势
1.defineProperty只能监听某个属性,不能对全对象监听
2.可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
3.可以监听数组,不再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。 (vue3.x可以检测到数组内部数据的变化)
setup函数
setup 函数是一个钩子函数,是在组件中使用组合式 API 的入口
setup的参数
setup(props, context) / setup(props, {attrs, slots, emit, expose})
第一个参数是组件的 props,和标准的组件一致。
第二个参数是一个 Setup 上下文对象,它暴露了其他一些在 setup 中可能会用到的值:
attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.
a
t
t
r
s
e
m
i
t
:
用来分发自定义事件的函数
,
相当于
t
h
i
s
.
attrs emit: 用来分发自定义事件的函数, 相当于 this.
attrsemit: 用来分发自定义事件的函数, 相当于 this.emit
slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
expose: 如果返回值是函数,需要expose暴露公共属性
响应式变量:reactive()、ref()
定义:当我改变这个变量的值后,所有引用了这个变量的地方的值都会一起变化
在选项式API
中,data
里面初始化的数据都是深层次的响应式变量
在组合式API
中,只能通过reactive()或者ref()
来生成响应式变量(使用它们需要引入:import { reactive, ref } from ‘vue’)
<script setup>
import { reactive, ref } from 'vue'; // 引入reactive,用来做响应式变量
// 如果是基础变量,不能使用reactive来生成响应式变量,必须使用ref
// ref会把基础变量生成一个RefImpl对象,这个对象包含一个value的属性,我们在setup必须对value进行操作
// 在外面和普通变量一致
let num = ref(0);
// 相当于 reactive({ value: 0 })
const add = () => {
num.value++; // 只要在setup中,我们就一定要用value来操作
}
// reactive生成的响应式变量只能改变它的属性,不能替换它的值,如果需要替换,还得使用ref
// let person = reactive({ name: '孙悟空' }) // personal是一个对象
let person = ref({ name: '孙悟空' }) // personal是一个对象
const change72 = () => { // 72变
person.value.name = '猪八戒';// 只要在setup中,我们就一定要用value来操作
};
const replaceSun = () => {
person.value = { name: '六耳猕猴' }; // 只要在setup中,我们就一定要用value来操作
};
</script>
<template>
<div>
<div>{{num}}</div>
<button v-on:click="add">增加</button>
<!-- 如果在setup之外,和普通变量一致 -->
<button v-on:click="num--">减少</button>
<hr>
<div>{{ person }}</div>
<div>{{ person.name }}</div>
<button v-on:click="change72">72变</button>
<button v-on:click="replaceSun">六耳猕猴来了</button>
<button v-on:click="person='假孙悟空'">六耳猕猴不需要通过如来佛祖</button>
</div>
</template>
<style scoped>
</style>
组件之间的交互
父组件使用ref调用子组件
App.vue(父组件)
<script setup>
// 在组合式api中,直接引入子组件就可以使用了,不需要注册
import Child from './Child.vue';
import { ref } from 'vue';
const childRef = ref(null); // 生成一个ref对象,用来指向子组件
const modifyChildName = ()=>{
// console.log('=================childRef', childRef);
childRef.value.modifyNameByFather(); // 只有暴露出来的方法才能使用
// childRef.value.name = '朱公子';
};
</script>
<template>
<div>
<div>我是父组件</div>
<button @click="modifyChildName">修改孩子的名字</button>
<hr>
<Child ref="childRef"></Child>
</div>
</template>
Child.vue(子组件)
<script setup>
import {ref, defineExpose} from 'vue';
let name = ref('朱小明');
// 子组件的属性和方法都只能在该组件中使用,如果外部需要使用,需要暴露出去
const modifyName = () => {
name.value = '朱大肠';
};
// 使用defineExpose暴露出去,参数是一个对象,每一个key值就是暴露出去的名称,值就是方法或者属性
defineExpose({ modifyNameByFather: modifyName, name });
</script>
<template>
<div>
<div>我是{{ name }}</div>
<button @click="modifyName">自己修改</button>
</div>
</template>
父传子
将原来的props
项换成defineProps
使用defineProps接收属性
App.vue(父组件)
<script setup>
import Child from '@/Child.vue';
const show = () => {
console.log('=================123', 123);
}
</script>
<template>
<div>
我是父组件
<hr>
<!-- 当子组件接收需要验证时:这里name传123会报错 -->
<!-- <Child :name="123" :age="30" :show="show"></Child> -->
<!-- 没有传name会报错 -->
<Child :age="30" :show="show"></Child>
<!-- 因为姓李,所以也不通过 -->
<!-- <Child name="李大鸣" :age="30" :show="show"></Child> -->
<Child name="朱大鸣" :age="30" :show="show"></Child>
<Child name="朱的开" :age="20" :show="show"></Child>
<Child name="朱小明" :age="10" :show="show"></Child>
</div>
</template>
Child.vue(子组件)
<script setup>
import { defineProps } from 'vue';
// 将原来的props项换成defineProps
// 使用defineProps接收属性
// 不需要验证直接接收时:使用数组进行接收
// const props = defineProps(['name', 'age', 'show']);
// const { name, age, ...api } = defineProps(['name', 'age', 'show']); // 使用数组来接收
// 需要验证时:使用对象进行接收
const props = defineProps({
age: Number, // 要求age必须是Number
name: { // 更多的验证
type: String, // 必须传String
validator(v) {
if (v[0] !== '朱') {
return false; // 不通过,会报错
}
return true; // 通过
},
// required: true, // 必传
default: '新生儿', // 默认值
},
show: null, // 不需要验证用null
});
// const log = (...args) => {
// console.log(...args);
// }
const click = () =>{
// 只要父组件传过来的所有属性和方法都可以使用,不需要defineExpose
// props.show();
props.show();
};
</script>
<template>
<div>
<!-- 我叫{{ props.name }},我今天{{ props.age }}岁 -->
我叫{{ props.name }},我今天{{ props.age }}岁
<br>
<button @click="click">执行父组件的方法</button>
</div>
</template>
子传父(自定义事件的使用)
App.vue(父组件)
<script setup>
import { ref } from 'vue';
import Child from './Child.vue'
let childName = ref('朱小明');
const changeChildName= (name) =>{
childName.value = name;
}
</script>
<template>
<div>
我是父组件
<hr>
<Child :name="childName" @changeName="changeChildName"></Child>
</div>
</template>
Child.vue(子组件)
<script setup>
import { defineProps, defineEmits, ref } from 'vue';
const props = defineProps(['name']); // 接收到的属性是只读属性,不能自己修改
// 如果要修改,通知父组件修改
// const emits = defineEmits(['changeName']); // 需要要验证时:一般用数组接收,返回的是一个函数
// 需要验证时:
const emits = defineEmits({
changeName(v) {
if (v[0] === '朱') { // 判断是否姓朱
return true; // 返回true表示验证成功
}
return false;// 返回false表示验证失败
}
});
const changeName = ()=>{
// props.name = '朱公子'; // 是只读属性,不能修改,需要发送事件
// this.$emit('changeName')
// 不能直接进行数据传递,需要一个事件来触发它进行数据传递
emits('changeName', '朱三公子'); // 第一个参数是事件名称
// emits('changeName', '李三公子'); // 会验证失败
};
</script>
<template>
<div>
<div>我是子组件,我叫{{ props.name }}</div>
<button @click="changeName">改名字</button>
</div>
</template>
计算属性(computed函数)
computed函数
如果是只读属性,只需要传入一个返回值为响应式变量的getter函数。
如果是可写属性,需要传入一个对象,两个key值,分别是set和get,get是一个getter函数,set是一个setter函数。
<script setup>
import { computed, reactive } from 'vue';
import _ from 'lodash';
const list = reactive([ // 一定要用reactive生成响应式变量
{
name: '商品1',
price: 10,
count: 1,
selected: false,
},
{
name: '商品2',
price: 9,
count: 1,
selected: false,
},
{
name: '商品3',
price: 8.8,
count: 1,
selected: false,
},
]);
// computed是一个函数,如果只要只读属性,回调是一个函数
const totalFee = computed(()=>{
let arrs = list.filter(o=>o.selected); // 筛选出选择的
const sum = arrs.reduce((r, o)=>r+o.price*o.count, 0); // 计算总价
return sum;
});
// 如果需要可写属性,需要设置一个对象,set和get属性
const allSelected = computed({
get() {
return _.every(list, o=>o.selected);
},
set(v) { // set属性一定有一个参数,就是当前的选择的值
if (v) {
list.forEach(o=>o.selected = true); // 如果全选,那么每一个都选择
} else {
list.forEach(o=>o.selected = false); // 否则,每一个都不选择
}
}
});
</script>
<template>
<div>
<div v-for="item in list" :key="item.name" class="row">
<input type="checkbox" v-model="item.selected" class="check">
<Item :item="item"></Item>
</div>
<div class="row">
<input type="checkbox" v-model="allSelected">
<div>¥{{ totalFee }}</div>
<button>立即支付</button>
</div>
</div>
</template>
<style scoped>
.row {
display: flex;
flex-direction: row;
margin: 10px 0;
}
.check {
margin-right: 20px;
}
</style>
侦听器(watch函数)
第一个参数是侦听器的源。这个来源可以是以下几种:
一个 ref 对象或者 Proxy对象
ref 对象或者 Proxy对象组成的数组
有返回值的函数(getter)
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
第三个可选的参数是一个对象,支持以下这些选项:
immediate:在侦听器创建时立即触发回调。
deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。
watch的返回值是stop函数,用于停止监听。
<div>还有{{leftTime}}分钟下课</div>
<div>{{state}}</div>
...
<script>
...
setup() {
const leftTime = ref(45); // 距离下课时间
const state = ref('上课中...'); // 状态
const stopWatch = watch(leftTime, (cur, pre, onCleanup) => {
if (cur === 0) {
state.value = '下课了';
}
});
return { // 返回值会暴露给模板,模板可以直接使用
leftTime,
state,
stopWatch, // 可以直接调用这个函数
};
},
...
</script>
可以用数组的形式监听多个参数,回调中当前状态和上一次状态也是数组。
setup() {
const count = ref(0);
const count1 = ref(0);
const stop = watch([count, count1], (curs, pres) => {
console.log(curs, pres);
})
}
watchEffect函数
只有一个参数,是一个监听的回调函数,它会在第一运行的时候去自动收集依赖源,依赖源更新时重新执行自身。因此watchEffect 的回调函数是惰性立即执行的。这个回调函数可以带一个用于注册副作用清理的回调函数。
watchEffect的返回值是stop函数,用于停止监听。
<div>还有{{leftTime}}分钟下课</div>
<div>{{state}}</div>
...
<script>
...
setup() {
const leftTime = ref(45); // 距离下课时间
const state = ref('上课中...'); // 状态
const stopWatch = watchEffect((onCleanup) => {
if (leftTime.value == 0) { // 自动收集依赖源
state.value = '下课了';
}
});
return { // 返回值会暴露给模板,模板可以直接使用
leftTime,
state,
stopWatch, // 可以直接调用这个函数
};
},
...
</script>
生命周期钩子函数
组合式API开发中的生命周期钩子
只有3个阶段共6个函数,3阶段分别是 mount(挂载) update(更新) unmount(卸载),用onBeforeXX 和 onXXed 的形式得到6个函数。
不再有create这个阶段,原来为什么有create是因为有一个数据初始化的阶段,在组合式API中数据放在setup里面初始化了,所以就不需要这个阶段了。
生命周期钩子的参数是一个回调函数,我们可以在回调函数中添加我们的代码。
生命周期钩子函数必须放在setup函数中。
setup() {
onBeforeMount(() => { // 挂载前
console.log('onBeforeMount')
});
onMounted(() => { // 已挂载
// DOM操作、数据请求、实例化、计时器、延时器、订阅数据
console.log('onMounted')
});
onBeforeUpdate(() => { // 更新前
console.log('onBeforeUpdate')
});
onUpdated(() => { // 已更新
// DOM操作、实例化
console.log('onUpdated')
});
onBeforeUnmount(() => { // 卸载前
// 取消订阅,清除计时器、延时器等
console.log('onBeforeUnmount')
});
onUnmounted(() => { // 已卸载
console.log('onUnmounted')
});
}
依赖注入
前面我们学过,依赖注入提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
相当于提供了一个全局变量的样子,在所有的子组件都可以访问到。实际例子相当于的家谱图,老祖宗留了一本家谱图,想知道姓什么的时候去翻一下就知道了。
选项式API开发中的依赖注入
...
// 使用 provide 选项为组件后代提供数据
provide: {
surname: '朱',
},
...
//子组件使用 inject 选项来注入上层组件提供的数据
inject: ['surname'], // 通过数组的形式获取
组合式API中的依赖注入
提供一个值,可以被后代组件注入。
provide函数接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
setup () {
provide('surname'); // 提供值,可以被后代组件注入,可以是数据,也可以是函数
}
inject函数
子组件使用inject函数来注入上层组件提供的数据
第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。
如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。
如果没有能通过 key 匹配到值,inject函数将返回 undefined,除非提供了一个默认值。
第二个参数是可选的,即在没有匹配到 key 时使用的默认值。
它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。
如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。
setup () {
const surname = inject('surname', '孙'); // 子组件使用inject函数来注入上层组件提供的数据
return () => `我姓:${surname}`;
}
路由
1、安装:vue-router
我们在使用脚手架的时候选择是否创建router选择为Yes就可以了。
2、路由的配置(和选项式api一样)
在src/router/index.js
文件下进行路由的配置
import { createRouter, createWebHistory } from 'vue-router'
import { usePersonalStore } from '../stores/counter' //引入全局状态管理
import axios from 'axios' //引入网络请求axios
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login', //跳转路径
component: () => import('@/pages/login/Login.vue'), //引入组件
name: 'Login',
meta:{
noAuth:true //路由元信息(用来判断是否需要验证才能进入该页面)
}
},
{
path: '/', //主页面
component: () => import('@/pages/Main.vue'),
name: 'Main',
redirect: '/home', //路由重定向
meta:{
noAuth:false
},
children: [{
path: '/home', //首页
component: () => import('@/pages/home/index.vue'),
name: 'Home',
},
{
path: '/member', //员工管理
component: () => import('@/pages/member/index.vue'),
name: 'Member',
}, {
path: '/mine', //个人页面
component: () => import('@/pages/mine/index.vue'),
name: 'Mine',
}]
}
]
})
// 添加全局守卫,判断页面是否需要登录后才能显示
let personalStore; //定义一个全局变量
router.beforeEach((to,from,next)=>{
if (!personalStore) {
personalStore = usePersonalStore() //生成personalStores实例
}
if (to.meta.noAuth) { //如果noAuth为true,直接进入
next()
}else if(!localStorage.userId){ //如果未登录,跳转到登录页面
router.push('/login')
}else{
// console.log('存储到全局状态');
axios.post('/api/detail/member',{id:localStorage.userId}).then(res=>{
const data=res.data
// console.log('全局守卫',data.result);
if (!data.success) {
router.push('/login')
}else{
personalStore.setPersonal(data.result) //把个人信息存储到全局状态中(调用stores里面的setPersonal方法,把传递过来的参数存储到全局状态)
next()
}
})
}
})
export default router
3、路由出口:
RouterView 是一个功能性组件,用于渲染路径匹配到的视图组件。
RouterLink
标签支持用户在具有路由功能的应用中(点击)导航。
| 属性 | 类型 | 说明 |
| ---- | ------ | -------------- | -------------------------- |
| to | String/Object | 目标路由/目标位置的对象 |
| replace | Boolean | 不留下导航记录 |
| append | Boolean | 在当前路径后加路径 /a => /a/b |
| tag | String | 指定渲染成何种标签 |
| active-class | String | 激活时使用的Class |
路由的跳转及传参
在组合式api中,因为我们没有访问 this,所以不能再直接访问 this.$router。作为替代使用 useRouter 函数:
import { useRouter, useRoute } from 'vue-router'
const router = useRouter() //路由的跳转
const route = useRoute() //获取路由的参数及方法
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user
获取传过来的参数:
因为我们在 setup 里面没有访问 this,所以不能再直接访问 this.$route。作为替代使用 useRoute 函数:
import { useRoute } from 'vue-router' //引入
const route = useRoute() //实例化
console.log(route.query); // 路由配置为 { path: "/cart?day=30" } 时
console.log(route.params); // 路由配置为 { path: "/cart/:day" } 时
网络请求:axios
跨域的处理
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
base:'/admin/', //(使用vite开发时)添加顶级路由,用于打包程序给测试人员用
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: { // 对开发服务器进行设置
proxy: { // 设置跨域
'/api': {
changeOrigin: true, //开启代理:在本地会创建一个虚拟服务端,然后发送请求数据
target: 'http://localhost:5000', //这里是后端接口
rewrite:(path)=> {
return path.replace(/^\/api/,'') // 会将本地的 /api/list/goods 代理为:http://localhost:5000/list/goods
}
},
'/uploadFile': { // 工程的接口以/api开头(图片处理)
target: 'http://localhost:5000', // 被代理服务
changeOrigin: true, // 改变源
},
}
}
})
网络请求的封装
import axios from 'axios'; // 引入axios
import { ElMessageBox } from 'element-plus'
import { ElLoading } from 'element-plus' //引入UI组件
// url 为我们的接口
// params 为参数
// wait 是否等待,默认不等待
function post(url, params = {}, wait) { // 定义我们的post接口
if (process.env.NODE_ENV === 'production') { // 如果是生产模式,才去掉/api
url = url.replace('/api', '');
}
return new Promise(resolve => { // 使用Promise封装异步
console.log(`${url}发送:`, params);
const toast = wait && ElLoading.service({
lock: true,
text: '数据获取中,请稍后...',
background: 'rgba(0, 0, 0, 0.7)',
});
params.userId = localStorage.userId; // 每一个接口要跟一个userId
axios.post(url, params).then(res => {
const data = res.data; // 接受返回值
if (!data.success) { // 如果登录失败,显示失败的信息
resolve(); // 返回不成功,返回空值回去
wait && toast.close(); // 关闭等待窗口
return ElMessageBox.alert(data.message); // 使用好看的显示
}
console.log(`${url}接收:`, data.result);
wait && toast.close(); // 关闭等待窗口
resolve(data.result); // 网络成功的处理
}).catch(e => { // 异常处理
console.log(`${url}错误:`, e);
resolve(); // 服务器错误,返回空值回去
wait && toast.close(); // 关闭等待窗口
ElMessageBox.alert('服务器错误')
});
});
}
// 暴漏出去
export default post;
全局状态管理:pinia
pinia 是vue官方推荐的全局状态管理工具,pinia只能在vue3中使用,不能再vue2中使用。
Pinia与Vuex的区别
pinia只有state、getters、actions,没有mutations,简化了状态管理的操作
pinia模块划分不需要modules,
pinia自动化代码拆分
pinia对ts支持很好以及vue3的composition API
pinia体积更小,性能更好
手动安装时的操作:(使用脚手架npm create vue创建的时候可以自动安装pinia)
npm i -S pinia
创建pinia实例
在根目录下创建一个名为 store 的文件夹,并在其中创建一个名为 counter.js 的文件
选项式api的格式:
import { defineStore } from 'pinia'; // 导入相应的方法
// vuex是使用createStore来创建一整个store,pinia是使用defineStore来创建一个叫counter的store
export const useCounterStore = defineStore('counter', { // 名称counter必须唯一
state: () => { // 相当于我们学习的组件中的data函数
return {
count: 0,
}
},
getters: { // 相当于我们学习的组件中的computed
doubleCount: (state) => {
return state.count * 2;
}
},
actions: { // 相当于我们学习的组件中的methods,pinia同步异步均支持
increment(n) {
this.count += n;
}
}
})
组合式api的格式:
import { ref} from 'vue'
import { defineStore } from 'pinia'
export const usePersonalStore = defineStore('personal', () => { //personal是唯一的key
const personal = ref(null) //用来存储个人信息
function setPersonal(info){ //修改个人信息的函数
personal.value = info
}
return { personal, setPersonal} //把方法和属性暴露出去
})
使用
import { usePersonalStore } from '../stores/counter' //引入
personalStore = usePersonalStore() //生成personalStores实例
personalStore.setPersonal(data.result) //把个人信息存储到全局状态中(调用stores里面的setPersonal方法,把传递过来的参数存储到全局状态)
案例二:
创建:
import { defineStore } from 'pinia'; // 导入相应的方法
import { ref, computed } from 'vue';
// vuex是使用createStore来创建一整个store,pinia是使用defineStore来创建一个叫counter的store
export const moneyStore = defineStore('account', ()=>{
const count = ref(10000); // 初始化余额
const doubleCount = computed(()=>{ // 使用computed计算金豆的数量
return count.value * 100;
});
const increment = (n) => { // 增加的方法
count.value += n;
};
return { count, doubleCount, increment }; // 暴露出数据和方法
});
使用:
用对象的方式使用:
<script setup>
import { useCounterStore } from './stores/counter.js'
// 里面的变量提取出来,直接使用counter.count调用,因为counter是响应式的,因此使用counter.count做操作也是响应式的
const counter = useCounterStore();
</script>
<template>
<main>
<p>数字:{{ counter.count }}</p>
<p>2倍数字:{{ counter.doubleCount }}</p>
<button @click="counter.increment">增加</button>
</main>
</template>
提取属性的方式使用:
为了从 Store 中提取属性,同时保持其响应性,您需要使用storeToRefs(),它将为每个响应性属性创建引用。
<script setup>
import { useCounterStore } from './stores/counter.js'
const counter = useCounterStore();
// const { count, doubleCount } = counter; // 直接提取属性,count和doubleCount不具备相应性
const { count, doubleCount } = storeToRefs(counter); // 现在count和doubleCount具备相应性
</script>
<template>
<main>
<p>数字:{{ count }}</p>
<p>2倍数字:{{ doubleCount }}</p>
<button @click="increment">增加</button>
</main>
</template>
实用工具
moment
设置时间日期的函数库
官网:moment官网
dayjs
设置时间日期的函数库
我们一般使用dayjs来代替moment,因为dayjs更加轻量
官网地址
lodash
数组方法库
官网地址
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.staticfile.org/lodash.js/4.17.11/lodash.min.js"></script>
</head>
<body>
<script>
// 1、从数组[1,2,3,4,5]得到新数组[2,4,6,8,10]
let list=[1,2,3,4,5]
let newList = _.map(list,item=>item*2)
console.log("1、从数组[1,2,3,4,5]得到新数组[2,4,6,8,10]-----",newList);
// 2、从数组[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]得到新数组[{ a: 1 }, {a: 2}, {a: 3}]
let list1 = [{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]
console.log("2、从数组[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]得到新数组[{ a: 1 }, {a: 2}, {a: 3}]-----",_.map(list1,item=>({a:item.a})));
// 3、计算数组[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]中所有a的和
console.log("3、[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]中所有a的和:-----",_.sum(_.map(list1,item=>item.a)));
// 4、从数组[1,2,3,4,5,6]中提取所有的奇数[1,3,5]
console.log("4、从数组[1,2,3,4,5,6]中提取所有的奇数[1,3,5]-----",_.filter(list,item=>item%2));
// 5、从对象中{ a: 1, b: 2, c: 3, d: 4 }中提取所有的key,得到['a','b','c','d']
let obj={ a: 1, b: 2, c: 3, d: 4 }
console.log("5、从对象中{ a: 1, b: 2, c: 3, d: 4 }中提取所有的key,得到['a','b','c','d']-----",_.keys(obj));
// 6、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 1, fb: 2, fc: 3, fd: 4 }
console.log("6、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 1, fb: 2, fc: 3, fd: 4 }-----",_.mapKeys(obj,(v,k)=>"f"+k));
// 7、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 2, fb: 4, fc: 6, fd: 8 }
console.log("7、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 2, fb: 4, fc: 6, fd: 8 }-----",_.mapValues(_.mapKeys(obj,(v,k)=>"f"+k),(v,k)=>v*2));
// 8、计算数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中 a>2的个数
let list2 =[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]
console.log("8、计算数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中 a>2的个数-----",_.size(_.pickBy(list2,v=>v.a>2)));
// 9、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中第一个大于3的b的值。
let list3 = [ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]
console.log("9、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中第一个大于3的b的值-----",_.find(list3,item=>item.b>3));
// 10、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中最后一个大于3的b的值。
console.log("10、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中最后一个大于3的b的值-----",_.findLast(list3,item=>item.b>3));
// 11、将数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]按照a排序
console.log("11、将数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]按照a排序-----",_.orderBy(list2,"a","desc"));
// 12、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否所有的a都大于0
console.log("12、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否所有的a都大于0-----",_.every(list2,item=>item.a>0));
// 13、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否有a大于10
console.log("13、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否有a大于10-----",_.some(list2,item=>item.a>10));
// 14、判断数组[ 1, 4, 5, 1, 2, 4, 5 ]是否包含3
let list4 = [ 1, 4, 5, 1, 2, 4, 5 ]
console.log("14、判断数组[ 1, 4, 5, 1, 2, 4, 5 ]是否包含3-----",_.includes(list4,3));
// 15、将数组[ 1, 4, 5, 1, 2, 4, 5 ]去重
console.log("15、将数组[ 1, 4, 5, 1, 2, 4, 5 ]去重-----",_.uniq(list4));
// 16、将数组[ {a: 1}, {a: 4}, {a: 1} ]按照a的值去重
let list5=[ {a: 1}, {a: 4}, {a: 1} ]
console.log("16、将数组[ {a: 1}, {a: 4}, {a: 1} ]按照a的值去重-----",_.uniqBy(list5,item=>item.a));
// 17、生成一个数组 [1,2,3,4,5]
console.log("17、生成一个数组 [1,2,3,4,5]-----",_.range(1,6));
// 18、生成一个数组 [2,4,6,8,10]
console.log("18、生成一个数组 [2,4,6,8,10]-----",_.range(2,11,2));
// 19、提取对象{ a: 1, b: 2, c: 3, d: 4 }中a和c得到 { a: 1, c: 3}
console.log("19、提取对象{ a: 1, b: 2, c: 3, d: 4 }中a和c得到 { a: 1, c: 3}-----",_.pick(obj,"a","c"));
// 20、将数组[ {a: 1}, {b: 4}, {c: 2} ]得到数组[1,4,2]
let list6=[{a: 1}, {b: 4}, {c: 2}]
console.log("20、将数组[ {a: 1}, {b: 4}, {c: 2} ]得到数组[1,4,2]-----",_.map(list6,item=>_.values(item)[0]));
// 21、求数组[1,3,5]和[2,3,4,1]的交集
console.log("21、求数组[1,3,5]和[2,3,4,1]的交集-----",_.intersection([1,3,5],[2,3,4,1]));
// 22、求数组[1,3,5]和[2,3,4,1]的并集
console.log("22、求数组[1,3,5]和[2,3,4,1]的并集-----",_.union([1,3,5],[2,3,4,1]));
// 23、求数组[1,3,5,6]中,在数组[2,3,4,1]不存在的元素组成的数组。
console.log("23、求数组[1,3,5,6]中,在数组[2,3,4,1]不存在的元素组成的数组-----",_.difference([1,3,5,6],[2,3,4,1]));
// 24、将对象{b:{a:{c:1}}}深拷贝
let obj1 = {b:{a:{c:1}}}
console.log("24、将对象{b:{a:{c:1}}}深拷贝-----",_.cloneDeep(obj1));
// 25、写一个中奖概率为10%的算法
// 生成0-9之间的随机整数
let probability = _.random(0, 9);
if (probability ===0 ) {
console.log("我的中奖了概率为10%");
} else {
console.log("没中奖的概率为90%");
}
</script>
</body>
</html>