前端Vue框架系列—— 学习笔记总结Day02

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

第一章 Vue组件化编程

1.1 模块与组件、模块化与组件化

在这里插入图片描述


在这里插入图片描述


1.1.1 模块

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

1.1.2 组件

  1. 理解: 用来实现局部(特定)功能效果的代码集合(html/css/js/image……)
  2. 为什么: 一个界面的功能很复杂
  3. 作用: 复用编码, 简化项目编码, 提高运行效率

1.1.3 模块化

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

1.1.4 组件化

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


1.2 非单文件组件

1.2.1 基本使用

代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>基本使用</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>

		<!-- 准备好一个容器-->
		<div id="root">
			<hello></hello>
			<hr>
			<h1>{{msg}}</h1>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<school></school>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<student></student>
		</div>

		<div id="root2">
			<hello></hello>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false

		//第一步:创建school组件
		const school = Vue.extend({
			template:`
				<div class="demo">
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>
					<button @click="showName">点我提示学校名</button>	
				</div>
			`,
			// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
			data(){
				return {
					schoolName:'光明学校',
					address:'广州'
				}
			},
			methods: {
				showName(){
					alert(this.schoolName)
				}
			},
		})

		//第一步:创建student组件
		const student = Vue.extend({
			template:`
				<div>
					<h2>学生姓名:{{studentName}}</h2>
					<h2>学生年龄:{{age}}</h2>
				</div>
			`,
			data(){
				return {
					studentName:'张三',
					age:18
				}
			}
		})
		
		//第一步:创建hello组件
		const hello = Vue.extend({
			template:`
				<div>	
					<h2>你好啊!{{name}}</h2>
				</div>
			`,
			data(){
				return {
					name:'Tom'
				}
			}
		})
		
		//第二步:全局注册组件
		Vue.component('hello',hello)

		//创建vm
		new Vue({
			el:'#root',
			data:{
				msg:'你好啊!'
			},
			//第二步:注册组件(局部注册)
			components:{
				school,
				student
			}
		})

		new Vue({
			el:'#root2',
		})
	</script>
</html>

效果:
在这里插入图片描述


总结:

  • Vue中使用组件的三大步骤:

    1. 定义组件(创建组件)
    2. 注册组件
    3. 使用组件(写组件标签)
  • 如何定义一个组件:?
    使用 Vue.extend(options) 创建,其中 optionsnew Vue(options) 时传入的那个 options 几乎一样,但也有点区别:

    1. el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
    2. data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。

    备注:使用template可以配置组件结构。

  • 如何注册组件?

    1. 局部注册:靠 new Vue的时候传入 components 选项
    2. 全局注册:靠 Vue.component('组件名',组件)
  • 编写组件标签:
    例如: <school></school>


1.2.2 几个注意点

代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>几个注意点</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<h1>{{msg}}</h1>
			<school></school>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//定义组件
		const s = Vue.extend({
			name:'zhangsan',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
				</div>
			`,
			data(){
				return {
					name:'光明学校',
					address:'广州'
				}
			}
		})

		new Vue({
			el:'#root',
			data:{
				msg:'欢迎学习Vue!'
			},
			components:{
				school:s
			}
		})
	</script>
</html>

效果:
在这里插入图片描述


总结:

几个注意点:

  1. 关于组件名

    • 一个单词组成:

      • 第一种写法(首字母小写):school
      • 第二种写法(首字母大写):School
    • 多个单词组成:

      • 第一种写法(kebab-case命名):my-school
      • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
    • 备注:

      • 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
      • 可以使用name配置项指定组件在开发者工具中呈现的名字。
  2. 关于组件标签

    • 第一种写法:<school></school>
    • 第二种写法:<school/>
    • 备注:不用使用脚手架时,<school/> 会导致后续组件不能渲染。
  3. 一个简写方式

    • const school = Vue.extend(options) 可简写为:const school = options

1.2.3 组件的嵌套

代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件的嵌套</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		//定义student组件
		const student = Vue.extend({
			name:'student',
			template:`
				<div>
					<h2>学生姓名:{{name}}</h2>	
					<h2>学生年龄:{{age}}</h2>	
				</div>
			`,
			data(){
				return {
					name:'张三',
					age:18
				}
			}
		})
		
		//定义school组件
		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<student></student>
				</div>
			`,
			data(){
				return {
					name:'光明学校',
					address:'广州'
				}
			},
			//注册组件(局部)
			components:{
				student
			}
		})

		//定义hello组件
		const hello = Vue.extend({
			template:`<h1>{{msg}}</h1>`,
			data(){
				return {
					msg:'欢迎习Vue组件!'
				}
			}
		})
		
		//定义app组件
		const app = Vue.extend({
			template:`
				<div>	
					<hello></hello>
					<school></school>
				</div>
			`,
			components:{
				school,
				hello
			}
		})

		//创建vm
		new Vue({
			template:'<app></app>',
			el:'#root',
			//注册组件(局部)
			components:{app}
		})
	</script>
</html>

效果:

在这里插入图片描述


1.2.4 VueComponent

