7.Vue

零、介绍

Vue:用于构建用户界面的渐进式JavaScript框架

Vue允许开发者将网页分割成多个可复用的组件,每个组件都包含各自的html,css,js

在这里插入图片描述

Vue特点:

  • 组件化,一个组件即为.vue文件,包括html、css、js;
  • 声明式代码,编码人员无需直接操作DOM;
  • 使用虚拟DOM和优秀的Diff算法,尽量复用DOM节点;

在这里插入图片描述

一、Vue核心

vue.js通过script 标签引入,Vue 会被注册为一个全局变量,可以理解为这时候Vue就是一个类,可以通过Vue构造函数来新建Vue实例。

简单例子eg:

<head>
    ...
    <!--导入vue.js -->
    <script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
    <!-- html中需要一个容器来与vue实例绑定-->
    <div id="root">
        <h1>
            Hello!{{name}}
        </h1>
    </div>
    
    <script>
        <!--实例化一个vue对象,构造函数是一个配置对象-->
    	var demon = new vue(
            {
                <!--通过id选择器与上面的容器进行绑定-->
                el:"#root",
                data:{
                	name:"world"
            	}
            }
        )
    </script>
</body>
  • Vue实例中,构造函数需要传入的参数是一个对象,称为配置对象,改对象中包括el、data等属性
  • 与Vue实例绑定的容器内的代码,称为Vue模板,代码主要还是html格式,但会涉及到一些模板语法,如{{…}}

1.1 模板语法

插值语法一般用于标签体内容;<>外

指令语法一般用于标签的属性;<>内

插值语法

上述的{{name}}就是插值语法;

  • 功能:用于解析标签体内容

  • 写法:{{xxx}},xxx是js表达式,且可以直接读取data中所有属性;

指令语法

  • 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件)
  • 举例:v-bind:href=“xxx”,xxx同样写为js表达式

假设vue实例如下:

new Vue({
    el:#root,
    data:{
    	name:'jack',
    	url:'http://www.baidu.com'
	}	
})

那么可通过指令语法将url与a标签中的url绑定;

<div id="root">
    <a v-bind:href="url">百度一下</a>
</div>
  • 如果没有v-bind: 。那么<a href=“…” 就是一个普通的html属性

  • 加上了v-bind,“”里的内容就当成了JS表达式去执行,即读取url属性

  • ” v-bind:“可以简写为“ : ”

  • vue的指令语法一般都是以v-开头,

1.2 数据绑定

单向绑定:

上面举的v-bind的都是单向绑定,即data中的数据改变,会影响到页面中的值;但是页面值的改变并不会影响vue实例的数据;

双向绑定

通过v-model指令,可以实现双向绑定;

<body>
<div id="demo">
    单向绑定:<input type="text" v-bind:value="name"><br>
    双向绑定:<input type="text" v-model="name"><br>
    <h1>vue实例中的数据情况:name:{{name}}</h1>
</div>
<script type="text/javascript" src="../vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#demo',
        data: {
            name:'君'
        }
    })
</script>
</body>

在这里插入图片描述

注意:v-model只能用于表单类元素上;v-model:value="xxx"可以简写为v-model=“name”,因为v-model默认收集的就是value值;

1.3MVVM模型

M:Model 模型:data中的数据

V:View 视图:模板代码

VM:ViewModel 视图模型:Vue实例

在这里插入图片描述

  • data中的所有属性,最后都出现在了vm(vue实例)上;
  • vm上所有的属性,以及Vue原型上的所有属性,在模板代码中都可以直接使用;

1.4 数据代理

Object.defineProperty()方法:定义(添加)对象属性的方法;

Object.defineProperty(对象, ‘属性名’,配置对象)

eg:

var student={
    name:'张三',
    sex:'男'
}
Object.defineProperty(student,'age',{
    value:18,	//设置数组值;
    enumerable:true,	//控制属性是否可以枚举,默认false
    writable:true,	//控制属性是否可以被修改,默认false
    configurable:ture,	//控制属性是否可以被删除,魔法false
    get(){		//读取属性值,当有人试图输出student.age时,get()就会被调用
        ...;
        return xxx;
    }
    set(value){		//设置属性值,当有人试图修改age属性值时,set()就会被调用
    	xxx = value;
	}
})

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

使用Object.defineProperty方法来实现;

简单举例:

var obj1 ={x:100};
var obj2 ={y:200};
Object.defineProperty(obj2,'x',{
    get(){
        return obj1.x;
    }
    set(value){
    obj1.x = value;
	}
})
//即通过obj2来读写obj1的x

转念想一想,为什么vue实例vm可以直接通过vm.name来访问data里的name,其实这也是数据代理。

Vue中的数据代理:即通过vm对象来代理data对象中属性的操作(读写)

Vue.set()方法

后期为了能够添加响应式属性(),需要通过Vue.set方法添加属性;

Vue.set(待加入属性的对象,‘需要添加的属性’,‘添加的属性值’)
Vue.set( target, propertyName/index, value )

eg:

Vue.set(vm._data.info,'sex','男')

它必须用于向响应式对象上添加新 property,注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。即target不能是vm,或vm._data

1.5 事件处理

通过v-on指令来传达事件处理函数;

<body>
<div id="demo">
    <button v-on:click="sayHello">点我一下</button>
</div>
<script type="text/javascript" src="../vue.js"></script>
<script>
    new Vue({
        el:'#demo',
        data:{
            name:'君',
            age:21
        },
        methods:{
            sayHello:function(){
                alert('你好,我叫'+this.name+',今年我'+this.age);
            }
        }
    })
</script>
</body>
  • 使用v-on:xxx来绑定事件,或者简写为@xxx,xxx是事件名;
  • 事件的回调定义在vue实例的methods对象中,最终会在vm上(此处不是数据代理)
  • methods中配置的函数不要使用箭头函数,否则this指向window不是vm
  • methods中配置的函数都是被vue管理的函数。this指向vm或组件实例对象;
  • @click=“demo"与@click="demo($event)"效果一致,但后者可以传参;

