Vue2.0学习总结

126 阅读16分钟
<!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

截屏2022-12-13 18.08.44.png

理解数据代理

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)
    }
  }
}

截屏2023-11-28 19.53.38.png

我们发现当点击添加老刘时,input框内的数据错乱了,这并不是我们想要的结果, 为什么会这样,是因为和vue的设计有关

截屏2023-11-28 19.57.20.png

要解决这个问题,我们只需要将 :key="p.id" 即可

  • 虚拟DOM中key的作用 key是虚拟DOM对象的标识,当数据发生变化时,vue会根据【新数据】生成【新的虚拟DOM】,随后vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

对比规则:

  1. 旧虚拟dom中找到与新虚拟dom中相同的key:

若虚拟dom中内容没变,直接使用之前的真实dom; 若虚拟dom中内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom

  1. 旧虚拟dom中未找到与新虚拟dom相同的key:

创建新的真实dom,随后渲染到页面

  1. 用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'

A151BB20-3AB7-429B-9132-1722E5E965B2.png

上面过滤器也可以这么写

<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);
    }
  }
}

截屏2023-11-30 14.06.57.png 通过运行发现:我们并不能取到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生命周期

  1. beforeCreate:此时无法通过vm访问到data中的数据,methods中的方法。
  2. created:此时可以通过vm访问到data中的数据,methods中配置的方法
  3. beforeMount:页面呈现的都是未经Vue编译的DOM结构,所有对DOM的操作,最终都不奏效
  4. mounted:页面中呈现的是经过vue编译的Dom,对Dom的操作均有效,一般在此进行:定时器的开启,发送网络请求,订阅消息,绑定自定义事件等初始化操作
  5. beforeUpdate:此时数据是新的,但是页面是旧的,即:页面尚未和数据保持同步
  6. updated:此时数据是新的,页面也是新的,即:页面和数据保存同步
  7. beforeDestory:vm中所有的data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、去选订阅消息、解绑自定义事件等收尾操作
  8. destoryed:vue实例被销毁

两个特殊的:

activated和deactivated:这两个比较特殊,其中activated是被 keep-alive 缓存的组件激活时调用,deactivated是被keep-alive 缓存的组件失活时调用。

生命周期.png

来源:禹神

实际开发中使用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>

结果如下图:

截屏2023-12-19 17.36.00.png

我们发现新插入的节点在页面上面显示了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>

截屏2023-12-19 17.43.20.png

作用域插槽

作用:可从组件向组件使用处传数据

//子组件
<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

路由基本使用

  1. 新建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

  1. 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>

总结

  1. 每个路由组件都有自己的$route属性,存储自己的路由信息
  2. 整个应用有一个$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

路由参数传递

如何给路由组件传递参数? 开发中我们经常使用如下这两种方式

  1. 路由的query参数
  2. 路由的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) {
}