代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>VueComponent</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<school></school>
			<hello></hello>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//定义school组件
		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<button @click="showName">点我提示学校名</button>
				</div>
			`,
			data(){
				return {
					name:'光明学校',
					address:'广州'
				}
			},
			methods: {
				showName(){
					console.log('showName',this)
				}
			},
		})

		const test = Vue.extend({
			template:`<span>张三</span>`
		})

		//定义hello组件
		const hello = Vue.extend({
			template:`
				<div>
					<h2>{{msg}}</h2>
					<test></test>	
				</div>
			`,
			data(){
				return {
					msg:'你好啊!'
				}
			},
			components:{test}
		})


		// console.log('@',school)
		// console.log('#',hello)

		//创建vm
		const vm = new Vue({
			el:'#root',
			components:{school,hello}
		})
	</script>
</html>

效果:
在这里插入图片描述


总结:

关于 VueComponent

  1. school组件本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是 Vue.extend 生成的。

  2. 我们只需要写 <school/><school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)

  3. 特别注意:每次调用 Vue.extend,返回的都是一个全新的VueComponent

  4. 关于this指向:

    1. 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
    2. new Vue(options) 配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
  5. VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。

    • Vue的实例对象,以后简称vm。

1.2.5 一个重要的内置关系

代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>一个重要的内置关系</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<school></school>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		Vue.prototype.x = 99

		//定义school组件
		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<button @click="showX">点我输出x</button>
				</div>
			`,
			data(){
				return {
					name:'光明学校',
					address:'广州'
				}
			},
			methods: {
				showX(){
					console.log(this.x)
				}
			},
		})

		//创建一个vm
		const vm = new Vue({
			el:'#root',
			data:{
				msg:'你好'
			},
			components:{school}
		})

		
		//定义一个构造函数
		/* function Demo(){
			this.a = 1
			this.b = 2
		}
		//创建一个Demo的实例对象
		const d = new Demo()

		console.log(Demo.prototype) //显示原型属性

		console.log(d.__proto__) //隐式原型属性

		console.log(Demo.prototype === d.__proto__)

		//程序员通过显示原型属性操作原型对象,追加一个x属性,值为99
		Demo.prototype.x = 99

		console.log('@',d) */

	</script>
</html>

效果:
在这里插入图片描述


总结:

  1. 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
  2. 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

1.3 单文件组件

  • School.vue

    <template>
        <div id='Demo'>
            <h2>学校名称:{{name}}</h2>
            <h2>学校地址:{{address}}</h2>
            <button @click="showName">点我提示学校名</button>
        </div>
    </template>
    
    <script>
        export default {
            name:'School',
            data() {
                return {
                    name:'光明学校',
                    address:'广州'
                }
            },
            methods: {
                showName(){
                    alert(this.name)
                }
            },
        }
    </script>
    
    <style>
        #Demo{
            background: orange;
        }
    </style>
    
    
  • Student.vue:

    <template>
        <div>
            <h2>学生姓名:{{name}}</h2>
            <h2>学生年龄:{{age}}</h2>
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',
            data() {
                return {
                    name:'张三',
                    age:20
                }
            },
        }
    </script>
    
    
  • App.vue

    <template>
        <div>
            <School></School>
            <Student></Student>
        </div>
    </template>
    
    <script>
        import School from './School.vue'
        import Student from './Student.vue'
    
        export default {
            name:'App',
            components:{
                School,
                Student
            }
        }
    </script>
    
    
  • main.js

    import App from './App.vue'
    
    new Vue({
        template:`<App></App>`,
        el:'#root',
        components:{App}
    })
    
  • index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>单文件组件练习</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="../../js/vue.js"></script>
        <script src="./main.js"></script>
    </body>
    </html>
    

第二章 使用Vue脚手架

2.1 初始化脚手架

2.1.1 说明

  1. Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。
  2. 最新的版本是 4.x。
  3. 文档: https://cli.vuejs.org/zh/

2.1.2 具体步骤

  1. 如果下载缓慢请配置 npm 淘宝镜像:npm config set registry http://registry.npm.taobao.org
  2. 全局安装@vue/cli:npm install -g @vue/cli
  3. 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
  4. 选择使用vue的版本
  5. 启动项目:npm run serve
  6. 暂停项目:Ctrl+C

备注:
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:vue inspect > output.js


2.1.3 分析脚手架结构

脚手架文件结构:

.文件目录
├── 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: 包版本控制文件

2.1.4 render函数

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    el:'#app',
    // 简写形式
	render: h => h(App),
    // 完整形式
	// render(createElement){
	//     return createElement(App)
	// }
})

总结:

关于不同版本的函数:

  1. vue.jsvue.runtime.xxx.js 的区别:

    1. vue.js 是完整版的 Vue,包含:核心功能+模板解析器
    2. vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器
  2. 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容


2.1.5 修改默认配置

  • vue.config.js 是一个可选的配置文件,如果项目的(和 package.json 同级的)根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载
  • 使用 vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
module.exports = {
    pages: {
        index: {
            // 入口
            entry: 'src/index/main.js'
        }
    },
  // 关闭语法检查
  lineOnSave:false
}

2.2 ref属性

代码:

  • School.vue

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州'
    			}
    		},
    	}
    </script>
    
    <style>
    	.school{
    		background-color: gray;
    	}
    </style>
    
  • App.vue

    <template>
    	<div>
    		<h1 v-text="msg" ref="title" id="msg"></h1>
    		<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
    		<School ref="sch"/>
    	</div>
    </template>
    
    <script>
    	//引入School组件
    	import School from './components/School'
    
    	export default {
    		name:'App',
    
    		components:{
    			School
    		},
    
    		data() {
    			return {
    				msg:'欢迎学习Vue!'
    			}
    		},
    
    		methods: {
    			showDOM(){
    				console.log(document.getElementById("msg")) //传统中用于获取指定id的DOM元素
    				console.log(this.$refs.title) //真实DOM元素
    				console.log(this.$refs.btn) //真实DOM元素
    				console.log(this.$refs.sch) //School组件的实例对象(vc)
    			}
    		},
    	}
    </script>
    

效果:
在这里插入图片描述


总结:

ref属性:

  1. 被用来给元素或子组件注册引用信息(id的替代者:document.getElementById()
  2. 应用在 html标签 上获取的是 真实DOM元素 ,应用在 组件 标签上是 组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx

2.3 props配置

代码:

  • Student.vue

    <template>
    	<div>
    		<h1>{{msg}}</h1>
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<h2>学生年龄:{{myAge+1}}</h2>
    		<button @click="updateAge">尝试修改收到的年龄</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Student',
    		data() {
    			console.log(this)
    			return {
    				msg:'我在学Vue!',
    				myAge:this.age
    			}
    		},
    		methods: {
    			updateAge(){
    				this.myAge++
    			}
    		},
    		//简单声明接收
    		// props:['name','age','sex'] 
    
    		//接收的同时对数据进行类型限制
    		/* props:{
    			name:String,
    			age:Number,
    			sex:String
    		} */
    
    		//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
    		props:{
    			name:{
    				type:String, //name的类型是字符串
    				required:true, //name是必要的
    			},
    			age:{
    				type:Number,
    				default:99 //默认值
    			},
    			sex:{
    				type:String,
    				required:true
    			}
    		}
    	}
    </script>
    
  • App.vue

    <template>
    	<div>
    		<Student name="李四" sex="女" :age="18"/>
    	</div>
    </template>
    
    <script>
    	import Student from './components/Student'
    
    	export default {
    		name:'App',
    		components:{Student}
    	}
    </script>
    

效果:
在这里插入图片描述


总结:
props配置项:

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

      props:{
      	name:{
      	type:String, //类型
      	required:true, //必要性
      	default:'老王' //默认值
      	}
      }
      

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。


2.4 mixin混入(合)

1. 局部混入
代码:

  • School.vue

    <template>
    	<div>
    		<h2 @click="showName">学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	//引入一个hunhe
    	import {hunhe,hunhe2} from '../mixin'
    
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州',
    				x:666
    			}
    		},
    		mixins:[hunhe,hunhe2],
    	}
    </script>
    
  • Student.vue

    <template>
    	<div>
    		<h2 @click="showName">学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    	</div>
    </template>
    
    <script>
    	import {hunhe,hunhe2} from '../mixin'
    
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男'
    			}
    		},
    		mixins:[hunhe,hunhe2]
    	}
    </script>
    
  • mixin.js

    export const hunhe = {
    	methods: {
    		showName(){
    			alert(this.name)
    		}
    	},
    	mounted() {
    		console.log('你好啊!')
    	},
    }
    export const hunhe2 = {
    	data() {
    		return {
    			x:100,
    			y:200
    		}
    	},
    }
    
    

效果:
在这里插入图片描述


2. 全局混入

代码:

  • School.vue

    <template>
    	<div>
    		<h2 @click="showName">学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	//引入一个hunhe
    	// import {hunhe,hunhe2} from '../mixin'
    
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州',
    				x:666
    			}
    		},
    		// mixins:[hunhe,hunhe2],
    	}
    </script>
    
  • Student.vue

    <template>
    	<div>
    		<h2 @click="showName">学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    	</div>
    </template>
    
    <script>
    	// import {hunhe,hunhe2} from '../mixin'
    
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男'
    			}
    		},
    		// mixins:[hunhe,hunhe2]
    	}
    </script>
    
  • main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    import {hunhe,hunhe2} from './mixin'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    
    Vue.mixin(hunhe)
    Vue.mixin(hunhe2)
    
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App)
    })
    

效果:
在这里插入图片描述


总结:

  1. 功能:可以把多个组件共用的配置提取成一个混入对象,实现组件功能的复用。

  2. 使用方式:

    第一步定义混合:

    export const xxx = {
        data() {
        
        },
        
        methods: {
        
        }
        ....
    }
    

    第二步使用混入:

    ​ 全局混入:Vue.mixin(xxx)
    ​ 局部混入:mixins:['xxx']

  3. mixin的优点:

    • 复用组件间的共同行为和属性
    • 避免重复代码
    • 易维护
  4. 注意事项:

    • mixin对象中的方法和数据会与组件自身产生冲突,需要避免名称重复
    • mixin会影响整个组件树,应谨慎使用全局mixin
    • 过多mixin会增加组件的复杂度

2.5 plugin插件

  • src/components/School.vue

    <template>
    	<div>
    		<h2>学校名称:{{name | mySlice}}</h2>
    		<h2>学校地址:{{address}}</h2>
    		<button @click="test">点我测试一个hello方法</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州',
    			}
    		},
    		methods: {
    			test(){
    				this.hello()
    			}
    		},
    	}
    </script>
    
  • src/components/Student.vue

    <template>
    	<div>
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<input type="text" v-fbind:value="name">
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男'
    			}
    			
    		},
    	}
    </script>
    
  • src/plugin.js

    export default {
    	install(Vue,x,y,z){
    		console.log(x,y,z)
    		//全局过滤器
    		Vue.filter('mySlice',function(value){
    			return value.slice(0,4)
    		})
    
    		//定义全局指令
    		Vue.directive('fbind',{
    			//指令与元素成功绑定时(一上来)
    			bind(element,binding){
    				element.value = binding.value
    			},
    			//指令所在元素被插入页面时
    			inserted(element,binding){
    				element.focus()
    			},
    			//指令所在的模板被重新解析时
    			update(element,binding){
    				element.value = binding.value
    			}
    		})
    
    		//定义混入
    		Vue.mixin({
    			data() {
    				return {
    					x:100,
    					y:200
    				}
    			},
    		})
    
    		//给Vue原型上添加一个方法(vm和vc就都能用了)
    		Vue.prototype.hello = ()=>{alert('你好啊')}
    	}
    }
    
  • src/main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //引入插件
    import plugins from './plugins'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    
    //应用(使用)插件
    Vue.use(plugins,1,2,3)
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App)
    })
    

效果:
在这里插入图片描述


总结:

  1. 功能:用于增强Vue

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

  3. 定义插件:

    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    
  4. 使用插件:Vue.use()


2.6 scoped样式

  • src/components/Student.vue

    <template>
    	<div class="demo">
    		<h2 class="title">学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州',
    			}
    		}
    	}
    </script>
    
    <style scoped>
    	.demo{
    		background-color: skyblue;
    	}
    </style>
    
  • src/components/School.vue

    <template>
    	<div class="demo">
    		<h2 class="title">学生姓名:{{name}}</h2>
    		<h2 class="sex">学生性别:{{sex}}</h2>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男'
    			}
    		}
    	}
    </script>
    
    <style lang="less" scoped>
    	.demo{
    		background-color: pink;
    		.sex{
    			font-size: 40px;
    		}
    	}
    </style>
    
  • App.vue

    <template>
    	<div>
    		<h1 class="title">你好啊</h1>
    		<School/>
    		<Student/>
    	</div>
    </template>
    
    <script>
    	import Student from './components/Student'
    	import School from './components/School'
    
    	export default {
    		name:'App',
    		components:{School,Student}
    	}
    </script>
    
    <style scoped>
    	.title{
    		color: red;
    	}
    </style>
    
    