事件修饰符

  • prevent:阻止默认事件;(常用)
  • stop:阻止事件冒泡;(常用)应用于子元素
  • once:事件只触发一次;(常用)
  • capture:使用事件的捕获模式;在事件捕获阶段就开始处理
  • self:只有event.target是当前操作的元素才触发事件,使用这个也能有阻止冒泡效果,应用在父元素
  • passive:事件的默认行为立即执行。无需等待事件回调执行完毕;正常流程是先执行完回调事件再执行默认事件;
<!--a标签默认点击事件是打开超链接,此处可以阻止默认事件,发送用户定义事件-->
<a herf="http://www.baidu.com" @click.prevent="sayHello">我才不是超链接</a>

事件冒泡 :当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window 。(注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。)

事件修饰符可以连着写,如click.prevent.stop,先阻止默认行为,在阻止冒泡

键盘事件

keydown,按下按键触发,不需要抬起;

keyup,按下按键,直至抬起才触发;

  • Vue中常用键盘的别名:

    • 回车:enter
    • 删除:delete
    • 退出:esc
    • 空格:space
    • 换行:tab(特殊,必须配合keydown使用)
    • 上、下、左、右:up down left right
  • vue未提供的别名,可以使用原始key值,双子母的key值转为首字小写加短横线连接的形式,如caps-lock

  • 系统修饰键(用法特殊):ctrl、alt、shift、win键或meta键

    • 配合keyup时,需要按下修饰键的同时,按住其他键,随后释放其他键才能触发
    • 配合keydown时,正常触发;
  • 可以通过keyCode指定按键,不过此特性将要被废弃,不建议使用

  • Vue.config.keyCodes.自定义键名 = 键码 ;来定制按键别名;

1.6 计算属性

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。

  • 计算属性写在配置对象的computed对象中;

  • 通过已有的属性计算得来;

  • 底层采用了Object.defineProperty方法提供getter和setter;

  • 与methods相比,内部采用了缓存机制(复用),效率更高;

  • 计算属性最终会出现在vm上,可直接读取;

  • 如果计算属性要被修改,那么必须写set函数,且要修改计算时所依赖的数据

    eg:

var vm = new Vue({
    el:"#demo",
    data:{
        firstName:"张",
        lastName:"三"
    },
    computed:{
        fullName:{
            get(){
                return this.firstName + '-' + this.lastName;
            }
            set(value){
        		const arr = value.split('-');
    			this.firstName = arr[0];
                this.lastName = arr[1];
   			}
        }
    }
})

get在何时被调用?

  • 用户初次读取fullName时;
  • 所依赖的数据发送改变时(例中为firstName和lastName)

set(value)何时被调用?

  • fullname被修改时

计算属性简写

当计算属性只读不改时,可以采用简写形式:

即不需要再写get(),直接将计算属性中的对象写成一个有返回值函数的形式;调用依然通过计算属性名(或叫函数名)调用,不需要加()

eg:

var vm = new Vue({
    el:"#demo",
    data:{
        firstName:"张",
        lastName:"三"
    },
    computed:{
        fullName:function(){
                return this.firstName + '-' + this.lastName;
        }
    }
})

1.7 监视属性

顾名思义,这个属性能个监视vue实例的属性值的变化;

格式:

watch:{
    需要被监视的属性名:监视配置对象
}

配置对象里的属性有:

  • handler(newValue,oldValue){…} 函数,此函数在被监视属性的值发送变化时被调用,newValue和oldValue只是形参,前者是变化后的监视属性值,后者是变化前的值;只写一个形参时指变化后的值
  • immediate:true(或者flase),该属性值为true时,初始化时handler就会被调用一次(即不需要等待监视属性的变化)
  • deep:true,深度监视,监视多级结构中,某个属性的变化,比如说监视的属性,其属性值是一个对象,正常情况下该对象的某个值发生改变时,handler函数并不会被调用。

也可以通过vue实例来监视:格式如下:

vm.$watch('需要被监视的属性名',监视配置对象)

数据监听原理

监视属性简写

但配置项中只有handler函数时,可以采用简写,格式如下:

watch:{
    需要被监视的属性名(newValue,oldValue){...}
}
vm.$watch('需要被监视的属性名',function(newValue,oldValue){...})

computed和lwatch之间的区别:

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

两个重要的小原则:

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

1.8 过滤器属性

使用配置对象属性:filter

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。

语法:
1.注册过滤器:

  • 全局的过滤器:Vue.filter(name,callback)
  • 局部的过滤器:new Vue(filters:{过滤器名:函数体})

2.使用过滤器:{{ xxx|过滤器名}}或v-bind:属性=“xxx|过滤器名"

备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联

2.并没有改变原本的数据,是产生新的对应的数据

1.9 class与style绑定

class绑定

通过v-bind,样式名与vue实例中的数据绑定

绑定class样式:

  • 字符串写法:适用于样式类名不确定,需要动态指定:

    <div class="basic" :class="mood" >
    </div>
    
    
    ...
    ...
    data:{
    	mood:"style1"
    }
    
  • 数组写法,适用于要绑定的样式格式不确定、名字也不确定:

    <div class="basic" :class="classArr">
    </div>   //classArr中的样式全部会被使用
    
    ...
    ...
    data:{
    	classArr:['style1','style2','style3']
    }
    
  • 对象写法:适用于要绑定的样式个数确定,名字也确定,但要动态决定用不用

    <div class="basic" :class="classObj">
        
    </div>
    ...
    ...
    data:{
    	classObj:{
    		style1:true,
    		style2:false,
    		style3:true
    	}
    }
    

style内联样式绑定

v-bind后面的语句不能使用诸如font-size:40px;此类css语法,因为这不是JS语句,需要写成对象的形式,并且font-size这类属性需要改成fontSize形式;

<div :style="{fontSize: size + 'px'}">
    ...
