Vue2+Vue3教程

一、vue的特点

  1. 遵循MVVM模式,采用组件化模式,提高代码复用率,且代码更好维护

  2. 声明式编码,让开发人员无需操作DOM,提高开发效率

  3. 使用虚拟DOM和diff算法,尽量复用节点

二、MVVM模型

  1. M:model模型,data中的数据
  2. V:view视图,模版代码
  3. VM:viewmodel视图模型,vue实例
  • 特点1:data中定义的所有属性,都出现在vm实例身上
  • 特点2:vm上所有的属性,以及Vue原型上所有属性,都可以在模版中直接使用

三、Object.defineProperty

      let number = 18;
      let person = { name: 'alan' }
      Object.defineProperty(person, 'age', {
        value: 18,
        enumerable: true, //控制属性是否可以被枚举,默认值false
        writable: true, //控制属性是否可以被修改,默认值false
        configurable: true, //控制属性是否可以被删除,默认值false

        // 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值会作为age的值
        get() {
          return number
        },
        // 当有人修改person的age属性是,set函数(setter)就会被调用,参数就是修改的具体值
        set(value) {
          console.log('修改的值', value)
          number = value;
        }
      })

数据代理:通过一个对象代理另一个对象属性的读写操作

vue中的数据代理:通过vm对象代理data对象中属性的操作

基本原理:

  1. vue将data中的数据拷贝到了_data属性中,
  2. 再将_data对象中的属性都添加一份到vm实例对象中
  3. 然后通过Object.defineProperty()给每一个添加到vm上的属性进行数据代理,都指定一个getter/setter,这样就能直接使用vm来访问属性
  4. 在getter/setter内部去做操作读写_data的对应属性
  5. _data又对data进行数据劫持实现响应式,遍历data对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。这些 getter/setter 让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更

重点:模拟vue响应式数据监测

let data = {
	name:'wzh',
	age:20
}		
const obs = new Observer(data);
	//准备一个vm实例对象
	let vm = {};
	vm._data = data = obs;
	// 定义一个观察器构造函数
	function Observer(obj){
		//汇总对象中所有的属性形成一个数组
		const keys = Object.keys(obj);
		keys.forEach(key=>{
			Object.defineProperty(this,key,{				
                get(){
					return obj[key]
				},
				set(val){
					console.log(`${key}的值被改变了,我要重新解析模版,生成新的虚拟dom节点跟老的虚拟dom对比,再生成真实dom`)
					obj[key] = val;
				}
			})			
     })
}

四、计算属性

1.定义:要用的属性不存在,要通过已有属性计算得来。

2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

3.get函数什么时候执行?

  • 初次读取时会执行一次。
  • 当依赖的数据发生改变时会被再次调用。

4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

5.备注:

  • 计算属性最终会出现在vm上,直接读取使用即可。
  • 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
// 完整写法
computed:{
		fullName:{
		    //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
		    //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
			get(){
				console.log('get被调用了')
				return this.firstName + '-' + this.lastName
			},
			//set什么时候调用? 当fullName被修改时。
			set(value){
				console.log('set',value)
				const arr = value.split('-')
				this.firstName = arr[0]
				this.lastName = arr[1]
			}
		}
}

// 不对计算属性进行修改时可简写
computed:{
		fullName(){
            console.log('get被调用了')
		    return this.firstName + '-' + this.lastName
		}
}

五、监听watch

深度监视:

(1).Vue中的watch默认不监测对象内部值的改变(一层)。

(2).配置deep:true可以监测对象内部值改变(多层)。

备注:

(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!

(2).使用watch时根据数据的具体结构,决定是否采用深度监视。

watch:{
		isHot:{
			immediate:true, //初始化时让handler调用一下
			deep:true, //开启深度监听 
			handler(newValue,oldValue){
				console.log('isHot被修改了',newValue,oldValue)
			}
		}
}



// 当只有handler没有其他属性时可简写
watch:{
		isHot(newValue,oldValue){
			console.log('isHot被修改了',newValue,oldValue,this)
		}
}

computed和watch之间的区别:

1.computed能完成的功能,watch都可以完成。

2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

两个重要的小原则:

1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。

2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

六、vue中的key的作用

1. 虚拟DOM中key的作用:

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,

随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

2. 对比规则:

        (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:

                ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!

                ②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

        (2).旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM随后渲染到到页面。

3. 用index作为key可能会引发的问题:

        (1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作:

                会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

        (2). 如果结构中还包含输入类的DOM:

                会产生错误DOM更新 ==> 界面有问题。

4. 开发中如何选择key?:

        (1).最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。

        (2).如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,

使用index作为key是没有问题的。

七、v-mode收集表单数据

  • 若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
  • 若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
  • 若:<input type="checkbox"/>

        1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)

        2.配置input的value属性:

                (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)

                (2)v-model的初始值是数组,那么收集的的就是value组成的数组

备注:v-model的三个修饰符:

lazy:失去焦点再收集数据

number:输入字符串转为有效的数字

trim:输入首尾空格过滤

八、过滤器filter

//全局过滤器
Vue.filter('lengthFormat',function(value){
	return value+'米'	
})


//局部过滤器
filters:{
	lengthFormat(value,str='米'){
		return value+str
	}
}

九、内置指令

v-cloak指令(没有值):

1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。

2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

<style>
	[v-cloak]{			
        display:none;
	}
</style>

<h2 v-cloak>{{name}}</h2>

v-once指令

1.v-once所在节点在初次动态渲染后,就视为静态内容了。

2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

<h2 v-once>初始化的n值是:{{n}}</h2>

v-pre指令

1.跳过其所在节点的编译过程。

2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

<h2 v-pre>Vue其实很简单</h2>

十、自定义指令

<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>

指令调用时机:

  1. 指令与元素成功绑定时(一上来)
  2. 指令所在的模板被重新解析时

自定义私有指令

directives:{
        // 大多数情况下只关心bind和update做重复动作不涉及页面的JS行为,可以简写为函数形式
		color(element,binding){
			console.log(this) //注意指令里的this都是window
			element.style.color = binding.value;
		},
	    fbind:{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时
			inserted(element,binding){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
		}
}

自定义全局指令

Vue.directive('color',{
         bind:function(element,binding){
                 element.style.color = binding.value;
         },                                          
})  

// 简写形式
Vue.directive('color',function(element,binding){
    element.style.color = binding.value;
})  

十一、组件化编程

1.组件和模块的概念

模块:

  • 理解:向外提供特定功能的js程序,一般就是一个js文件
  • 为什么:js文件很多很复杂
  • 作用:复用、简化js的编写,提高js运行效率

组件:

  • 定义:用来实现局部功能的代码和资源的集合(html/css/js/image...)
  • 为什么:一个界面的功能很复杂
  • 作用:复用编码,简化项目编码,提高运行效率

模块化:当一个应用的js都是以模块来编写的,那这个应用就是模块化应用

组件化:当应用中的功能都是以多组件方式来编写的,那这个应用就是组件化应用

2.组件的基本使用

  1. 定义组件

    使用 Vue.extend(options) 创建,其中options和 new Vue(options) 时传入的那个options几乎一样,但也有点区别;

    (1) el不要写, 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

    (2) data必须写成函数, 避免组件被复用时,数据存在引用关系。

  2. 注册组件

    (1) 局部注册:new Vue(options) 的时候 options 传入components选项

    (2) 全局注册:Vue.component('组件名',组件)

  3. 使用组件

    编写组件标签如 <school></school>

3.VueComponent

  1. school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
  2. 我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
  3. 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent

4.一个重要的内置关系

VueComponent.prototype.__proto__ === Vue.prototype

让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

十二、render函数

vue.js与vue.runtime.xxx.js的区别:

  1. vue.js是完整版的Vue,包含:核心功能+模板解析器。
  2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

vue/cli脚手架中默认import的vue版本是vue.runtime.xxx.js

因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用

render函数接收到的 createElement 函数去指定具体内容。

//引入Vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
//关闭vue的生产提示
Vue.config.productionTip = false

//创建Vue实例对象---vm
new Vue({
	el:'#app',
	// render函数完成了这个功能:将App组件放入容器中
    // 简写形式
    render: h => h(App),
	// 完整版形式
    // render(createElement){
    //     return createElement(App)
    // }
})

十三、props配置项

功能:让组件接收外部传过来的数据,只读属性不可改,强行改会发出警告

  1. 第一种方式(只接收) props:[ 'name' ] 
  2. 第二种方式(限制类型):props:{ name: String }
  3. 第三种方式(限制类型、限制必要性、指定默认值):
props:{
    name:{
        type:String, //类型
        required:true, //必要性
        default:'老王' //默认值
    }
}

十四、mixin混入

功能:可以把多个组件共用的配置提取成一个混入对象

使用方法:

  1. 定义混入:创建一个mixin.js文件,配置好混合对象并export暴露
    export const mixin = {    
         data(){....},
         methods:{....},
         mounted(){....},
         ....
    }
  2. 使用混入
    (1)局部混入
    <script>
        // 1.导入定义的mixin.js
        import { mixin } from '../mixin'
        // 2.在组件的mixins配置项中使用mixin,组件就可以使用mixin中所有的属性和方法
    	export default {
    		name:'School',
    		data() {
    			return {}
    		},
    		mixins:[mixin]
    	}
    </script>
    (2)全局混入( main.js中 
    // 1.在main.js中导入mixin混合对象
    import { mixin } from './mixin'
    
    // 2.全局使用mixin对象
    Vue.mixin(mixin)
    

备注:

1.组件和混入对象有同名选项发生冲突时,以组件本身的选项优先

2.组件和混入对象的生命钩子函数会合并为数组,都会被调用,混入对象的钩子函数先调用

十五、plugin 插件

1. 功能:用于增强Vue

2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据

3. 定义插件:创建一个plugins.js,并暴露一个对象

export default {
	install(Vue,options){
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法或属性
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
}

4. 使用插件:在main.js中导入plugin.js,使用 Vue.use(plugin) 应用插件

// 引入插件
import plugin from './plugins'

// 应用插件
Vue.use(plugin,1,2,3)

十六、组件自定义事件

  1. 场景:
    当子组件想传数据给父组件,要在父组件中给子组件绑定自定义事件,在子组件中触发自定义事件并将数据作为参数传给父组件
     
  2. 绑定方法
    a.第一种方法:使用v-on绑定
    <Demo @事件名="父组件方法"/>

    b.第二种方法:使用$on绑定

    <Demo ref="demo"/>
    ......
    mounted(){
        this.$refs.demo.$on('事件名',this.父组件方法)
    }

    可以使用 .once 修饰符或 $once 方法让自定义事件只能触发一次

  3. 触发自定义事件:子组件中使用 $emit

    this.$emit('事件名',数据)
  4. 解绑自定义事件:子组件中使用 $off

    this.$off('事件名') // 解绑一个自定义事件
    this.$off(['事件名2','事件名2']) // 解绑多个自定义事件
    this.$off() // 解绑组件实例上所有自定义事件
  5. 组件绑定原生事件:使用 .native 修饰符

    <Demo @click.native="父组件方法"/>

十七、全局事件总线(GlobalEventBus)

  1. 一种可以在任意组件间通信的方式
  2. 安装全局事件总线:将vm实例作为傀儡bus,绑定自定义事件所有组件都可调用
    new Vue({
       	......
       	beforeCreate() {
       		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
       	},
        ......
    }) 
  3. 使用全局事件总线:
    a. 接收数据:A组件想接收数据,在A组件中$bus绑定自定义事件,事件的回调方法留在A中

    methods(){
        demo(data){......}
    },
    mounted() {
        this.$bus.$on('自定义事件名',this.demo)
    }

    b. 提供数据:B组件调用$bus上绑定的自定义事件,并将数据作为参数传递

    this.$bus.$emit('自定义事件名',数据)
  4. 在$bus上绑定自定义事件的组件,在销毁前最好使用 $off 将自定义事件解绑

    beforeDestroy(){
        this.$bus.$off('自定义事件名')
    }

十八、$nextTick

1. 作用:在下一次 DOM 更新结束后执行其指定的回调。

2. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

3. 使用案例:修改数据展示input输入框后,自动聚焦

<input type="text" v-show="isEdit" ref="inputTitle">
...

handleEdit(){
    this.isEdit = show;
    this.$nextTick(()=>{
        this.$refs.inputTitle.focus()
    })
},

十九、网络请求

1.fetch请求

缺点是兼容性不太好,IE浏览器不能用

(1)get请求

fetch('请求路径').then(res =>
    res.json();
    // 状态码响应头,拿不到真实数据
).then((res) => {
    // 这里才是真实数据
    console.log(res)
}).catch((err) => {
    console.log(err)
})

(2)post请求


// post1:json格式
fetch('请求路径', {
    method: 'post',
    body: JSON.stringify({
        name:'qqq', age:20
    }),
    headers: {
      'Content-Type': 'application/json'
    }
}).then(res=>res.json()).then(res=>{
    console.log(res)
})

// post2:表单格式
fetch('请求路径', {
    method: 'post',
    body: 'name=qqq&age=20',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
}).then(res=>res.json()).then(res=>{
    console.log(res)
})

2.axios请求

(1)get请求

axios.get('请求路径').then(res=>{
    console.log(res.data)
},error=>{
    console.log(error.message)
})

(2)post请求

axios.post('请求路径',{name:'qqq',age:20}).then(res=>{
    console.log(res.data)
},error=>{
    console.log(error.message)
})

二十、配置代理服务器

1.方法一

在vue.config.js中添加如下配置:

devServer:{
    proxy:"http://localhost:5000" //目标服务器
}
  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

2.方法二

​编写vue.config.js配置具体代理规则:

module.exports = {
    devServer: {
        proxy: {
            '/api1': { // 匹配所有以 '/api1'开头的请求路径
                target: 'http://localhost:5000', // 代理目标的基础路径
                changeOrigin: true,
                pathRewrite: {'^/api1': ''}
             },
            '/api2': { // 匹配所有以 '/api2'开头的请求路径
                target: 'http://localhost:5001', // 代理目标的基础路径
                changeOrigin: true,
                pathRewrite: {'^/api2': ''}
            }
        }
    }
}

/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。


二十一:插槽

1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式

2. 分类:默认插槽、具名插槽、作用域插槽

3. 使用方式

  1. 默认插槽
    // 父组件中:
    <Category>
        <div>html结构1</div>
    </Category>
    
    // 子组件中:
    <template>
        <div>
        <!-- 定义插槽 -->
            <slot>插槽默认内容...</slot>
        </div>
    </template>
  2. 具名插槽:子组件中给slot标签定义name属性,父组件中给要插入的HTML结构添加 slot 属性或者 v-slot:插槽name ,注意 v-slot 只能用在 template 标签上
    父组件中:
    <Category>
        <template slot="center">
            <div>html结构1</div>
        </template>
        <template v-slot:footer>
            <div>html结构2</div>
        </template>
    </Category>
    
    子组件中:
    <template>
        <div>
            <!-- 定义插槽 -->
            <slot name="center">插槽默认内容...</slot>
            <slot name="footer">插槽默认内容...</slot>
        </div>
    </template>
  3. 作用域插槽:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
    简单理解:父组件复用子组件多次,要用子组件的同一数据渲染出不同的html结构
    父组件中:
    <Category>
        <template scope="scopeData">
        <!-- 生成的是ul列表 -->
        <ul>
            <li v-for="g in scopeData.games" :key="g">{{g}}</li>
        </ul>
        </template>
    </Category>
             
    <Category>
        <template slot-scope="scopeData">
        <!-- 生成的是h4标题 -->
        <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
        </template>
    </Category>
    
    子组件中:
    <template>
        <div>
            <slot :games="games"></slot>
        </div>
    </template>
    <script>
    export default {
        name:'Category',
        //数据在子组件自身
        data() {
            return {
                games:['红色警戒','穿越火线','劲舞团','超级玛丽']
            }
        },
    }
    </script>

二十二、Vuex

1. 概念:在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2. 搭建vuex环境

  1. 下载安装vuex   npm i vuex@3
  2. 创建文件:src/store/index.js
       //引入Vue核心库
       import Vue from 'vue'
       //引入Vuex
       import Vuex from 'vuex'
       //应用Vuex插件
       Vue.use(Vuex)
       
       //准备actions对象——响应组件中用户的动作
       const actions = {}
       //准备mutations对象——修改state中的数据
       const mutations = {}
       //准备state对象——保存具体的数据
       const state = {}
       
       //创建并暴露store
       export default new Vuex.Store({
       	actions,
       	mutations,
       	state
       })

3. vuex基本使用

  1. 初始化数据、配置 actions、配置 mutations、配置 getters 加工数据,操作文件 store.js
       //引入Vue核心库
       import Vue from 'vue'
       //引入Vuex
       import Vuex from 'vuex'
       //引用Vuex
       Vue.use(Vuex)
       
       // 异步操作,触发mutations的方法修改数据
       const actions = {
            //响应组件中加的动作
       	    jia(context,value){
       		    context.commit('JIA',value)
       	    },
       }
       const mutations = {
           //执行加
       	    JIA(state,value){
       		    state.sum += value
       	    }
       }
       //初始化数据
       const state = {
            sum:0
       }
       //对数据加工再暴漏,类似计算属性
       const getters = {
           bigSum(state){
       	       return state.sum * 10
       	   }
       }
       //创建并暴露store
       export default new Vuex.Store({
       	    actions,
       	    mutations,
       	    state,
            getters,
       })
  2. 组件中读取vuex中的数据:

    // 读取state的数据
    this.$store.state.sum
    // 读取getters包装的数据
    this.$store.getters.bigSum
  3. 组件中修改vuex中的数据:

    // 修改数据行为是异步操作,使用disptach触发actions,比如接口请求或者定时器
    this.$store.dispatch('action中的方法名',数据)
    
    // 修改数据行为是同步操作,直接使用commit触发mutations
    this.$store.commit('mutations中的方法名',数据)

4. 简化代码的四种map辅助函数

在使用vuex的组件中导入 

import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'

  1. mapState:用于映射 state 中的数据为计算属性
  2. mapGetters:用于映射 getters 中的数据为计算属性
  3. mapActions:用于生成与 actions 对话的方法,即包含 $store.dispatch(xxx) 的函数
  4. mapMutations:用于生成与 mutations 对话的方法,即包含$store.commit(xxx) 的函数
     

注意:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

computed: {
     //借助mapState生成计算属性:sum、school、subject(对象写法)
      ...mapState({sum:'sum',school:'school',subject:'subject'}),
            
     //借助mapState生成计算属性:sum、school、subject(数组写法)
     ...mapState(['sum','school','subject']),

     //借助mapGetters生成计算属性:bigSum(对象写法)
     ...mapGetters({bigSum:'bigSum'}),
   
     //借助mapGetters生成计算属性:bigSum(数组写法)
     ...mapGetters(['bigSum'])
},
methods:{
     //靠mapActions生成:incrementOdd、incrementWait(对象形式)
     ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
   
     //靠mapActions生成:incrementOdd、incrementWait(数组形式)
     ...mapActions(['jiaOdd','jiaWait'])

     //靠mapActions生成:increment、decrement(对象形式)
     ...mapMutations({increment:'JIA',decrement:'JIAN'}),
       
     //靠mapMutations生成:JIA、JIAN(对象形式)
     ...mapMutations(['JIA','JIAN']),
}

5.模块化+命名空间:让代码更好维护,让多种数据分类更加明确。

  1. 修改 store.js
       const countAbout = {
         namespaced:true,//开启命名空间
         state:{x:1},
         mutations: { ... },
         actions: { ... },
         getters: {
           bigSum(state){
              return state.sum * 10
           }
         }
       }
       
       const personAbout = {
         namespaced:true,//开启命名空间
         state:{ ... },
         mutations: { ... },
         actions: { ... }
       }
       
       const store = new Vuex.Store({
         modules: {
           countAbout,
           personAbout
         }
       })
  2. 开启命名空间后,组件中读取 state 数据

    //方式一:自己直接读取
    this.$store.state.countAbout.sum
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
  3. 开启命名空间后,组件中读取 getters 数据

    //方式一:自己直接读取
    this.$store.getters['countAbout/bigSum']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
  4. 开启命名空间后,组件中调用 dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('countAbout/jiaOdd',2)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  5. 开启命名空间后,组件中调用 commit

    //方式一:自己直接commit
    this.$store.commit('countAbout/JIA',2)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

二十三、路由

1. 理解:

一个路由(route)就是一组映射关系(key - value),key为路径,value可能为function或者component

2. 路由分类:

  • 后端路由:value 是 function,用于处理客户端发出的请求
    工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
  • 前端路由:value 是 component,用于展示页面
    工作过程:当浏览器的路径改变时,对应的组件就会显示

3.基本使用

  1. 安装 vue-router  命令 npm i vue-router@3
  2. 创建 router/index.js 文件,配置并暴露路由信息对象
       //引入VueRouter
       import VueRouter from 'vue-router'
       //引入路由组件
       import About from '../pages/About'
       import Home from '../pages/Home'
       
       //创建router实例对象,去管理一组一组的路由规则
       const router = new VueRouter({
       	routes:[
       		{
       			path:'/about',
       			component:About
       		},
       		{
       			path:'/home',
       			component:Home
       		}
       	]
       })
       //暴露router
       export default router
  3.  在main.js中应用插件并导入配置的router对象
    //引入VueRouter
    import VueRouter from 'vue-router'
    //应用插件
    Vue.use(VueRouter)
    //引入路由器
    import router from './router'
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	router,
    })
    
  4. 实现路由切换功能
    <!--浏览器会把router-link标签解析成a标签,active-class可以配置高亮样式 -->
    <router-link active-class="active" to="/about">About</router-link>
    <router-link active-class="active" to="/home">Home</router-link>
    <!--router-view标签用来作为路由路径对应组件的展示容器 -->
    <router-view></router-view>

4. 嵌套路由

  1. 配置路由规则,使用 children 配置项:

    routes:[
       	{
       		path:'/about',
       		component:About,
       	},
       	{
       		path:'/home',
       		component:Home,
       		children:[ //通过children配置子级路由
       			{
       				path:'news', //此处一定不要写:/news
       				component:News
       			},
       			{
       				path:'message',//此处一定不要写:/message
       				component:Message
       			}
       		]
       	}
    ]
  2. 使用 router-link 跳转时 to 属性要写完整路径

    <router-link active-class="active" to="/home/news">News</router-link>
    <router-link active-class="active" to="/home/message">Message</router-link>
  3. 命名路由:添加 name 属性简化跳转

    routes:[{
        path:'/demo',
        component:Demo,
        children:[
            {
          	    path:'test',
          		component:Test,
          		children:[
          			{
                         name:'hello' //给路由命名
          				 path:'welcome',
          				 component:Hello,
          			}
          		]
             }
         ]
    }]
    
    
    <!--简化前,需要写完整的路径 -->
    <router-link to="/demo/test/welcome">跳转</router-link>
          
    <!--简化后,直接通过名字跳转 -->
    <router-link :to="{name:'hello'}">跳转</router-link>
          
    <!--简化写法配合传递参数 -->
    <router-link 
        :to="{
            name:'hello',
            query:{
                id:666,
                title:'你好'
            }
        }"
    >跳转</router-link>

5. 路由 query 传参

  1. 传递参数
    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
       				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
        :to="{
       		path:'/home/message/detail',
       		query:{
       		   id:666,
               title:'你好'
       		}
       	}"
    >跳转</router-link>
  2. 接收参数

    $route.query.id
    $route.query.title

6.路由 params 传参

  1. 配置路由path时,声明接收params参数
    routes:[{
       	path:'/home',
       	component:Home,
       	children:[
       		{
       			path:'news',
       			component:News
       		},
       		{
                path:'message',
       			component:Message,
       			children:[
       				{
       					name:'Details',
       					path:'detail/:id/:title', //使用占位符声明接收params参数
       					component:Detail
       				}
       			]
       		}
       	]
    }]
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
       				
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
       	:to="{
       		name:'xiangqing', // params对象传参必须使用name匹配路径!!!
       		params:{
       		   id:666,
               title:'你好'
        }"
    >跳转</router-link>

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用 name 配置!

  3. 接收参数

    $route.params.id
    $route.params.title

7.路由的 props 配置

​ 作用:让路由组件更方便的收到参数,使用时在路由组件的props中声明key

{
	name:'xiangqing',
	path:'detail/:id/:title',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	props:{id:'001',title:'你好啊'}

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件,注意:只有params传参生效!!!
	props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props(route){
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
}


Detail组件使用:
<script>
    export default ({
        name:"Detail",
        props:['id','title']
    })
</script>

8. <router-link> replace 属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为 pushreplace,push 是追加历史记录,replace 是替换当前记录。路由跳转时候默认为 push
  3. 如何开启 replace 模式:
    ​​​​​​​<router-link replace .......>News</router-link>

9.编程式路由导航

作用:不借助 <router-link> 实现路由跳转,让路由跳转更加灵活

   this.$router.push({
   	    name:'Detail',
   		params:{
   			id:xxx,
   			title:xxx
   		}
   })
   
   this.$router.replace({
   	    name:'Detail',
   		params:{
   			id:xxx,
   			title:xxx
   		}
   })
   this.$router.forward() //前进
   this.$router.back() //后退
   this.$router.go(3) //可前进也可后退 3前进3步 -3后退3步

10.使用 <keep-alive> 缓存路由组件

<!--使用keep-alive标签包裹router-view容器,
include定义缓存白名单,表示缓存的组件名,
exclude定义缓存黑名单,表示不缓存的组件名-->
<keep-alive include="News">   
    <router-view></router-view>
</keep-alive>
<!--缓存多个路由组件-->
<keep-alive :include="['News','Message']">   
    <router-view></router-view>
</keep-alive>

11.路由组件生命钩子函数

<keep-alive> 缓存的路由组件会带有两个钩子函数,用于捕获路由组件的激活状态。

activated(){
    console.log('路由组件被激活时触发')
},
deactivated(){
    console.log('路由组件失活时触发')
},

12.路由守卫

作用:对路由进行权限控制

分类:全局守卫、独享守卫、组件内守卫

  1. 全局守卫:在 src/router/index.js 中使用 beforeEach 前置守卫 和 afterEach 后置守卫
    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
        if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
            if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
                next() //放行
            }else{
                alert('暂无权限查看')
            }
        }else{
            next() //放行
        }
    })
       
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
        if(to.meta.title){ 
            document.title = to.meta.title //修改网页的title
        }else{
            document.title = 'vue_test'
        }
    })
  2. 独享守卫:每个路由规则都有独享的 beforeEnter 守卫

    {
        path:'class',
        component:Class,
        meta:{
            isAuth:true,
        },
        beforeEnter(to,from,next){
            console.log('路由独享守卫')
            next();
        }
    },
  3. 组件内守卫:路由组件有 beforeRouteEnter 和 beforeRouteLeave 两个守卫

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }

注意:所有路由钩子的执行顺序如下
beforeEach(全局前置) --> beforeEnter(独享) --> beforeRouteEnter(组件进入) --> afterEach(全局后置) --> beforeRouteLeave(组件离开)​​​​​​​

13.路由器的两种工作模式

1. 对于一个url来说,# 及其后面的内容就是hash值。

2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

3. hash模式:

  • 1. 地址中永远带着#号,不美观 。
  • 2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
  • 3. 兼容性较好。

4. history模式:

  • 1. 地址干净,美观 。
  • 2. 兼容性和hash模式相比略差。
  • 3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值