效果:
在这里插入图片描述


总结:

在传统的CSS中,样式规则是全局的,会对整个页面中所有匹配的元素生效。这可能导致样式冲突和命名空间污染的问题。

为了解决这些问题,HTML5中引入了 scoped 样式。当在 <style>标签中添加 scoped 属性时,样式规则只会应用于当前组件或元素及其子元素,而不会泄漏到其他组件或元素中。

  1. 定义:scoped 样式是一种用于指定样式仅应用于当前组件或元素的CSS样式作用域。
  2. 作用:让样式在局部生效,防止冲突。
  3. 写法:<style scoped>

注意: scoped 样式一般不会在 App.vue 中使用。


2.7 TodoList案例

  • src/components/MyHeader.vue

    <template>
    	<div class="todo-header">
    		<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
    	</div>
    </template>
    
    <script>
    	import {nanoid} from 'nanoid'
    	export default {
    		name:'MyHeader',
    		//接收从App传递过来的addTodo
    		props:['addTodo'],
    		data() {
    			return {
    				//收集用户输入的title
    				title:''
    			}
    		},
    		methods: {
    			add(){
    				//校验数据
    				if(!this.title.trim()) return alert('输入不能为空')
    				//将用户的输入包装成一个todo对象
    				const todoObj = {id:nanoid(),title:this.title,done:false}
    				//通知App组件去添加一个todo对象
    				this.addTodo(todoObj)
    				//清空输入
    				this.title = ''
    			}
    		},
    	}
    </script>
    
    <style scoped>
    	/*header*/
    	.todo-header input {
    		width: 560px;
    		height: 28px;
    		font-size: 14px;
    		border: 1px solid #ccc;
    		border-radius: 4px;
    		padding: 4px 7px;
    	}
    
    	.todo-header input:focus {
    		outline: none;
    		border-color: rgba(82, 168, 236, 0.8);
    		box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    	}
    </style>
    
  • src/components/MyList.vue

    <template>
    	<ul class="todo-main">
    		<MyItem 
    			v-for="todoObj in todos"
    			:key="todoObj.id" 
    			:todo="todoObj" 
    			:checkTodo="checkTodo"
    			:deleteTodo="deleteTodo"
    		/>
    	</ul>
    </template>
    
    <script>
    	import MyItem from './MyItem'
    
    	export default {
    		name:'MyList',
    		components:{MyItem},
    		//声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
    		props:['todos','checkTodo','deleteTodo']
    	}
    </script>
    
    <style scoped>
    	/*main*/
    	.todo-main {
    		margin-left: 0px;
    		border: 1px solid #ddd;
    		border-radius: 2px;
    		padding: 0px;
    	}
    
    	.todo-empty {
    		height: 40px;
    		line-height: 40px;
    		border: 1px solid #ddd;
    		border-radius: 2px;
    		padding-left: 5px;
    		margin-top: 10px;
    	}
    </style>
    
  • src/components/MyItem.vue

    <template>
    	<li>
    		<label>
    			<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
    			<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
    			<!-- <input type="checkbox" v-model="todo.done"/> -->
    			<span>{{todo.title}}</span>
    		</label>
    		<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    	</li>
    </template>
    
    <script>
    	export default {
    		name:'MyItem',
    		//声明接收todo、checkTodo、deleteTodo
    		props:['todo','checkTodo','deleteTodo'],
    		methods: {
    			//勾选or取消勾选
    			handleCheck(id){
    				//通知App组件将对应的todo对象的done值取反
    				this.checkTodo(id)
    			},
    			//删除
    			handleDelete(id){
    				if(confirm('确定删除吗?')){
    					//通知App组件将对应的todo对象删除
    					this.deleteTodo(id)
    				}
    			}
    		},
    	}
    </script>
    
    <style scoped>
    	/*item*/
    	li {
    		list-style: none;
    		height: 36px;
    		line-height: 36px;
    		padding: 0 5px;
    		border-bottom: 1px solid #ddd;
    	}
    
    	li label {
    		float: left;
    		cursor: pointer;
    	}
    
    	li label li input {
    		vertical-align: middle;
    		margin-right: 6px;
    		position: relative;
    		top: -1px;
    	}
    
    	li button {
    		float: right;
    		display: none;
    		margin-top: 3px;
    	}
    
    	li:before {
    		content: initial;
    	}
    
    	li:last-child {
    		border-bottom: none;
    	}
    
    	li:hover{
    		background-color: #ddd;
    	}
    	
    	li:hover button{
    		display: block;
    	}
    </style>
    
  • src/components/MyFooter.vue

    <template>
    	<div class="todo-footer" v-show="total">
    		<label>
    			<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
    			<input type="checkbox" v-model="isAll"/>
    		</label>
    		<span>
    			<span>已完成{{doneTotal}}</span> / 全部{{total}}
    		</span>
    		<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'MyFooter',
    		props:['todos','checkAllTodo','clearAllTodo'],
    		computed: {
    			//总数
    			total(){
    				return this.todos.length
    			},
    			//已完成数
    			doneTotal(){
    				//此处使用reduce方法做条件统计
    				/* const x = this.todos.reduce((pre,current)=>{
    					console.log('@',pre,current)
    					return pre + (current.done ? 1 : 0)
    				},0) */
    				//简写
    				return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
    			},
    			//控制全选框
    			isAll:{
    				//全选框是否勾选
    				get(){
    					return this.doneTotal === this.total && this.total > 0
    				},
    				//isAll被修改时set被调用
    				set(value){
    					this.checkAllTodo(value)
    				}
    			}
    		},
    		methods: {
    			/* checkAll(e){
    				this.checkAllTodo(e.target.checked)
    			} */
    			//清空所有已完成
    			clearAll(){
    				this.clearAllTodo()
    			}
    		},
    	}
    </script>
    
    <style scoped>
    	/*footer*/
    	.todo-footer {
    		height: 40px;
    		line-height: 40px;
    		padding-left: 6px;
    		margin-top: 5px;
    	}
    
    	.todo-footer label {
    		display: inline-block;
    		margin-right: 20px;
    		cursor: pointer;
    	}
    
    	.todo-footer label input {
    		position: relative;
    		top: -1px;
    		vertical-align: middle;
    		margin-right: 5px;
    	}
    
    	.todo-footer button {
    		float: right;
    		margin-top: 5px;
    	}
    </style>
    
  • src/App.vue

    <template>
    	<div id="root">
    		<div class="todo-container">
    			<div class="todo-wrap">
    				<MyHeader :addTodo="addTodo"/>
    				<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
    				<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script>
    	import MyHeader from './components/MyHeader'
    	import MyList from './components/MyList'
    	import MyFooter from './components/MyFooter.vue'
    
    	export default {
    		name:'App',
    
    		components:{
    			MyHeader,
    			MyList,
    			MyFooter
    		},
    
    		data() {
    			return {
    				//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
    				todos:[
    					{id:'001',title:'学习',done:true},
    					{id:'002',title:'吃饭',done:false},
    					{id:'003',title:'看剧',done:true}
    				]
    			}
    		},
    		methods: {
    			//添加一个todo
    			addTodo(todoObj){
    				this.todos.unshift(todoObj)
    			},
    			//勾选or取消勾选一个todo
    			checkTodo(id){
    				this.todos.forEach((todo)=>{
    					if(todo.id === id) todo.done = !todo.done
    				})
    			},
    			//删除一个todo
    			deleteTodo(id){
    				this.todos = this.todos.filter( todo => todo.id !== id )
    			},
    			//全选or取消全选
    			checkAllTodo(done){
    				this.todos.forEach((todo)=>{
    					todo.done = done
    				})
    			},
    			//清除所有已经完成的todo
    			clearAllTodo(){
    				this.todos = this.todos.filter((todo)=>{
    					return !todo.done
    				})
    			}
    		}
    	}
    </script>
    
    <style>
    	/*base*/
    	body {
    		background: #fff;
    	}
    	.btn {
    		display: inline-block;
    		padding: 4px 12px;
    		margin-bottom: 0;
    		font-size: 14px;
    		line-height: 20px;
    		text-align: center;
    		vertical-align: middle;
    		cursor: pointer;
    		box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    		border-radius: 4px;
    	}
    	.btn-danger {
    		color: #fff;
    		background-color: #da4f49;
    		border: 1px solid #bd362f;
    	}
    	.btn-danger:hover {
    		color: #fff;
    		background-color: #bd362f;
    	}
    	.btn:focus {
    		outline: none;
    	}
    	.todo-container {
    		width: 600px;
    		margin: 0 auto;
    	}
    	.todo-container .todo-wrap {
    		padding: 10px;
    		border: 1px solid #ddd;
    		border-radius: 5px;
    	}
    </style>
    
    