</div>
...
...
data:{
	...
	size:40
}

或者采用对象写法:

<div :style="classObj">
    ...
</div>
...
...
data:{
	classObj{
		fontSize:'40px',
		color:'red'
	}
}

1.10 条件渲染

  • v-show指令,控制标签元素的显示,底层是通过display:none;来实现的;

    <p v-show="flase">	//不会显示,但结构还在
        hello!
    </p>
    
  • v-if指令,v-if=“false”,连结构都不在了

1.11 列表渲染

通过v-for指令来遍历数组或对象,渲染列表

:key建议绑定数据里的唯一标识,对于固定不变的数据,index固然可行,但是对于可能会发生变化的数据尤其是插入、删除数据此类的变化,则应该绑定数据列表中用户定义的数据列,可以理解为数据库表里的主键

数组格式:(遍历各个元素)

<ul>
    <!--这里的p和index只是一个形参,index可以看成一个唯一索引,数组index返回0、1、2等值-->
    <li v-for="(p,index) in persons" :key="index">
    	{{index}}-{{p.name}}-{{p.age}}
    </li>
</ul>
...
...
data:{
	persons[
		{name:'张三',age:18},
		{name:'李四',age:19},
		{name:'王五',age:18}
	]
}

对象格式:(遍历各个属性)

<ul>
    <!--对象index返回属性名-->
    <li v-for="(p,index) in persons" :key="index">
    	{{index}}-{{p}}
    </li>
</ul>
...
...
data:{
	persons:{name:'张三',age:18,sex:'男'}	
}

也可以用来遍历字符串和指定次数;(用得少))

key的原理

在这里插入图片描述

在这里插入图片描述

列表过滤

filter函数

1.12 其他指令

其他内置指令

  • v-test

    <p>{{name}}</p>
    
    <p v-test="name"></p>
    
  • v-html

    与v-test作用相似,但是v-html支持结构式解析,即name可以是带标签的文本,并且标签能够被渲染

  • v-clock

    • 特殊属性,Vue实例创建完毕并接管容器后,会删掉v-clock属性
    • 使用css的属性选择器,配合v-clock可以解决网速慢时页面展示{{name}}的问题
  • v-once

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

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

      <p v-once>初始值为:{{n}}</p>
      <p>当前值为:{{n}}</p>
      <button @click="n++">点我n+1</button>
      
  • v-pre

    • 跳过其所在节点的编译过程。
    • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

自定义指令

使用配置对象属性:directives

1.13 Vue生命周期

在这里插入图片描述

二、Vue组件化编程

组件就是一块砖,哪里需要哪里搬

vue组件可分为:

  • 非单文件组件:一个文件中含有n个组件
  • 单文件组件:一个文件只包含一个组件

2.1 非单文件组件

创建→注册→使用

2.1.1 基本使用

创建

格式:

const 组件名 = Vue.extend(配置对象)
//简写形式
const 组件名 = 配置对象

这个配置对象和vue示例的配置对象大体一致,除了:

  • 组件定义时,配置对象里不能写el属性,组件是可复用的,不需要和容器绑定;
  • data属性不能写成对象形式,必须写成函数形式(函数返回值为对象);
  • 组件的html结构片段,x写在组件配置对象的template属性中,注意,此属性中只能写一个根标签,一般为div;
const stu = Vue.extend({
	data(){
		return{
			stuName:'张三',
			age:18
		}
	},
    template:'
    	<div>
    		<h2> 学生姓名:{{stuName}}</h2>
            <h2> 学生年龄:{{age}}</h2>
    	</div>
    '	
})
const sch = Vue.extend({
	data(){
		return{
			schName:'合肥工业大学',
			address:'安徽合肥'
		}
	}
    template:'
    	<div>
    		<h2> 学校名:{{schName}}</h2>
            <h2> 学校地址:{{address}}</h2>
    	</div>
    '
})
注册

在vue实例中注册,需要用到配置对象的components属性;

  • 局部注册:注册在vue实例中

    格式:

    var vm = new Vue({
        ...,
        ...,
        components:{
        	正式组件名:创建阶段的组件名,		//两者一致时可以直接写一个
        	...
    	}
    })
    

eg:

var vm = new Vue({
	el:'#demo',
	data:{},
	components:{
		student:stu,
		school:sch
	}
})
  • 全局注册:在实例外通过Vue.component方法注册

    格式:

    Vue.component('正式组件名',组件对象名或组件配置对象)
    
使用

直接在vue实例所绑定的容器中调用组件标签即可;

eg:接上例

<div id="demo">
    <student></student>
    <school></school>
