<!DOCTYPE HTML>
<html>
<head>
<script src="vue.min.js"></script>
</head>
<body>
<div id="root">
<h1>{{name}}-{{address}}</h1>
</div>
<script>
const vm = new Vue({
el:"#root",
data:{
name:"张三",
address:"西安",
url:"https://www.baidu.com",
value:''
}
})
</script>
</body>
</html>
模板语法
如上代码,root容器里的代码被称为Vue模板
- 插值语法
<h1>{{name}}-{{address}}</h1>
其中name,address 是js表达式,且可以直接读取到data中的所有属性
- 指令语法
<a v-bind:href="url">点我跳转</a>
v-bind:简写为 :
插值语法用于标签体当中,指令语法用于解析标签(标签属性,标签整体内容,绑定事件等)
数据绑定
- 单向数据绑定 v-bind: 数据只能从data流向页面
- 双向数据绑定 v-model 数据不仅能从data流向页面,也能从页面流向data
注意:只有表单类才可以使用双向数据绑定(都有value值)
<div>
双向数据绑定<input v-model="value">
</div>
el与data的两种写法
//方式一
new Vue({
el:"#root",
data:{
name:"张三",
}
})
//方式二
const vm = new Vue({
data:{
name:"张三",
}
})
vm.$mount('#root')
//方式一:对象式
data:{
name:"张三",
}
//方式二:函数式 在组件中需要使用函数式
data(){
return {
name:"张三"
}
}
理解vue中的MVVM
理解数据代理
Object.defineproperty(obj, prop, desc )
- 参数1:obj 需要定义属性的当前对象
- 参数2:prop 当前需要定义的属性名
- 参数3:desc 描述符 一般是一个对象
let person = {
name:"张三",
sex:"男"
}
Object.defineProperty(person,'age',{
value:18
})
console.log(Object.keys(person))
//此时 person里面的age是不可枚举的 通过打印发现没有打印出age
Object.defineProperty(person,'age',{
value:18,
enumerable:true, //可被枚举,默认为false
writable:true, //可以被修改,默认为false
configurable:true //可以被删除,默认为false
})
Object.defineProperty(person,'age',{
//当有人读取age属性时,get函数会被调用,且返回值就是age的值
get(){
return number
},
//当有人修改age属性时,set函数会被调用,且会收到的修改的具体值
set(value){
number = value
}
})
数据代理:通过一个对象代理对令一个对象中属性的操作(读/写)
let obj = {
x:100
}
let obj2 = {
y:200
}
Object.defineProperty(obj2,'x',{
get(){
return obj.x
},
set(value){
obj.x = value
}
})
obj2.x = 300 此时obj.x 也为300,这就是简单的数据代理
vue中的数据代理
let data = {
name:"张三"
}
const vm = new Vue({
el:"#root",
data
})
vm._data === data //true
当我们定义了一个vm时,vue将传入data进行处理,将一个_data绑定在vm上面, 然后再把data中的数据放到vm上面一份
vue数据代理总结:通过vm对象来代理data对象中属性的操作vue中数据代理的好处:更加方便的操作data中的数据基本原理:通过Object.defineProperty()把data对象中所有属性添加到 vm上面,为每个添加到vm上面的属性都指定一个getter/setter,在getter和setter 内部操作时,data中的数据也会随着改变
事件处理
<body>
<div id="root">
<h1 @click="nameClick">{{name}}</h1>
<h1 @click="nameClick2(6,$event)">{{name}}</h1>
</div>
<script>
const vm = new Vue({
el:"#root",
data:{
name:"张三"
},
methods:{
//
nameClick(event){
console.log(this)
console.log(event.target.innerText)
},
nameClick2(num,event){
console.log(this)
console.log(event.target.innerText)
}
}
})
</script>
</body>
vue提供$event传入event,此时nameClick,nameClick2也会绑定到vm上面,但是不会和
data里面的数据一样做数据代理,因为函数没有必要
methods中配置的函数,不要用箭头函数,否则this就不是vm了,也就是说methods中配置的函数,都是被vue所管理的函数,this指向的都是vm或组件实例对象
事件修饰符
- prevent 阻止默认事件(常用)
- stop 阻止事件冒泡(常用)
- once 事件只触发一次(常用)
- capture 使用事件的捕获模式
- self 只有event.target是当前操作的元素时才会触发事件
- passive 事件的默认行为立即执行,无需等待事件回调执行完毕
//阻止 a标签的默认跳转
<a href="https://www.baidu.com" @click.prevent="clickUrl">点我</a>
计算属性
- 要用的属性不存在,需要通过已有的属性计算得来
- 底层也是借助Object.defineproperty 方法提供的getter和setter实现
- 与methods相比,计算属性内部有缓存机制,效率更高
计算属性代码中涉及到的变量,只要值发生变化,计算属性中代码都会执行
<template>
<div id="app">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名: <input type="text" v-model="fullName"> <br/>
</div>
</template>
export default {
name: 'App',
data(){
return {
firstName:"张",
lastName:"三"
}
},
computed:{
fullName:{
get(){
return this.firstName + '-' + this.lastName
},
set(value){
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
}
//简写
fullName() {
return this.firstName + '-' + this.lastName;
}
// 当type变化时 fullName()代码段也会执行
computed:{
fullName(){
if(this.type === '0'){
return this.firstName + this.lastName
}else{
return this.firstName +'-'+ this.lastName
}
}
}
监视属性
watch
- 当监视属性变化时,回调函数自动调用,进行相关操作
- 监视属性必须存在(data里面的属性,或者computed里面的属性),不能进行监视
<template>
<div id="app">
<h2>今天的天气很{{info}}</h2>
<button @click="changeInfo">切换天气</button>
</div>
</template>
export default {
name: 'App',
data(){
return {
isHot:true
}
},
methods:{
changeInfo(){
this.isHot = !this.isHot
}
},
watch:{
isHot:{
//初始化时 让handler调用一下 默认是false 不调用 当设置为true时,初始化时会调用
immediate:false,
//isHot 被修改后调用
handler(newValue,oldValue){
console.log(newValue,oldValue)
}
},
//简写
isHot(newValue,oldValue){
}
},
computed:{
info:{
get(){
return this.isHot ? '炎热': '凉爽'
}
}
}
}
深度监视
watch 默认是不支持监听多层级结构中某个属性的变化的,如果想监听,需要打开deep:true
<template>
<div id="app">
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
</div>
</template>
export default {
name: 'App',
data(){
return {
numbers:{
a:1,
b:2
}
}
},
watch:{
numbers:{
deep:true,
handler(newValue,oldValue){
console.log(newValue,oldValue)
}
}
},
}
循环渲染key的作用与原理
<template>
<div id="app">
<ul>
<li v-for="(p,index) of persons" :key="p.index">
{{p.name}}
<input type="text">
</li>
</ul>
<button @click.once="addClick">添加一个老刘</button>
</div>
</template>
export default {
name: 'App',
data(){
return {
persons:[
{name:"张三", id:1},
{ name:"李四", id:2},
{ name:"王五",id:3}
]
}
},
methods:{
addClick(){
let p = {name:"老刘", id:4}
this.persons.unshift(p)
}
}
}
我们发现当点击添加老刘时,input框内的数据错乱了,这并不是我们想要的结果, 为什么会这样,是因为和vue的设计有关
要解决这个问题,我们只需要将 :key="p.id" 即可
- 虚拟DOM中key的作用 key是虚拟DOM对象的标识,当数据发生变化时,vue会根据【新数据】生成【新的虚拟DOM】,随后vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
对比规则:
- 旧虚拟dom中找到与新虚拟dom中相同的key:
若虚拟dom中内容没变,直接使用之前的真实dom; 若虚拟dom中内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom
- 旧虚拟dom中未找到与新虚拟dom相同的key:
创建新的真实dom,随后渲染到页面
- 用index作为key可能引发的问题
1> 若对数据进行:逆序添加,逆序删除等破坏顺序操作,会产生没有必要的真实dom更新,页面没有问题,但是效率低
2>如果结构中存在输入类的dom,会产生错误dom更新,页面会有问题
vue监测数据原理
vue会监视data中所有层次的数据
如何监测对象中的数据?
通过setter实现监视,且要在[new vue]时就要传入要监测的数据
- 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式需要使用
Vue.set(target,propertyName,value)或者vm.$set(target,propertyName,value)
如何监测数组中的数据?
- 调用原生对应的方法对数组进行更新
- 重新解析模版,进行更新页面
在vue中修改数组元素用如下方法
- push()、pop()、shift()、unshift()、splice()、sort()、reverse()
- Vue.set()、vm.$set()
注意:Vue.set()、vm.$set() 不能给vm 或者vm的根数据对象添加属性,也就是说不能给data添加属性,添加了也不是响应式
vue中常用API
filter-过滤器
vue项目中引入本地js文件
- 将day.js 添加到项目 src/utils/
- 在使用的地方引入
import dayjs from './utils/day'
上面过滤器也可以这么写
<h1>现在是:{{ time | timeFormater("YYYY-MM-DD")}}</h1>
filters:{
timeFormater(value,formatStr){
return dayjs(value).format(formatStr)
}
}
nextTick
案例: 有一个div,默认用 v-if 将它隐藏,点击一个按钮后,改变 v-if 的值,让它显示出来,同时拿到这个div的文本内容。如果v-if的值是 false,直接去获取div内容是获取不到的,因为此时div还没有被创建出来,那么应该在点击按钮后,改变v-if的值为 true,div才会被创建,此时再去获取,示例代码如下:
<template>
<div id="app">
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取div内容</button>
</div>
</template>
export default {
name: 'App',
data() {
return {
showDiv : false
}
},
methods:{
getText(){
this.showDiv = true
let text = document.getElementById('div').innerHTML;
console.log(text);
}
}
}
通过运行发现:我们并不能取到div中的值,这里就涉及到一个重要的概念:
异步更新队列:
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。而$nextTick()就是为了告诉你什么时候Dom更新完成了。修改上面代码为:
this.$nextTick().then(function (){
let text = document.getElementById('div').innerHTML;
console.log(text);
})
v-cloak
- 本质是一个特殊的属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 使用css配合v-cloak可以解决网速慢时页面展示出
{{x}}的问题
<div id="app" v-cloak>
<h1>{{msg}}</h1>
</div>
<style>
[v-cloak]{
display: none;
}
</style>
v-once
只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能
<template>
<div id="app">
<h1 v-once>初始:{{count}}</h1>
<h1>现在:{{count}}</h1>
<button @click="add">点我count+1</button>
</div>
</template>
export default {
name: 'App',
data() {
return {
count: 0
};
},
methods:{
add(){
this.count++
}
}
}
v-on : 绑定事件监听, 一般简写为@
v-on 是用于监听 DOM 事件并触发相应的方法。它可以绑定一个事件监听器,当指定的事件被触发时,会执行相应的方法。例如,你可以使用 v-on:click 来监听元素的点击事件,并在点击时执行一个方法
<button v-on:click="handleClick">Click me</button>
<button @click="handleClick">Click me</button>
v-bind: 绑定解析表达式, 可以省略 v-bind
v-bind 用于将数据绑定到 HTML 属性上。它可以动态地将 Vue 实例中的数据绑定到 HTML 元素的属性上,使其随着数据的变化而更新。例如,你可以使用 v-bind:href 将一个链接的 href 属性绑定到 Vue 实例中的一个数据属性
<a v-bind:href="url">Go to website</a>
<a :href="url">Go to website</a>
vue生命周期
- beforeCreate:此时无法通过vm访问到data中的数据,methods中的方法。
- created:此时可以通过vm访问到data中的数据,methods中配置的方法
- beforeMount:页面呈现的都是未经Vue编译的DOM结构,所有对DOM的操作,最终都不奏效
- mounted:页面中呈现的是经过vue编译的Dom,对Dom的操作均有效,一般在此进行:定时器的开启,发送网络请求,订阅消息,绑定自定义事件等初始化操作
- beforeUpdate:此时数据是新的,但是页面是旧的,即:页面尚未和数据保持同步
- updated:此时数据是新的,页面也是新的,即:页面和数据保存同步
- beforeDestory:vm中所有的data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、去选订阅消息、解绑自定义事件等收尾操作
- destoryed:vue实例被销毁
两个特殊的:
activated和deactivated:这两个比较特殊,其中activated是被 keep-alive 缓存的组件激活时调用,deactivated是被keep-alive 缓存的组件失活时调用。
来源:禹神
实际开发中使用mounted和beforeDestory较多,mounted()发送网络请求,启动定时器等一步任务。beforeDestory()做收尾工作,清除定时器等
组件
组件的定义
在vue中,组件是可以重用的,自包含的代码块,用于构建用户界面,组件可以包含模版、样式、行为,并可以在程序中多次使用。
创建组件实例
使用Vue.extend()方法创建一个组件实例,你可以在单个文件组件(.vue文件)中定义组件,或者你在javascript文件中使用Vue.extend().
// 在单个文件组件中定义组件
<template>
<!-- 组件的模板 -->
</template>
<script>
export default {
// 组件的选项和逻辑
}
</script>
<style>
/* 组件的样式 */
</style>
注册组件
将组件注册到 Vue 应用中,以便在其他组件中使用。你可以在全局注册组件,或者在特定组件中局部注册组件。 全局注册组件方法是在应用程序的入口文件(通常是main.js)中使用Vue.component()方法
// main.js
import Vue from 'vue'
import MyComponent from './MyComponent.vue'
Vue.component('my-component', MyComponent)
局部注册组件的方法是在使用该组件的父组件中使用 components 选项:
// ParentComponent.vue
import MyComponent from './MyComponent.vue'
export default {
components: {
'my-component': MyComponent
},
// ...
}
使用组件
在其他组件或模板中使用已注册的组件。
<!-- 在模板中使用组件 -->
<template>
<div>
<my-component></my-component>
</div>
</template>
组件之间传递数据
组件之间传递数据包括:父组件给子组件传递数据、子组件给父组件传递数据、兄弟组件之间传递数据
父组件给子组件传递数据
父组件给子组件传递数据:props(3种方式)
注意:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
- 只指定名称(props: ["name","age"]) 用数组将所有参数都接收过来
//父组件
<template>
<div id="app">
<my-components3 name="张三" age="12"></my-components3>
</div>
</template>
//子组件
<template>
<div >
<h3>我是姓名:{{name}}</h3>
<h3>我是年龄:{{age}}</h3>
</div>
</template>
<script>
export default {
name: 'MyComponents3',
props: ["name","age"],
data () {
return {
}
}
}
</script>
- 指定名称和类型
<template>
<div >
<h3>我是姓名:{{name}}</h3>
<h3>我是年龄:{{age}}</h3>
</div>
</template>
<script>
export default {
name: 'MyComponents3',
props: {
name:String,
age:Number
},
data () {
return {
}
},
mounted(){
console.log(typeof(this.age)) //Number
}
}
</script>
- 指定名称/类型/必要性/默认性
<template>
<div >
<h3>我是姓名:{{name}}</h3>
<h3>我是年龄:{{age}}</h3>
</div>
</template>
<script>
export default {
name: 'MyComponents3',
props: {
name:{
type:String,
required:true, //是否必须传
default:"李四" //默认值
},
age:{
type:Number,
required:false,
default:20
},
},
data () {
return {
}
},
mounted(){
console.log(typeof(this.age))
}
}
</script>
子组件给父组件传递数据
在vue中,子组件向父组件传递数据需要使用自定义事件,子组件通过触发一个事件,将数据发送给父组件,而父组件则监听该事件并接收数据,开发中主要有如下两种方式:
- 父组件绑定自定义事件
//父组件
<template>
<div id="app">
<my-components4 @sendEvent="handleSendEvent"></my-components4>
<h3>我是父组件-接收数据:{{name}}</h3>
</div>
</template>
<script>
import MyComponents4 from "./components/MyComponents4"
export default {
name: 'App',
components:{MyComponents4},
data() {
return {
name:""
};
},
methods:{
handleSendEvent(data){
this.name = data.name
}
}
}
</script>
//子组件
<template>
<div>
<h3>我是子组件4</h3>
<button @click="sendMessageToParent">点我给父组件传值</button>
</div>
</template>
<script>
export default {
name: '',
data () {
return {
}
},
methods: {
sendMessageToParent(){
this.$emit("sendEvent",{name:"张三"})
}
}
}
</script>
- 使用ref获取到子组件,然后绑定事件
//父组件
<template>
<div id="app">
<my-components4 ref="components4"/>
<h3>我是父组件-接收数据:{{parentName}}</h3>
</div>
</template>
<script>
import MyComponents4 from "./components/MyComponents4"
export default {
name: 'App',
components:{MyComponents4},
data() {
return {
parentName:""
};
},
mounted () {
//这里使用箭头函数,或者在methods中定义函数
this.$refs.components4.$on("sendEvent",(data)=>{
this.parentName = data.name
})
}
}
</script>
//子组件
<template>
<div>
<h3>我是子组件4</h3>
<button @click="sendMessageToParent">点我给父组件传值</button>
</div>
</template>
<script>
export default {
name: 'MyComponents4',
data () {
return {}
},
methods: {
sendMessageToParent(){
this.$emit("sendEvent",{name:"张三"})
}
}
}
</script>
插槽
插槽是一种用于组件中编写可重用内容的机制,插槽允许你在组件的模版中定义一个或多个占位符,然后再使用该组件时,将具体内容填充到这些占位符中。
为什么要有插槽?我们看如下代码
//子组件
<template>
<div>
<h3>我是子组件5</h3>
</div>
</template>
<script>
export default {
name: 'MyComponents5',
data () {
return {}
}
}
</script>
//父组件
<template>
<div id="app">
<my-components5>
<h3>我想在组件中插入一个新结点</h3>
</my-components5>
<br>
</div>
</template>
<script>
import MyComponents5 from "./components/MyComponents5"
export default {
name: 'App',
components:{MyComponents5},
data() {
return {};
}
}
</script>
我们理想状态下是在页面上面展示两个h3标签,一个是组件内的,<my-components5><h3>我想在组件中插入一个新结点</h3></my-components5>插入的,但是事与愿违,我们发现页面只会展示组件内部的h3标签,<my-components5><h3>我想在组件中插入一个新结点</h3></my-components5> 插入没有生效。仔细想一下,虽然我们给组件内部写了元素,但是组件内部也是比较懵的,他不知道这个元素应该插入在哪个位置,所以Vue就直接不做处理。插槽就是为了解决这个问题。
默认插槽
//子组件
<template>
<div>
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
<h3>我是子组件5</h3>
</div>
</template>
<script>
export default {
name: 'MyComponents5',
data () {
return {}
}
}
</script>
//父组件
<my-components5>
<slot>
<h3>我想在组件中插入一个新节点</h3>
</slot>
</my-components5>
我们发现父组件中插入的节点展示到了页面中
命名插槽
如果我们想插入两个节点呢?学习了默认插槽后,我们可能会写出如下代码:
//子组件
<template>
<div>
<slot>我是第一个占位的</slot>
<h3>我是子组件5</h3>
<slot>我是第二个占位的</slot>
</div>
</template>
//父组件
<my-components5>
<slot>
<h3>新结点1</h3>
</slot>
<slot>
<h3>新结点2</h3>
</slot>
</my-components5>
结果如下图:
我们发现新插入的节点在页面上面显示了2次,不是我们想要的结果。因为Vue中的默认插槽只能写1个或者不写,不能写多个,如果需要多个,就需要使用命名插槽。修改如上代码:
//子组件
<template>
<div>
<slot name="slot1">我是第一个占位的</slot>
<h3>我是子组件5</h3>
<slot name="slot2">我是第二个占位的</slot>
</div>
</template>
//父组件
<my-components5>
<slot slot="slot1">
<h3>新结点1</h3>
</slot>
<slot slot="slot2">
<h3>新结点2</h3>
</slot>
</my-components5>
作用域插槽
作用:可从组件向组件使用处传数据
//子组件
<template>
<div>
<h3>我是子组件6:我要把数据带出去</h3>
<slot :info="info">我是占位的</slot>
</div>
</template>
<script>
export default {
name: 'MyComponents6',
data () {
return {
info:{
name:"张三",
age:14
}
}
}
}
//父组件 写法一(api逐渐废弃)
<my-components6>
<template slot-scope="{info}">
<h3>带出来的name:{{info.name}}</h3>
<h3>带出来的age:{{info.age}}</h3>
</template>
</my-components6>
//父组件 写法二
<my-components6>
<template v-slot:default="soltData">
<h3>带出来的name:{{soltData.info.name}}</h3>
<h3>带出来的age:{{soltData.info.age}}</h3>
</template>
</my-components6>
//父组件 写法三
<my-components6>
<template v-slot="soltData">
<h3>带出来的name:{{soltData.info.name}}</h3>
<h3>带出来的age:{{soltData.info.age}}</h3>
</template>
</my-components6>
全局事件总线
在vue中,我们知道父子组件之间传值有多种方式,但是兄弟组件之间如何进行传值呢?全局事件总线即可优雅的解决兄弟组件之间的传值问题。
vue原型对象上包含事件处理方法有:
- $on(eventName,listener):绑定自定义事件监听
- $emit(eventName,data):分发自定义事件
- $off(eventName):解绑自定义事件监听
App.vue
<template>
<div id="app">
<my-components1></my-components1>
<br>
<my-components2></my-components2>
</div>
</template>
<script>
import MyComponents1 from "./components/MyComponents1"
import MyComponents2 from "./components/MyComponents2"
export default {
name: 'App',
components:{MyComponents1,MyComponents2},
data() {
return {};
}
}
</script>
实现全局事件总结步骤
- 给vue原型上面绑定$bus属性
//main.js文件
new Vue({
render: h => h(App),
//这里进行操作
beforeCreate(){
Vue.prototype.$bus = this
}
}).$mount('#app')
- 发送事件方
<template>
<div class="">
<h3>我是组件1</h3>
<button @click="sendMessageToComponts2">点我给组件2传值</button>
</div>
</template>
<script>
export default {
name: 'MyComponents1',
props: {},
data () {
return {
msg:"666"
}
},
methods:{
sendMessageToComponts2(){
//这里进行数据发送
this.$bus.$emit("sendMsg",this.msg)
}
}
}
</script>
- 接收事件方
<template>
<div class="">
<h3>我是组件2</h3>
<h3>接受组件1的数据:{{msg}}</h3>
</div>
</template>
<script>
export default {
name: 'MyComponents2',
data () {
return {
msg:""
}
},
mounted () {
//这里进行接收
this.$bus.$on("sendMsg",(data)=>{
this.msg = data
})
},
// 这里进行移除,要养成良好的编程习惯,不用了就移除掉
beforeDestroy(){
this.$bus.$off("sendMsg")
}
}
</script>
mixin
作用
- 公共逻辑的复用:当多个组件具有相同的逻辑或者方法时,可以将这些逻辑和方法提取到一个mixin对象中,然后在需要的组件中引入该mixin。这样可以避免在每个组件中重复编写相同的代码
- 跨组件的功能扩展:有时需要再多个组件中扩展相同的功能,例如添加全局的错误处理,路由守卫等,通过mixin,可以将这些功能定义为一个mixin,并在需要的组件中引入,以实现功能的统一扩展和统一管理
示例
管理系统中的列表分页功能
//定义一个mixin
export default {
data() {
return {
activatedIsNeedLoad: false,//组件激活时是否需要加载数据
listUrl: '',//列表接口地址
currentPage: 1, //当前页码
pageSize: 10,//每页显示的条数
totalItems: 0,//总记录数
totalPages: 0,//总页数
items: [] //当前页数据
}
},
created() {
console.log('Mixin created');
if (this.activatedIsNeedLoad) {
this.getDataList()
}
},
methods: {
getDataList() {
console.log("开始加载数据")
//这里发起网络请求
const parms = {
pageNum: this.currentPage,
pageSize: this.pageSize,
}
setTimeout(() => {
this.items = ["语文","数学"]
}, 3000);
},
// 修改每页条数
pageSizeChangeHandle(val) {
this.currentPage = 1;
this.pageSize = val;
this.getDataList();
},
// 分页, 当前页
pageCurrentChangeHandle(val) {
this.currentPage = val;
this.getDataList();
},
}
}
//组件中使用
<template>
<div class="box">
mixin
<div v-for="(item,index) in items" :key="index">{{index}}-{{item}}</div>
</div>
</template>
<script>
import pageMixin from "../mixin/index"
export default {
mixins:[pageMixin],
name: 'MyComponents13',
data() {
return {
activatedIsNeedLoad: true,
listUrl: 'https://xxxx',
currentPage: 1,
pageSize: 10,
};
},
mounted() {}
}
</script>
<style scoped></style>
VueX
在使用全局事件总线的时候,我们发现组件中传递数据很方便,但是当我们一个组件中的数据需要做全局响应式处理时,全局事件总线会显得有些麻烦,比如A组件中数据sum,在B组件,C组件中使用,而且sum数据变化,A,B,C三个组件都要更新,且A,B,C三个组件都能操作数据sum,这样一来,只要操作数据,A,B,C组件都有去接收数据或者触发事件。显然这样的数据保存在A,B,C组件哪个中都不太适合,VueX就可以完美的解决这种数据共享的问题。
Vuex 是什么
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. Vue2.x 使用 Vuex 3, Vue 3 匹配的 Vuex 4,开发中根据自己vue版本进行vuex的安装
VueX 安装
npm install vuex@3
本文使用的vue2.x 所以我们安装 vuex3
使用
- 创建文件: src/store/index.js
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
//准备actions对象:响应组件中用户的动作 异步提交
//mutations中参数:context,payload
//context:是一个包含与store实例具有相同方法和属性的对象,你可以访问commit、dispatch、getters等方法和属性
//payload: 是一个可选参数,用于传递额外的参数给actions函数
const actions = {
add(context,payload){
context.commit('ADD',payload)
}
}
//准备mutations对象:修改state中的数据 同步提交
//mutations中参数:state, payload
//state:表示当前的状态对象
//payload: 是一个可选参数,用于传递额外的参数给mutations函数
const mutations = {
ADD(state,payload){
state.sum += payload
}
}
//准备state对象:保存具体的数据
const state = {
sum:100,
todos: [
{ id: 1, text: 'Task 1', completed: true },
{ id: 2, text: 'Task 2', completed: false },
{ id: 3, text: 'Task 3', completed: true }
]
}
//getters用于从store中获取派生状态,可以对store中的状态进行计算或筛选,并返回一个新的值
const getters = {
sum(state){
return state.sum
},
completedTodos: state =>{
return state.todos.filter(todo => todo.completed)
}
}
//创建store
const store = new Vuex.Store({
state,
actions,
mutations,
getters
})
export default store
- main.js全局引入
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'
import store from './store/index'
Vue.config.productionTip = false
Vue.use(Vuex)
new Vue({
render: h => h(App),
store
}).$mount('#app')
- 组件中使用
<template>
<div>
<h5>mapState注入: {{mapStateSum}}</h5>
<h5>mapGetters注入: {{sum}}</h5>
<ul>
<li v-for="todo in completedTodos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
<button @click="add(1)">mapActions注入方法</button>
<p></p>
<button @click="ADD(2)">mapMutations注入方法</button>
<p></p>
<button @click="actionsClick">Actions方法调用</button>
<p></p>
<button @click="mutationsClick">Mutations方法调用</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from "vuex"
export default {
name: 'MyComponents10',
methods:{
//actions通过dispatch调用
actionsClick(){
this.$store.dispatch('add', 1);
},
//mutations通过commit调用
mutationsClick(){
this.$store.commit('ADD', 1);
},
//也可以这么写...mapActions(['add'])
...mapActions({
add:'add'
}),
//也可以这么写...mapMutations(['ADD'])
...mapMutations({
ADD:'ADD'
})
},
computed:{
//也可以这么写 ...mapState(['sum'])
...mapState({
mapStateSum:'sum'
}),
//也可以这么写
/*
...mapGetters({
x1:'sum',
x2:'completedTodos'
})
*/
...mapGetters(['sum','completedTodos'])
}
}
</script>
Vue Router
安装
本文使用vue2.x,所以我们安装vue-router时需要安装vue-router3版本,最新的是vue-router4,配套vue3.x使用
npm i vue-router@3
路由基本使用
- 新建router/index.js
import VueRouter from "vue-router";
import About from "../components/MyComponents12"
import Home from "../components/MyComponents11"
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
export default router
- main.js引入
//main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router/index'
Vue.config.productionTip = false
Vue.use(VueRouter)
new Vue({
render: h => h(App),
router:router
}).$mount('#app')
3. 组件中使用
<template>
<div id="app">
<div class="list-group">
<!-- Vue中router-link标签实现路由的切换 -->
<router-link to="/home" active-class="active" class="link">Home</router-link>
<router-link to="/about" active-class="active" class="link">About</router-link>
</div>
<div class="content">
<!-- 指定组件的显示位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
import MyComponents11 from "./components/MyComponents11"
import MyComponents12 from "./components/MyComponents12"
export default {
name: 'App',
components: { MyComponents11, MyComponents12 },
data() {
return {};
},
}
</script>
总结
- 每个路由组件都有自己的$route属性,存储自己的路由信息
- 整个应用有一个$rourer属性
嵌套路由
import VueRouter from "vue-router";
import About from "../components/MyComponents12"
import Home from "../components/MyComponents11"
import News from "../components/News"
import Message from "../components/Message"
const router = new VueRouter({
routes:[
{
path:'/home',
component:Home,
children:[
{
name:'homenews',
//在嵌套路由里面,注意path的写法,是news 不是/news
path:"news",
component:News
},
{
name:'homemessage',
path:"message",
component:Message
}
]
},
{
path:'/about',
component:About
},
]
})
export default router
路由参数传递
如何给路由组件传递参数? 开发中我们经常使用如下这两种方式
- 路由的query参数
- 路由的params参数
query
url类型:http://localhost:8080/#/home/message?id=1&title=test
- 字符串拼接写法
//id title 为变量 使用 :to
<router-link :to="`/home/news?id=${id}&title=${title}`">News</router-link>
- 对象写法
//这里的path可以使用命名路由 name代替
<router-link :to="{
path:'/home/message',
query:{
id:id,
title:title
}
}">Message</router-link>
<router-link :to="{
name'homemessage',
query:{
id:id,
title:title
}
}">Message</router-link>
参数的获取
mounted(){
const id = this.$route.query.id
console.log("id=",id)
const title = this.$route.query.title
console.log("title=",title)
}
params
url类型:http://localhost:8080/#/home/message/1/test
1. 配置路由,声明接收params参数
import VueRouter from "vue-router";
import About from "../components/MyComponents12"
import News from "../components/News"
import Message from "../components/Message"
const router = new VueRouter({
routes:[
{
path:'/about',
component:About,
children:[
{
name:"news",
//需要配置params参数
path:"news/:id/:title",
component:News
},
name:"message",
path:"message/:id/:title",
component:Message
},
]
},
]
})
export default router
- 字符串拼接写法
<!-- 携带params参数 字符串拼接写法-->
<router-link :to="`/about/news/${id}/${title}`">News</router-link>
- 对象写法
<!-- 携带params参数 对象写法 注意这里用的是name参数,不是path参数-->
<router-link :to="{
name:'message',
params:{
id:id,
title:title
}
}">Message</router-link>
对象写法在传递路由参数时,使用了命名路由,在router中进行配置name属性,这里不能使用path
参数的获取
$route.params.id
$route.params.title
编程式路由导航
不借助router-link实现路由的自由跳转
// 使用路径字符串
this.$router.push('/example')
// 使用包含命名路由的对象
this.$router.push({ name: 'exampleRoute' })
// 使用带有查询参数的对象
this.$router.push({ path: '/example', query: { key: 'value' } })
// 使用带有动态参数的路径
this.$router.push({ path: '/user/:id', params: { id: 123 } });
// 使用命名路由和参数
this.$router.push({ name: 'user', params: { userId: 123 } });
路由守卫
前置路由守卫
- 权限验证: 确保用户具有足够的权限访问特定页面。
- 登录检查: 验证用户是否已登录,如果未登录,则重定向到登录页面。
// main.js 或者 router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './views/Home.vue';
import Login from './views/Login.vue';
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{ path: '/', component: Home, meta: { requiresAuth: true } },
{ path: '/login', component: Login },
// ...其他路由配置
],
});
// 注册前置路由守卫
router.beforeEach((to, from, next) => {
// 获取路由元信息
const requiresAuth = to.meta.requiresAuth;
// 假设有一个登录状态检查的函数
const isAuthenticated = checkUserAuthentication();
if (requiresAuth && !isAuthenticated) {
// 如果需要登录权限且用户未登录,则重定向到登录页面
next('/login');
} else {
// 允许导航
next();
}
});
new Vue({
router,
render: h => h(App),
}).$mount('#app');
后置路由守卫
import VueRouter from "vue-router";
import Home from "../components/MyComponents11"
const router = new VueRouter({
routes:[
{
path:'/home',
component:Home,
meta:{title:"首页"}
}
]
})
//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from)
document.title = to.meta.title || 'DLS'
})
export default router
独享守卫
const routes = [
{
path: '/example',
component: ExampleComponent,
beforeEnter: (to, from, next) => {
// 路由独享守卫逻辑
next();
},
},
// ...其他路由配置
];
总体来说,beforeEnter 是一种更精确的导航守卫,它直接与单个路由相关,而 beforeEach 是一种全局守卫,适用于整个路由。
组件内守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}