效果:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


总结:

  1. 组件化编码流程:

    ​ (1)拆分静态组件:组件要按照 功能 点拆分,命名不要与html元素冲突。

    ​ (2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

    • (1)一个组件在用:放在组件自身即可。

    • (2)一些组件在用:放在他们共同的父组件上(状态提升)。

    ​ (3)实现交互:从绑定事件开始。

  2. props 适用于:

    ​ (1)父组件 ==> 子组件 通信

    ​ (2)子组件 ==> 父组件 通信(要求父先给子一个函数)

  3. 使用 v-model 时要切记:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的!

  4. props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但不推荐这样做。


2.8 浏览器本地存储

代码:
localStorage.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>localStorage</title>
	</head>
	<body>
		<h2>localStorage</h2>
		<button οnclick="saveData()">点我保存一个数据</button>
		<button οnclick="readData()">点我读取一个数据</button>
		<button οnclick="deleteData()">点我删除一个数据</button>
		<button οnclick="deleteAllData()">点我清空一个数据</button>

		<script type="text/javascript" >
			let p = {name:'张三',age:18}

			function saveData(){
				localStorage.setItem('msg','hello!!!')
				localStorage.setItem('msg2',666)
				localStorage.setItem('person',JSON.stringify(p))
			}
			function readData(){
				console.log(localStorage.getItem('msg'))
				console.log(localStorage.getItem('msg2'))

				const result = localStorage.getItem('person')
				console.log(JSON.parse(result))

				// console.log(localStorage.getItem('msg3'))
			}
			function deleteData(){
				localStorage.removeItem('msg2')
			}
			function deleteAllData(){
				localStorage.clear()
			}
		</script>
	</body>
</html>

效果:
在这里插入图片描述
在这里插入图片描述


sessionStorage.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>sessionStorage</title>
	</head>
	<body>
		<h2>sessionStorage</h2>
		<button οnclick="saveData()">点我保存一个数据</button>
		<button οnclick="readData()">点我读取一个数据</button>
		<button οnclick="deleteData()">点我删除一个数据</button>
		<button οnclick="deleteAllData()">点我清空一个数据</button>

		<script type="text/javascript" >
			let p = {name:'张三',age:18}

			function saveData(){
				sessionStorage.setItem('msg','hello!!!')
				sessionStorage.setItem('msg2',666)
				sessionStorage.setItem('person',JSON.stringify(p))
			}
			function readData(){
				console.log(sessionStorage.getItem('msg'))
				console.log(sessionStorage.getItem('msg2'))

				const result = sessionStorage.getItem('person')
				console.log(JSON.parse(result))

				// console.log(sessionStorage.getItem('msg3'))
			}
			function deleteData(){
				sessionStorage.removeItem('msg2')
			}
			function deleteAllData(){
				sessionStorage.clear()
			}
		</script>
	</body>
</html>

效果:
在这里插入图片描述
在这里插入图片描述


总结:

  1. 存储内容大小一般支持5MB左右(不同浏览器可能不一样)

  2. 浏览器端通过 Window.sessionStorageWindow.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value')
      该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person')

      ​ 该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key')

      ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      ​ 该方法会清空存储中的所有数据。

  4. 备注:

    1. SessionStorage 存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage 存储的数据是持久性的,可以长时间保存,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么 getItem()的返回值是null。
    4. JSON.parse(null)的结果依然是null。

2.9 TodoList_本地存储

src/App.vue

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader :addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import MyList from './components/MyList.vue'
    import MyFooter from './components/MyFooter.vue'

    export default {
        name:'App',
        components: { MyHeader,MyList,MyFooter },
        data() {
            return {
                //若localStorage中存有'todos'则从localStorage中取出,否则初始为空数组
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                //由于todos是对象数组,所以必须开启深度监视才能发现数组中对象的变化
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        }
    }
</script>

<style>
    body {
    	background: #fff;
    }

    .btn {
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
        border-radius: 4px;
    }

    .btn-danger {
        color: #fff;
        background-color: #da4f49;
        border: 1px solid #bd362f;
    }

    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }

    .btn:focus {
    	outline: none;
    }

    .todo-container {
        width: 600px;
        margin: 0 auto;
    }
    .todo-container .todo-wrap {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
</style>

2.10 组件自定义事件

2.10.1 绑定

  • src/App.vue

    <template>
        <div class="app">
    		
            <!-- 通过父组件给子组件传递函数类型的props实现子给父传递数据 -->
            <School :getSchoolName="getSchoolName"/>
    
            <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第一种写法,使用@或v-on) -->
            <!-- <Student @jojo="getStudentName"/> -->
    
            <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) -->
    		<Student ref="student"/>
        </div>
    </template>
    
    <script>
        import Student from './components/Student.vue'
        import School from './components/School.vue'
    
        export default {
            name:'App',
            components: { Student,School },
            methods:{
                getSchoolName(name){
                    console.log("已收到学校的名称:"+name)
                },
                getStudentName(name){
                    console.log("已收到学生的姓名:"+name)      
                }
            },
            mounted(){
                this.$refs.student.$on('jojo',this.getStudentName)
            }
        }
    </script>
    
    
    <style scoped>
    	.app{
    		background-color: gray;
    		padding: 5px;
    	}
    </style>
    
  • src/components/School.vue

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    		<button @click="sendSchoolName">点我传递学校名给App</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'School',
    		props:['getSchoolName'],
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州',
    			}
    		},
    		methods: {
    			sendSchoolName(){
    				this.getSchoolName(this.name)
    			}
    		},
    	}
    </script>
    
    <style scoped>
    	.school{
    		background-color: skyblue;
    		padding: 5px;
    	}
    </style>
    
  • src/components/Student.vue

    <template>
        <div class="student">
            <h2>学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2>
            <button @click="sendStudentName">点我传递学生姓名给App</button> 
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',
            data() {
                return {
                    name:'张三',
    				sex:'男'
                }
            },
            methods:{
                sendStudentName(){
                    this.$emit('jojo',this.name)
                }
            }
        }
    </script>
    
    <style scoped>
        .student{
            background-color: chartreuse;
            padding: 5px;
    		margin-top: 30px;
        }
    </style>
    
    