</div>
注意事项
  • 关于组件名:
    • 一个单词组成:小写,首字大小写都可;
    • 多个单词组成:单词小写并且通过-来连接(JS代码下要’'引起来),或者所有单词首字大写(这种只能在脚手架环境下使用)
  • 关于组件标签:
    • 组件名尽可能回避已有标签名或与其相似的组件名,如P,Div,div,
    • 组件标签的名字使用的是注册的名字
    • 在脚手架环境下,可以采用自闭合形式的标签(即单标签)

2.1.2 组件的嵌套

先把子组件创建好,再把它注册到父组件的创建配置对象中,属性也是components,子组件的标签也要写在父组件的template属性中,最后再把父组件注册到Vue实例或全局,调用父组件标签即可。

格式:

假设A>B:


<div id='demo'>
    <A></A>
<div>

...

const com_b = Vue.extend({...});
const com_a = Vue.extend({
	...,
	tamplate:'
		...
		<B></B>
		...
	',
	components:{
		B:com_b
}
})
var vm = new Vue({
    el:'...',
    ...,
    components:{
    	A:com_a
	}
})

app组件是所有组件的父组件,app又在vue实例容器之下。

2.2 Vue与组件

组件的本质

  • 创建的组件本质上并不是普通的对象,而是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的;
  • 我们写的组件标签,Vue解析时会帮我们创建他们的实例对象

Vue实例与组件实例

在这里插入图片描述

一个重要的内置关系:VueComponent的原型对象的原型对象,指向的是Vue的原型对象,而不是直接指向Object对象

2.3 单文件组件

单文件组件,即.vue文件;

1、创建单文件组件

命名建议采用Java中的类名命名的样式,即单词首字大写

.vue文件的构成:

  • template标签:组件的结构
  • style标签:组件的样式
  • script标签:组件的行为交互

script标签中不可直接按照非单文件组件的样式来创建组件,需要(School.vue):

  • 由于外面要导入此组件,所以在定义时需要暴露,使用export关键字,三种形式选择一种即可,一般使用默认暴露

    <script>
    //1、统一暴露
        const School = Vue.extend({
            ...
        })
        export {School}
    //2、分别暴露
        export const School = Vue.extend({
        	...                          
        })
    //3、默认暴露
        export default{
            name:'School',
            ...
        }
    </script>
    

2、创建App.vue

写完单文件组件后,必须要有App.vue文件,该文件的作用是汇总所有组件,

App.vue文件中,格式如下:

<template>
<div class='style1'>
	<School></School>    
</div>
</template>

<script>
    //引入组件
    import School from './School.vue'
    import ... from '...'
    export default{
        name:'App',
        //注册组件
        components:{
            School,
            ...
        }
    }
</script>

<style>
    .style1{
        ...
    }
</style>

3、创建main.js 来创建vm实例

格式如下:

import App from './App.vue'

new Vue({
    el:'root',
    components:{
        App
    },
    ...
})

4、创建index.html容器

容器内需要引入vue.js和mian.js,main.js内的代码就需要用到Vue,所以vue.js需要先引入。考虑到JS阻塞,可以将js从body中引入

<body>
    <div id='root'></div>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript" src="./main.js"></script>
</body>

三、脚手架 CLI

模板项目的结构:

  • node_modules

  • public

    • favicon.ico 页签图标
    • index.html 主页面
  • src

    • assets 存放静态资源
      • logo.png
    • component 存放组件
      • HelloWorld.vue
    • App.vue: 汇总所有组件
    • main.js 入口文件
  • .gitignore: git 版本管制忽略的配置

  • babel.config.js babel 的配置文件

  • package.json 应用包配置文件

  • README.md 应用描述文件

  • package-lock.json 包版本控制文件

package.json :

  • serve:运行
  • build:构建(完工后运行)
  • lint:语法检查

1、render函数

脚手架中mian.js代码一般为:

import Vue from 'vue';
new Vue({
  el: '#app',
  render: h => h(App)
});

这个render何方神圣呢?

当使用常规写法时,即el、template、components属性都写上,import Vue from ‘vue’ 其实引入的是残缺版的vue,并不是直接引用vue.js,使用会造成页面加载不出来

2、ref标签属性

可以代替id属性,对于设置了ref属性的标签。vue可以通过this.$ref来获取标签相关的内容:

  • 如果是普通标签设置了ref属性,如h1,a,p之类的,那么this.$ref可以获取到这个标签html结构
  • 如果是子组件标签。那么this.$ref可以获取到子组件vc对象。进而可以读取到子组件的属性和方法。

3、vue的props属性

props属性可以用来进行父子组件的通信;

在父组件中,对其子组件标签设置标签属性,这些标签属性可以被子组件接收,前提是这些标签属性都是存在于子组件的props属性中。

子组件可以像访问自己data里数据一样读取这些从父组件传过来的值,但是不能直接修改(会警告)。(可以拷贝至data里,再对data进行操作)

eg:

<!--父组件-->
<tamplate>
    <MyCom name='zj' age='18'></MyCom>
</tamplate>

<!--子组件-->
new Vue({
...,
...,
props:['name','age']
})

注意:

  • 上例中,age=“18” 这个是典型的标签属性的写法,里面的值都当成String处理,就算是这个18必须是number型数据,也不能写成 age = 18 ,那么可以通过v-bind的形式,将“”里的内容当成js代码来处理。即 :age=“18” ,这时候18就算number类型的数据了

  • 这里的标签属性的取名不要和特殊的标签属性撞车了,像ref,key之类的都是无效的

  • props属性值也可以写成配置对象的形式

    //限制类型
    props:{
    	name:String,
    	age:number
    }
    //限制类型、必要性、默认值等等
    props:{
    	name:{
        	type:String,
        	requierd:true,
        	default:'张三'
    	},
    	age:{
            ...
        }
    }
    

4、mixin混入

不同组件相同的methods方法或者data数据,都可以可以抽离出一个js文件,再分别配置到各个组件的mixins属性里去,

  • 功能:可以把多个组件共用的配置提取成一个混入对象;
  • 使用:
    • 全局:Vue.mixin(xxx)
    • 局部:mixins:[‘xxx’]

5、插件

联想一下项目中的,element.js,其实就是一个插件,他能像外挂一下给Vue添加全局的功能或方法,像this. a l e r t 、 t h i s . alert、this. alertthis.message等elementUI里的弹框和消息框。

  • 功能:用于增强Vue
  • 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

6、组件化编程流程

  • 最好按功能点拆分组件
  • 先实现静态组件:把壳做出来
  • 再实现动态数据
    • 数据类型、名称
    • 数据保存在哪个组件
  • 最后实现交互:绑定事件监听

7、组件的自定义事件

7.1绑定自定义事件

<child @myFun="getS"></child>
....
....
methods:{
	getS(value){
		... = value
	}
}

这里是给child子组件的实例对象自定义了一个myFun事件,并与父组件绑定。当子组件中的myFun事件被触发时,就会触发所绑定的父组件中的getS回调方法,其中value是回调值

在子组件child中,可以通过下面代码来触发写在父组件中的myFun事件

this.$emit('myFun',data)

第一个是事件名,第二个是要传递的值

父组件中也可以这样写来实现事件绑定:

<child ref="ch"></child>
...
...
this.$refs.ch.$on("myFun",this.getS)

7.2解绑自定义事件

解绑函数:找到绑定的子组件实例对象,子组件直接掉this,父组件就通过ref来调用this. r e f s . c h . refs.ch. refs.ch.off

//解绑一个
this.$off("event")
//解绑多个要写成数组形式
this.$off(["event1","event2","event3",...])
//不写形参,全部解绑
this.$off()

当组件被销毁时,(this.$destory),所有的自定义事件都不奏效

8、全局事件总线

上面的例子中,父组件绑定了一个自定义事件a(附加回调函数f),这个自定义事件是在子组件的vue实例中的,当子组件触发这个事件a时(携带数据d),可以唤起父组件中的回调函数f,并将携带的数据d作为回调函数f的形参; e m i t 可以理解为触发, emit可以理解为触发, emit可以理解为触发,on可以理解为绑定事件,执行回调,一个自定义事件只能写在一个vue实例中,就是说 e m i t 和 emit和 emiton由同一个vue实例来执行

全局事件总线可以实现任意组件之间的通信;其原理是:

  • 假设存在组件A和B,A需要向B发送数据d,现引入一个第三方vue实例对象eventBus;
  • 在B组件中,调用EventBus对象,绑定一个自定义事件sentAB,同时设置一个回调函数,自定义事件被触发时执行回调,即event.$on(“sentAB”,this.setData)
  • A组件同样调用EventBus对象,触发自定义事件sentAB,同时携带A组件的数据d,即event.$emit(“sentAB”,this.d)
  • 这时B组件的回调函数就会收到数据d作为形参,执行回调函数,将A的数据d存到了B中,实现了组件A和B的通信

事件总线的必须满足:

  • 所有需要通信的组件必须都能调用得到;
  • 能够调用 o n 、 on、 onemit、$off

一个重要的内置关系:VueComponent的原型对象的原型对象,指向的是Vue的原型对象,而不是直接指向Object对象

也就是说,所有vc对象都可以访问的地方,就是Vue的原型对象;

另外,有趣的是,vc对象和其原型对象(vc的原型对象的类型是Vue)上都不存在 e m i t 和 emit和 emiton方法,这些方法其实是在Vue的原型对象中

因此可以得出:

  • 事件总线对象适合放在Vue的原型对象当中,这样的话所有组件都能访问到这个对象;(在main.js里设置全局对象也行)

  • 由于 e m i t 和 emit和 emiton等方法也存在于Vue的原型对象里,能调用Vue原型对象的就是vm实例和vc实例了;因此事件总线必须是vc实例或者vm实例

在vue脚手架里,可以通过以下方案来安装全局事件总线

8.1 事件总线安装方案一

可以在main.js中新建一个全局的vm实例对象,命名为eventBus:

const eventBus = new Vue();

在需要通信的组件里导入eventBus对象:

import {eventBus} from "@/main";

调用:

eventBus.$on("xxx",xxx)
eventBus.$emit("xxx",xxx)

8.2 事件总线安装方案二

也可以在Vue原型对象里添加vm实例本身来作为事件总线(习惯命名为$bus),但是必须要在vm生成前添加:main.js代码如下:

new Vue({
  el: '#app',
  render: h => h(App),
  beforeCreate() {
      Vue.protoType.$bus = this;
  }
});

现在 b u s 就是一个唯一的对象了,同时它又是 v m 对象,可以调用 bus就是一个唯一的对象了,同时它又是vm对象,可以调用 bus就是一个唯一的对象了,同时它又是vm对象,可以调用emit等方法,所以为什么不直接将vm作为事件总线呢,const bus = vm;,这是因为要将vm本身赋值给事件总线,那么要等vm构建完成才能执行这段代码,但是vm构建过程中都已经要用到事件总线了,自相矛盾了属于是。

调用

this.$bus.$emit("xxx",xxx)
this.$bus.$on("xxx",xxx)

这种方案不需要导入对象;$bus是Vue原型对象,所有vc实例都能顺藤摸瓜找到他(即data没有就找原型对象,原型对象没有就找原型对象的原型对象)

最好在beforeDestory钩子中,解绑当前组件所使用的事件;

9、消息订阅与发布

任意组件直接的通信,这是导入第三方库来实现;可以安装pubsub-js

安装:

npm i pubsub-js

在需要通信的组件里导入:

import pubsub from 'pubsub-js'

然后就可以直接通过pubsub对象名来调用方法了

使用方式:

订阅消息(对比$on):

import pubsub from 'pubsub-js'
...
setData(msgName,data){		//注意,与事件总线不同的是,这个回调函数会收到两个参数,第一个是方法名,第二个才是数据
    this.xxx = data;
}
...
this.pubId  = pubsub.subcribe("msgName",setData)		//事件名,回调函数

发布消息(对比$emit)

import pub from 'pubsub-js'
...
pubsub.publish("msgName",data)

关闭订阅:

//与定时器类似,不能直接通过消息名来关闭,而是通过将订阅存入变量,通过关这个变量来关闭
pubsub.unsubcribe(this.pubId)

10、过渡与动画

10.1 显示/隐藏 按钮的实现

<button @click="isShow = !isShow" >显示/隐藏</button>
<Component v-show="isShow"></Component>
...
...
data(){
	return{
		isShow:ture
	}
}

10.2 简单动画效果

动画关键帧:@keyfarmes 这个要回头看看css或者js

@keyfarmes myD{
    from{
        transform:translateX(-100%);			//整个元素从左侧处出来
    }
    to{
        transform:translateX(0px);
    }
}

如何使用呢?

定义两个动画的类名,显示的动画可以叫come ,隐藏的动画可以叫go

.come{
    animation:myD,1s,linear;	//动画名,时间,匀速
}
.go{
 	animation:myD,1s,reverse;	//加上reverse表示反转   
}

将.come或.go应用于Component,但是这样只能生效一个动画,这时我们可以将需要动画展示的标签用transition标签(过渡)包裹起来,

另外不能在简单的命名为.come或.go,在vue里,默认命名为.v-enter-active和.v-leave-active,如果transition有name属性值,假设为hello,那就要写成hello-enter-active

.v-enter-active{
    animation:myD 1s linear;	//动画名,时间,匀速
}
.v-leave-active{
 	animation:myD 1s reverse;	//加上reverse表示反转   
}
<transition>
	<Component v-show="isShow"></Component>
</transition>

10.3 过渡效果

动画也可能用Vue的过渡来写:(假设过渡名为hello)

/*进入的起点*/
.hello-enter{
  transform: translateX(-1800px);
}
/*进入的终点*/
.hello-enter-to{
  transform: translateX(0);
}
/*离开的起点*/
.hello-leave{
  transform: translateX(0);
}
/*离开的终点*/
.hello-leave-to{
  transform: translateX(-1800px);
}

/*上面的代码明显0可以这样优化*/
/*进入的起点,离开的终点*/
.hello-enter,.hello-leave-to{
  transform: translateX(-1800px);
}
/*进入的终点,离开的起点*/
.hello-enter-to,.hello-leave{
  transform: translateX(0);
}

另外在需要应用动画的标签上,加上transition属性来调整动画;

<transition name="hello" appear>
	<Child1  v-show="isShow" class="my-animation"></Child1>
</transition>
...
.my-animation{
  transition: 2s linear;
}

也可以写在active里:

/*进入和离开过程中*/
.hello-enter-active,.hello-leave-active{
	transition:2s linear;
}

10.4 多个元素的过渡

transition过渡标签只能包裹一个标签元素,要想应用多个过渡,那应该使用transition-group标签,而且每个子标签需要添加key属性;

<transition-group name="hello" appear>
	<Child1  v-show="isShow" key="1"></Child1>
    <Child1  v-show="isShow" key="2"></Child1>
</transition-group>

10.5 集成动画库

老师推荐:npm上的animate.css

网站主页:Animate.css | A cross-browser library of CSS animations.

使用方式:主页上有介绍;

11 配置代理

97-97

四、slot插槽、vuex

4.1 slot插槽

在模板中挖个坑,等着组件的使用者去填

默认插槽

假设有父组件parent.vue和子组件chird.vue

<!--子组件-->
<template>
    <div>
        <h1>hello</h1>
        <slot>甚至插槽标签的内容体也可以写东西,这些是默认值,当父组件没有往插槽里写内容时呈现</slot>
    </div>
</template>
<!--父组件-->
<template>
	<child>
        <h3>world</h3>
    </child>
</template>

就是说,组件的使用者可以在自定义组件标签的内容体里添加内容,前提是该子组件在其模板内留有插槽

注意:

  • child标签内容里的东西是解析完成后再传到slot里的,也就是说可以在父组件里为这些内容添加样式

具名插槽

子组件(child)插槽格式:

<slot name="center"></slot>
<slot name="footer"></slot>

父组件插入格式:

<template>
    <child>
    	<h1 slot="center">hello</h1>
        <span slot="footer">world</span>
    </child>
</template>

注意:

  • 可以通过div将需要使用同一个插槽的内容抱起来,但这样最终会生成一个div元素,这时候可以使用template标签将它们包起来,不会生成结构元素,这时候可以使用slot=“xxx”的写法。也可以使用v-slot:xxx写法(仅限template标签)

    <template>
        <child>
        	<h1 slot="center">hello</h1>
            <template v-slot:footer>
                ...
            </template>
        </child>
    </template>
    

作用域插槽

需求:假设子组件有一个数组,父组件需要分三种方式来展示这个数组,比如ul、ol、或者p、h1等等

即:数据在子组件中,但这些使用这些数据的结构需要由父组件(子组件的使用者)来决定

可以用作用域插槽来解决这类需求,子组件中写好数据和slot插槽,父组件(使用者)在子组件标签内写好结构要使用的数据;至于父组件如何获取这些数据,可以在子组件插槽中这样写:

<slot :自定义数据名称A="子组件中的数据名a"></slot>

相当于子组件将数据a传给了slot插槽,一旦子组件的使用者(父组件)给插槽赋予了结构,那么父组件就能通过A收到子组件的数据a

另外父组件要想获取这个数据,必须需要用template标签包裹起来,标签内还要写上scope属性,属性值是用户自定义的一个对象名X,因为数据是会以对象的形式传过来(意味着可以传多个数据),结构内就可以通过X.A的形式调用a数据了

<template>
	<child>
    	<template scope="X">
			<ul>
            	<li v-for="item in X.A">
    				{{item}}
    			</li>    
    		</ul>
		</template>
    </child>
</template>

注:新的API将scope属性改成了slot-scope

4.2 vuex

1、概述

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

官网:https://github.com/vuejs/vuex

在这里插入图片描述

在这里插入图片描述

使用场景:

  • 多个组件依赖于同一状态
  • 来自不同组件的行为需要变更同一状态

2、Vuex工作原理图

在这里插入图片描述

  • dispatch(派遣)和commit(提交)是人为操作的,而mutate(转换)和render(渲染)是自动执行的
  • Bcakend API :后端接口 ,可以将业务逻辑写在actions对象中

搭建Vuex环境

安装

npm i vuex

使用

import Vuex from 'vuex'
Vue.use(Vuex)

原理图中的三者必须封装在一个store对象里,而且这个store对象,所以的vc组件都能访问

搭建步骤:

  • 在src文件夹里,创建store文件夹,并在store文件夹里创建index.js文件

  • index.js文件内容如下:

    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    import Vue from 'vue'
    Vue.use(Vuex)
    
    //准备actions,用于响应组件中的动作
    const actions = {}
    //准备mutations,用于操作数据
    const mutations = {}
    //准备state,用于存储数据
    const state = {}
    
    //创建并暴露store对象	(配置对象里,key和值重名了,可以采用简写)
    export default new Vuex.Store({
        actions,
        mutations,
        state
    })
    
  • 然后在main.js中,导入store

    import store from './store'
    ...
    new Vue({
        ...
        render:h=>h(App),
        store,			//对象简写形式
        ...
    })
    

3、Vuex的使用

  • 在store文件夹里的index.js里,将需要共享的数据写到state对象里
const state = {
    data1:{
        ......
    },
    data2:{
        ......
    }
}
  • 在store文件夹里的index.js里,将需要调用的方法(业务逻辑)写到actions对象里
    • context对象是一个mini版的store对象,可以获取state数据和调用commit,甚至可以再次调用dispatch
    • 调用dispatch意味着,actions的方法可以链接起来,方法1走完了dispatch方法2,再dispatch方法3…最终commit到mutations中去操作数据
    • actions如果没有业务逻辑操作,那么就只起到一个转发的作用,那么这部分就可以不写,直接在组件里调用mutation方法
const actions = {
    方法名fun1:function(context,value){
        //可以对数据进行一番操作,再将值传给mutations,这里没有处理,直接传value了
        ....
        context.commit('FUN1',value);
    },
    方法名fun2:function(){
        ....
    },
}
  • 在store文件夹里的index.js里,将操作数据的方法写到mutations对象里
    • 一般将actions里对应的方法,在mutations转为大写
const mutations = {
    方法名FUN1:function(state,value){
        //这时可以利用传过来的value,对stare中的数据进行修改
        ....
    },
    
}
组件使用state中数据
<p>
    {{$store.state.data1.xxx}}
</p>
组件中通过dispatch传递一个方法和数据到actions
this.$store.dispatch('方法名fun1',数据)	 //数据将传到actions方法的value形参中
组件中通过commit跳过actions直接和mutations交互
this.$store.commit("方法名FUN1",数据)

4、Vuex开发者工具的使用

Vuex开发者工具就在vue开发者工具当中;菜单栏的第2项!

在这里插入图片描述

在这里插入图片描述

通过原理图可以发现,Devtools是检测mutation的

  • Base State:基础状态,

  • 下载图标:将当前选中的状态转为基础状态,意味着之前的状态将清除

  • 禁止图标:将当前选中的状态操作取消,这样意味着它之后的操作也会取消,因为这些操作都是链接式的跟在它后面,Dom界面会回退到它的上一个状态

  • 时间图标:可以将Dom回退到当前选中状态

  • 右上角的下载图标:将最终状态作为基础状态(Dom也处于该状态),清空所有操作

  • 右上角的禁止图标:保留基础状态,清除全部操作

  • 右上角红点:Vuex开发者工作的开启和关闭

时间图标:

在这里插入图片描述

下载图标:

在这里插入图片描述

禁止图标:

在这里插入图片描述

下半部分的右上角还有导入导出功能,可以导出当前选中状态的state数据到剪贴板,导入可以将剪贴板内容导入

在这里插入图片描述

5、其他配置

getters配置项

用于将state中的数据进行加工;在store,index.js中,可以加入getters配置项,类似于计算属性

const getters = {
    fun1(state){
        return state.xxx......
    }
}
    
export default new Vuex.Store({
    ...
    ...
    ...,
    getters
})
mapState和mapGetters

每次调用vuex的数据的时候,都要写this. s t o r e . s t a t e 和 t h i s . store.state和this. store.statethis.store.getters,相对比较麻烦,vuex提供mapState和mapGetters,简化上面那种写法,直接当成计算属性来写,(本质上就是映射)

假设state数据如下:

const state = {
    school:'xxx',,
    student:'xxx',
    age:'xxx'
}

以mapState为例:

首先需要导入mapState

import {mapState} from 'vuex'

对象写法:

//不能简写
computed:{
    ...mapState({
        school:'school',
        student:'student',
        age:'age'
    })
}

数组写法:

computed:{
    ...mapState(['school','student','age'])
}
mapActions与mapMutations

原理和上面类似,不过这俩需要写到methods里。而且需要注意的是,仅仅绑定方法名还不行,还要留意形参

例如下面这种:

methods:{
	subtract(){
		this.$store.dispatch('subtract',this.n)
    }
}

可以写成:

methods:{
    ...mapActions(['subtract'])
}

但是调用的时候,必须写成

subtract(this.n);

6、vuex模块化

虽说vuex可以实现数据共享,但是只要共享的数据比较多,比较杂乱,各自操作数据的方法交织在一起,就会显得很乱,没有条理,开发效率降低;

这时就可以将vuex进行模块化的管理,将相关的数据和操作(actions、mutations、state、getters)放在一起组成一个模块,

//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
import Vue from 'vue'
Vue.use(Vuex)

//和xx有关的配置
const xxOptions = {
    namespaced:true,	//开启命名空间,mapState就可以通过对象名来绑定里面的数据
    actions:{},
	mutations:{},
	state:{},
    getters:{}
}
//和xxx有关的配置
const xxxOptions = {
    namespaced:true,
    actions:{},
	mutations:{},
	state:{},
    getters:{}
}

//创建并暴露store对象	(配置对象里,key和值重名了,可以采用简写)
export default new Vuex.Store({
    modules:{
        a:xxOptions,
        b:xxxOptions
    }
})

通过上面的操作,a和b都会存放到$store的state里

假设以下数据存放在xxOptions中,即a的state中

state:{
    school:'xxx',,
    student:'xxx',
    age:'xxx'
}

那么mapState就需要通过以下方式来绑定:

computed:{
    ...mapState('a',['school','student','age'])
    ...mapState('b',['xxx','xxx','xxx'....])
}

同理:mapActions也需要指明模块:

methods:{
    ...mapActions('a',{fun1:'xxx1',...})  //对象写法
    ...mapActions('b',['xxx1','xxx2'...]) //数组写法
}

注意

  • 如果不使用mapActions和mapMutations,使用原始的方法,方法名前要加模块对象名和/
this.$store.dispatch('a/fun1',xxx)
  • state的数据是按a,b两个模块划分的,但是getters中的数据是按a/xxx 、 b/xxx的形式划分的,所以不能通过this. s t o r e . g e t t e r . a . x x x 来获取,而是需要通过 t h i s . store.getter.a.xxx来获取,而是需要通过this. store.getter.a.xxx来获取,而是需要通过this.store.getter[‘a/xxx’]来获取

五、路由

5.1 概述

路由:

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

前端路由:

  • value是component,用于展示页面内容;
  • 工作过程:当浏览器的路径改变时,对应的组件就会显示;

后端路由:

  • value是function,用于处理客户端提交的请求
  • 工作过程:服务器收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据

5.2 基本使用

  • 安装
npm i vue-router
  • 在src文件中创建router文件夹,再到router文件夹里创建index.js文件,index.js文件代码如下:
//引入路由
import VueRouter from 'vue-router'
//引入组件
import xxx from 'xxx'
...
//创建并暴露一个路由器
export default new VueRouter({
	routers:[
        {path:'/xxx',component:xxx},
        {path:'/xxx',component:xxx},
        ...
    ]
})
  • 在main.js中引入并使用:
import Vue from 'vue'
//引入VueRouter
import VueRouter from 'vue-router'
//应用插件
Vue.use(VueRouter)
//引入路由器
import router from './router'

new Vue({
    ...
    ...
    router:router,
    ...
})
  • 使用router-link标签,点击它来跳转路由(本质上是a标签)
    • 特有属性to,属性值为跳转路由的路径
<router-link to="/xxx">点我跳转xxx</router-link>
  • 使用router-view标签来承接路由组件的内容
<router-view></router-view>

5.3 路由使用注意点

  • 注册路由的组件可以称为路由组件,放在page文件夹里,其他组件称为一般组件,放在component文件夹里
  • 当路由组件被切换走了,这个组件就会被销毁(这里要注意,路由组件之间的通信用全局事件总线交互的时候很可能会失败)
  • r o u t e 属性和 route属性和 route属性和router属性
    • 每个组件都有自己的$route属性,存储着自己的路由信息
    • 整个应用只有一个$router属性

5.4 嵌套(多级)路由

在一级路由中使用children属性项。

子路由配置如下:(router/index.js)

  • 和router配置项一样,children配置项用数组包裹,里面是子路由对象
  • children路由中,路径不加/
//引入路由
import VueRouter from 'vue-router'
//引入组件
import Home from './page'
import xxx from 'xxx'
...
//创建并暴露一个路由器
export default new VueRouter({
	routers:[
        {
            path:'/home',
            component:Home,
            children:[
            	{
            		path:'message',
            		component:Massage
        		},
				{
            		path:'news',
            		component:News
        		}
            ]
        },
        {
            path:'/about',
            component:xxx
        },
        ...
    ]
})

在网页中,home组件和about的父组件正常写路由,而message和news需要写在home组件当中,写法大体一致,不过需要注意以下几点

  • 跳转路径要加上父组件路径,(后期有方法省略)
<router-link to="/home/message">xxx</router-link>

5.5 路由传参

将router-link的数据或者说父级组件的数据传到router-view中;

1、采用跳转路由携带query参数的方法

  • 跳转路由携带query参数,to的字符串写法

    • 发送数据(仅仅是字符串)

      <router-link to="/home/message/detail?id=001&title=hello"></router-link>
      
    • 发送数据(变量)模板写法

      x <router-link :to="`/home/message/detail?id=${this.xxx}&title=${this.xxx}`"></router-link>
      
  • 跳转路由携带query参数,to的对象写法

    <router-link :to="{
    	path:'/home/message/detail',	
    	query:{
        	id:this.xxx,
    		title:this.xxx
    	}
    }">
    xxxx
    </router-link>
    
  • 接收数据,在router-view对应的组件里,调用this.$route.query