效果:
在这里插入图片描述


2.10.2 解绑

  • src/App.vue

    <template>
        <div class="app">
            <Student @jojo="getStudentName"/>
        </div>
    </template>
    
    <script>
    import School from './components/School.vue'
        import Student from './components/Student.vue'
    
        export default {
            name:'App',
            components: { Student, School },
            methods:{
                getStudentName(name){
                    console.log("已收到学生的姓名:"+name)      
                }
            }
        }
    </script>
    
    <style scoped>
    	.app{
    		background-color: gray;
    		padding: 5px;
    	}
    </style>
    
    
  • src/components/Student.vue

    <template>
        <div class="student">
            <h2>学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2>
            <button @click="sendStudentName">点我传递学生姓名给App</button> 
            <button @click="unbind">解绑自定义事件</button> 
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',
            data() {
                return {
                    name:'JOJO',
    				sex:'男'
                }
            },
            methods:{
                sendStudentName(){
                    this.$emit('jojo',this.name)
                },
                unbind(){
                    // 解绑一个自定义事件
                    // this.$off('jojo')
                    // 解绑多个自定义事件
                    // this.$off(['jojo'])
                    // 解绑所有自定义事件
                    this.$off()
                }
            }
        }
    </script>
    
    <style scoped>
        .student{
            background-color: chartreuse;
            padding: 5px;
    		margin-top: 30px;
        }
    </style>
    
    

效果:
在这里插入图片描述


总结:

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @custom-event="test"/><Demo v-on:custom-event="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('custom-event',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('custom-event',数据)

  5. 解绑自定义事件this.$off('custom-event')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('custom-event',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!


2.11 TodoList_自定义事件

  • src/App.vue

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
        export default {
            name:'App',
            components: { MyHeader,MyList,MyFooter },
            data() {
                return {
                    todos:JSON.parse(localStorage.getItem('todos')) || []
                }
            },
            methods:{
                //添加一个todo
                addTodo(todoObj){
                    this.todos.unshift(todoObj)
                },
                //勾选or取消勾选一个todo
                checkTodo(id){
                    this.todos.forEach((todo)=>{
                        if(todo.id === id) todo.done = !todo.done
                    })
                },
                //删除一个todo
                deleteTodo(id){
                    this.todos = this.todos.filter(todo => todo.id !== id)
                },
                //全选or取消勾选
                checkAllTodo(done){
                    this.todos.forEach(todo => todo.done = done)
                },
                //删除已完成的todo
                clearAllTodo(){
                    this.todos = this.todos.filter(todo => !todo.done)
                }
            },
            watch:{
                todos:{
                    deep:true,
                    handler(value){
                        localStorage.setItem('todos',JSON.stringify(value))
                    }
                }
            }
        }
    </script>
    
    <style>
        body {
        	background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
       		outline: none;
        }
    
        .todo-container {
        	width: 600px;
        	margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    
  • src/components/MyHeader.vue

    <template>
        <div class="todo-header">
            <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/>
        </div>
    </template>
    
    <script>
        import {nanoid} from 'nanoid'
        export default {
            name:'MyHeader',
            data() {
                return {
                    title:''
                }
            },
            methods:{
                add(){
                    if(!this.title.trim()) return
                    const todoObj = {id:nanoid(),title:this.title,done:false}
                    this.$emit('addTodo',todoObj)
                    this.title = ''
                }
            }
        }
    </script>
    
    <style scoped>
        /*header*/
        .todo-header input {
            width: 560px;
            height: 28px;
            font-size: 14px;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 4px 7px;
        }
    
        .todo-header input:focus {
            outline: none;
            border-color: rgba(82, 168, 236, 0.8);
            box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
        }
    </style>
    
  • src/components/MyFooter

    <template>
        <div class="todo-footer" v-show="total">
            <label>
                <input type="checkbox" v-model="isAll"/>
            </label>
            <span>
                <span>已完成{{doneTotal}}</span> / 全部{{total}}
            </span>
            <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
        </div>
    </template>
    
    <script>
        export default {
            name:'MyFooter',
            props:['todos'],
            computed:{
                doneTotal(){
                    return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
                },
                total(){
                    return this.todos.length
                },
                isAll:{
                    get(){
                        return this.total === this.doneTotal && this.total > 0
                    },
                    set(value){
                        this.$emit('checkAllTodo',value)
                    }
                }
            },
            methods:{
                clearAll(){
                    this.$emit('clearAllTodo')
                }
            }
        }
    </script>
    
    <style scoped>
        .todo-footer {
            height: 40px;
            line-height: 40px;
            padding-left: 6px;
            margin-top: 5px;
            }
    
        .todo-footer label {
            display: inline-block;
            margin-right: 20px;
            cursor: pointer;
        }
    
        .todo-footer label input {
            position: relative;
            top: -1px;
            vertical-align: middle;
            margin-right: 5px;
        }
    
        .todo-footer button {
            float: right;
            margin-top: 5px;
        }
    </style>
    

2.12 全局事件总线(GlobalEventBus)

  • src/main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
    })
    
  • src/App.vue

    <template>
    	<div class="app">
    		<h1>{{msg}}</h1>
    		<School/>
    		<Student/>
    	</div>
    </template>
    
    <script>
    	import Student from './components/Student'
    	import School from './components/School'
    
    	export default {
    		name:'App',
    		components:{School,Student},
    		data() {
    			return {
    				msg:'你好啊!',
    			}
    		}
    	}
    </script>
    
    <style scoped>
    	.app{
    		background-color: gray;
    		padding: 5px;
    	}
    </style>
    
  • src/components/School.vue

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州',
    			}
    		},
    		mounted() {
    			// console.log('School',this)
    			this.$bus.$on('hello',(data)=>{
    				console.log('我是School组件,收到了数据:',data)
    			})
    		},
    
    		beforeDestroy() {
    			this.$bus.$off('hello') //解绑当前组件用到的事件
    		},
    	}
    </script>
    
    <style scoped>
    	.school{
    		background-color: skyblue;
    		padding: 5px;
    	}
    </style>
    
  • src/components/Student.vue

    <template>
    	<div class="student">
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<button @click="sendStudentName">把学生名传递给School组件</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男',
    			}
    		},
    		mounted() {
    			// console.log('Student',this.x)
    		},
    		methods: {
    			sendStudentName(){
    				this.$bus.$emit('hello',this.name)
    			}
    		},
    	}
    </script>
    
    <style lang="less" scoped>
    	.student{
    		background-color: pink;
    		padding: 5px;
    		margin-top: 30px;
    	}
    </style>
    

效果:
在这里插入图片描述


总结:

  1. 一种组件间通信的方式,适用于 任意组件间通信

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在 beforeDestroy 钩子中,用$off去解绑当前组件所用到的事件。