2、采用params参数的方法

定义路由的时候,在路径后面补上:声明接收参数

path:'/xxx/:id/:title'

这样就表示允许在路由跳转的传入两个参数:

<!--字符串写法-->
<router-link to="/xxx/0001/你好">xx</router-link>
<!--模板写法-->
<router-link :to="`/xxx/${this.xx}/${this.xx}">xx</router-link>
<!--对象写法-->
<router-link :to="{
	//path:'/home/message/detail',	
    name:'xxx',			//不能写path。必须写name
	params:{
    	id:this.xxx,
		title:this.xxx
	}
}">

然后通过this.$route.params调用

3、props配置项

路由配置中的props属性,

//props的第一种写法,值为对象,对象中的所有键:值会以props的形式传递给component里注册的组件,这种写法下,每次调用的都是固定的数据
...
component:xxx,
props:{a:1, b:2}
//props的第二种写法,值为布尔值,布尔值为true,则会把该路由组件收到的所有params参数,以props的形式传递给component里注册的组件,也就是说可以直接当成外来参数的形式调用,而不用再写this.$route.params.xxx和计算属性了
...
component:xxx,
props:true
//props的第三种写法,值为函数,上面写法只能接收params参数,而不能收query的参数,这时候可以考虑使用函数写法,将query作为形参回调,调用query.xxx,赋予一个属性a,再到对应的组件的设置props:[a]
...
component:Message,
props(query){
    return {id:query.id,title:query.title}
}

//第三种写法。然后Message组件里,写上props属性
...
data...
props:['id','title']

5.6命名路由

vue可以在路由器配置里给路由加上name的属性,一般是语义化的名字;

...
router:[
	{
		name:'xxxx',
        path:'/xxx',
        component:xxx
	}
]

具名的好处之一是,在to的对象化写法中,可以直接写name属性来跳转,而不用写一大堆的xxx/xxxx/xx/x/xx/xx

5.7 router-link 的replace属性

router-link默认是一种push模式

在这里插入图片描述

在router-link标签里加:replace:ture(简写直接写replace也可)可以切换为replace模式,replace模式每次进入新路由,都会替代掉上一次的路由地址,而不是压栈

5.8 编程式路由导航

未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值