2.13 TodoList_事件总线

  • src/mian.js

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
        el:"#app",
        render: h => h(App),
        beforeCreate() {
            Vue.prototype.$bus = this
        }
    })
    
  • src/components/App.vue

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
        export default {
            name:'App',
            components: { MyHeader,MyList,MyFooter },
            data() {
                return {
                    todos:JSON.parse(localStorage.getItem('todos')) || []
                }
            },
            methods:{
                //添加一个todo
                addTodo(todoObj){
                    this.todos.unshift(todoObj)
                },
                //勾选or取消勾选一个todo
                checkTodo(id){
                    this.todos.forEach((todo)=>{
                        if(todo.id === id) todo.done = !todo.done
                    })
                },
                //删除一个todo
                deleteTodo(id){
                    this.todos = this.todos.filter(todo => todo.id !== id)
                },
                //全选or取消勾选
                checkAllTodo(done){
                    this.todos.forEach(todo => todo.done = done)
                },
                //删除已完成的todo
                clearAllTodo(){
                    this.todos = this.todos.filter(todo => !todo.done)
                }
            },
            watch:{
                todos:{
                    deep:true,
                    handler(value){
                        localStorage.setItem('todos',JSON.stringify(value))
                    }
                }
            },
            mounted(){
                this.$bus.$on('checkTodo',this.checkTodo)
                this.$bus.$on('deleteTodo',this.deleteTodo)
            },
            beforeDestroy(){
                this.$bus.$off(['checkTodo','deleteTodo'])
            }
        }
    </script>
    
    <style>
        body {
            background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
            outline: none;
        }
    
        .todo-container {
            width: 600px;
            margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    
  • src/components/MyItem.vue

    <template>
        <li>
            <label>
                <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span>{{todo.title}}</span>
            </label>
            <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
        </li>
    </template>
    
    <script>
        export default {
            name:'MyItem',
            props:['todo'],
            methods:{
                handleCheck(id){
                    this.$bus.$emit('checkTodo',id)
                },
                handleDelete(id,title){
                    if(confirm("确定删除任务:"+title+"吗?")){
                        this.$bus.$emit('deleteTodo',id)
                    }
                }
            }
        }
    </script>
    
    <style scoped>
        li {
            list-style: none;
            height: 36px;
            line-height: 36px;
            padding: 0 5px;
            border-bottom: 1px solid #ddd;
        }
    
        li label {
            float: left;
            cursor: pointer;
        }
    
        li label li input {
            vertical-align: middle;
            margin-right: 6px;
            position: relative;
            top: -1px;
        }
    
        li button {
            float: right;
            display: none;
            margin-top: 3px;
        }
    
        li:before {
            content: initial;
        }
    
        li:last-child {
            border-bottom: none;
        }
    
        li:hover {
            background-color: #eee;
        }
    
        li:hover button{
            display: block;
        }
    </style>
    

2.14 消息订阅与发布(pubsub)

  • src/components/School.vue

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	import pubsub from 'pubsub-js'
    
    	export default {
    		name:'School',
    		data() {
    			return {
    				name:'光明学校',
    				address:'广州',
    			}
    		},
    		methods:{
    			demo(msgName,data) {
    				console.log('我是School组件,收到了数据:',data)
    			}
    		},
    		mounted() {
    			this.pubId = pubsub.subscribe('demo',this.demo) //订阅消息
    		},
    		beforeDestroy() {
    			pubsub.unsubscribe(this.pubId) //取消订阅
    		}
    	}
    </script>
    
    <style scoped>
    	.school{
    		background-color: skyblue;
    		padding: 5px;
    	}
    </style>
    
    
  • src/components/Student.vue

    <template>
    	<div class="student">
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<button @click="sendStudentName">把学生名给School组件</button>
    	</div>
    </template>
    
    <script>
    	import pubsub from 'pubsub-js'
    
    	export default {
    		name:'Student',
    		data() {
    			return {
    				name:'张三',
    				sex:'男',
    			}
    		},
    		methods: {
    			sendStudentName(){
    				pubsub.publish('demo',this.name) //发布消息
    			}
    		}
    	}
    </script>
    
    <style scoped>
    	.student{
    		background-color: pink;
    		padding: 5px;
    		margin-top: 30px;
    	}
    </style>
    
    

效果:
在这里插入图片描述


总结:

  1. 一种组件间通信的方式,适用于 任意组件间通信

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在 beforeDestroy 钩子中,用 PubSub.unsubscribe(pid) 去取消订阅。


2.15 使用消息的订阅与发布优化Todo-List

  • src/App.vue

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import pubsub from 'pubsub-js'
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
    
        export default {
            name:'App',
            components: { MyHeader,MyList,MyFooter },
            data() {
                return {
                    todos:JSON.parse(localStorage.getItem('todos')) || []
                }
            },
            methods:{
                //添加一个todo
                addTodo(todoObj){
                    this.todos.unshift(todoObj)
                },
                //勾选or取消勾选一个todo
                checkTodo(_,id){
                    this.todos.forEach((todo)=>{
                        if(todo.id === id) todo.done = !todo.done
                    })
                },
                //删除一个todo
                deleteTodo(id){
                    this.todos = this.todos.filter(todo => todo.id !== id)
                },
                //全选or取消勾选
                checkAllTodo(done){
                    this.todos.forEach(todo => todo.done = done)
                },
                //删除已完成的todo
                clearAllTodo(){
                    this.todos = this.todos.filter(todo => !todo.done)
                }
            },
            watch:{
                todos:{
                    deep:true,
                    handler(value){
                        localStorage.setItem('todos',JSON.stringify(value))
                    }
                }
            },
            mounted(){
                this.pubId = pubsub.subscribe('checkTodo',this.checkTodo)
                this.$bus.$on('deleteTodo',this.deleteTodo)
            },
            beforeDestroy(){
                pubsub.unsubscribe(this.pubId)
                this.$bus.$off('deleteTodo')
            }
        }
    </script>
    
    <style>
        body {
            background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
            outline: none;
        }
    
        .todo-container {
            width: 600px;
            margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    
  • src/components/myItem.vue

    <template>
        <li>
            <label>
                <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span>{{todo.title}}</span>
            </label>
            <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
        </li>
    </template>
    
    <script>
        import pubsub from 'pubsub-js'
        export default {
            name:'MyItem',
            props:['todo'],
            methods:{
                handleCheck(id){                    
                    pubsub.publish('checkTodo',id)
                },
                handleDelete(id,title){
                    if(confirm("确定删除任务:"+title+"吗?")){
                        this.$bus.$emit('deleteTodo',id)
                    }
                }
            }
        }
    </script>
    
    <style scoped>
        li {
            list-style: none;
            height: 36px;
            line-height: 36px;
            padding: 0 5px;
            border-bottom: 1px solid #ddd;
        }
    
        li label {
            float: left;
            cursor: pointer;
        }
    
        li label li input {
            vertical-align: middle;
            margin-right: 6px;
            position: relative;
            top: -1px;
        }
    
        li button {
            float: right;
            display: none;
            margin-top: 3px;
        }
    
        li:before {
            content: initial;
        }
    
        li:last-child {
            border-bottom: none;
        }
    
        li:hover {
            background-color: #eee;
        }
    
        li:hover button{
            display: block;
        }
    </style>
    

2.16 nextTick(回调函数)

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

2.17 过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
      	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。


 
非常感谢您阅读到这里,如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 分享👥 留言💬thanks!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java技术一点通

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值