组件基础

138 阅读2分钟

组件的概念

组件是可复用的 Vue 实例,包含html、css、js三部分。

组件树

通常一个应用会以一棵嵌套的组件树的形式来组织:

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

组件创建

局部组件

	<body>
		<div id="app">
			<!--3. 使用组件 -->
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			// App组件
			// 1.创建组件
			//组件是可复用的vue实例
			const App = {
				// 一个组件就是一个对象
				//template里写html结构
				template: `
					<div>
						<h3>{{msg}}</h3>
						<button @click='handleClick'>按钮</button>
					</div>
					`,
				data(){
					// 在组件中data必须是一个函数,返回一个对象,便于维护数据
					return {
						msg: '我是App组件'
					}
				},
				methods:{
					handleClick(){
						this.msg = '局部组件'
					}
				},
				computed:{
					
				}
			};
			//vue就是一个根组件
			const vm = new Vue({
				el: "#app",
				data: {

				},
				//2. 挂载组件
				components: {
					App
				}
			});
		</script>
	</body>

全局组件

Vue.compontent('组件名',{配置});
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			//1.创建全局组件,第一个参数是组件名,第二个是配置
			//只要创建了全局组件可以在任意地方(template)使用
			Vue.component('myHeader',{
				template:`
					<div>我是导航组件</div>	
				`
			});
			Vue.component('myAside',{
				template:`
					<div>我是侧边栏组件</div>
				`
			});
			const myContent = {
				data(){
					return{
						
					}
				},
				template:`
					<div>我是内容组件</div>
				`
			}
			const App = {
				// 2.使用全局组件
				template: `
					<div>
						<myHeader></myHeader>
						<div>
							<myAside/>
							<myContent></myContent>
						</div>
					</div>
					`,
				components:{
					myContent
				}
			};
			const vm = new Vue({
				el: "#app",
				data: {
					
				},
				components: {
					App
				}
			});
		</script>
	</body>

注意事项

组件命名

定义组件名有两种方式

使用 kebab-case

Vue.component('my-component-name', { /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

使用 PascalCase

Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说<my-component-name><MyComponentName>都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

组件通信

父传子

通过props来进行通信:

  1. 子组件中声明props接收在父组件挂载的属性
  2. 可以在子组件的template中任意使用
  3. 父组件中绑定自定义的属性
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			
			Vue.component('Appson',{
				template:`
				<div>
					<h3>我是App的子组件</h3>
					<h4>{{sonData}}</h4>
				</div>`,
				//接收父组件传值
				props:['sonData']
			})
			const App = {
				template:`
				<div>
					<Appson :sonData = 'msg'></Appson>
				</div>`,
				components:{
					
				},
				data(){
					return {
						msg:'我是父组件传进来的值'
					}
				}
			};
			const vm = new Vue({
				el: "#app",
				data: {
					
				},
				components: {
					App
				}
			});
		</script>
	</body>

子传父

  1. 父组件中,绑定自定义的事件
  2. 在子组件中 ,触发原生的事件
  3. 在事件函数中通过this.$emit触发自定义的事件
  4. 自定义事件执行
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			
			Vue.component('Appson',{
				template:`
				<div>
					<h3>我是App的子组件</h3>
					<!--step2:在子组件sonData中,触发原生的input事件 -->
					<input type='text'@input ='handleInp'/> 
				</div>`,
				methods:{
					handleInp(e){
						const val = e.target.value;
						//step3:在事件函数handleInp中通过this.$emit触发自定义的事件
						this.$emit('inpHandle',val);
					},
				}
			});
			const App = {
				template:`
				<div>
					<div class="father">数据:{{newVal}}</div>
					<!--step1:在父组件App中,绑定自定义事件inpuHandle -->
					<Appson  @inpHandle='input' ></Appson>
				</div>`,
				data(){
					return {
						newVal:''
					}
				},
				methods:{
					//step4:自定义inpuHandle事件执行
					input(newVal){
						this.newVal = newVal;
					}
				}
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App
				}
			});
		</script>
	</body>

平行组件通信

通过中央事件总线

const bus = new Vue();
//bus.$on()绑定事件
//bus.$emit()触发事件
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			
			Vue.component('A',{
				template:`
				<button @click = 'handleClick'>加入购物车</button>
				`,
				methods:{
					handleClick(){
						bus.$emit('add',1);
					}
				}
			});
			// 中央事件总线 bus
			const bus = new Vue;
			Vue.component('B',{
				template:`
				<div>{{count}}</div>
				`,
				data(){
					return{
						count:0
					}
				},
				created(){
					// $on 绑定事件
					bus.$on('add',(n)=>{
						this.count += n;
					});
					// $emit 触发事件
				}
			});
			const App = {
				template:`
				<div>
					<A></A>
					<B></B>
				</div>`,
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				}
			});
		</script>
	</body>

其他通信方式

通过provideinject无论组件嵌套多深,都能实现。

  1. 父组件 provide提供变量
  2. 子组件 inject接收变量
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			
			Vue.component('A',{
				template:`
				<div>
					<B></B>
				</div>
				`,
				created(){
					console.log(this.$parent);//App组件
					console.log(this.$parent.title);//grandfather
				}
			});
			Vue.component('B',{
				template:`
					<div>{{msg}}</div>
				`,
				inject:['msg'],
				created(){
					console.log(this.msg);
				}
			});
			const App = {
				template:`
				<div>
					<A></A>
				</div>`,
				data(){
					return{
						title:'grandfaher'
					}
				},
				provide(){
					return {
						msg:'grandfather'
					}
				}
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				},
			});
		</script>
	</body>

插槽

匿名插槽

	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			Vue.component('Mbtn',{
				template:`
				<button>
					<slot></slot>
				</button>
				`	
			});
			const App = {
				template:`
				<div>
					<Mbtn>登录</Mbtn>
					<Mbtn>注册</Mbtn>
				</div>`,
				data(){
					return{
						title:'grandfaher'
					}
				},
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				},
			});
		</script>
	</body>

具名插槽

只要匹配到slot标签的name值,模板template中的内容就会被插入到槽中。

	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			Vue.component('Mbtn',{
				template:`
				<button>
					<slot name="login"></slot>
					<slot name="submit"></slot>
					<slot name="reset"></slot>
				</button>
				`	
			});
			const App = {
				template:`
				<div>
					<Mbtn>
						<template slot="login">
							<a href='#'>登录</a>
						</template>	
					</Mbtn>
					<Mbtn>
						<template slot="submit">
							注册
						</template>
					</Mbtn>
					<Mbtn>
						<template slot="reset">
							重置
						</template>
					</Mbtn>
				</div>`,
				data(){
					return{
						title:'grandfaher'
					}
				},
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				},
			});
		</script>
	</body>

作用域插槽

针对项目需求使用

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			const todoList = {
				data(){
					return{
						
					}
				},
				props:{
					todos:Array,
					defaultValue:[]
				},
				template:`
				<ul>
					<li v-for='item in todos' :key='item.id'>
						<slot :itemValue='item'>
						</slot>
						{{item.title}}
						
					</li>
				</ul>	
				`
				
			}
			
			const App = {
				data(){
					return{
						todoList:[
							{
								title:'vue基础',
								isComplate:true,
								id:1
							},
							{
								title:'vue组件化',
								isComplate:false,
								id:2
							},
							{
								title:'vue全家桶',
								isComplate:false,
								id:3
							},
							{
								title:'vue实战',
								isComplate:false,
								id:4
							},
						]
					}
				},
				components:{
					todoList
				},
				template:`
					<todoList :todos='todoList'>
						<template v-slot="data">
							<input type="checkbox" v-model="data.itemValue.isComplate"/>
						</template>
					</todoList>
					`
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				},
			});
		</script>
	</body>
</html>

生命周期

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			.active{
				color: red;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			Vue.component('Test',{
				data(){
					return{
						msg:'john',
						isActive:false
					}
				},
				template:`
				<div>
					<button @click = 'handleClick'>修改</button>
					<h3 :class={active:isActive}>{{msg}}</h3>
				</div>
				`,
				methods:{
					handleClick(){
						this.msg = '改变了';
						this.isActive = true;
					}
				},
				beforeCreate(){
					console.log('组件创建之前',this.$data);
				},
				created(){
					//此时发送ajax,请求后端的数据
					console.log('组件创建完成',this.$data);
				},
				beforeMount(){
					// 即将挂载
					console.log('dom挂载之前',document.getElementById('app'));
				},
				mounted(){
					// 发送ajax
					console.log('dom挂载完成',document.getElementById('app'));
				},
				beforeUpdate(){
					console.log('更新之前的dom',document.getElementById('app').innerHTML);
				},
				updated(){
					console.log('更新之后的dom',document.getElementById('app').innerHTML);
				},
				beforeDestroy(){
					console.log('销毁之前');
				},
				destroyed(){
					console.log('销毁完成');
				},
				activated(){
					console.log('组件被激活了');
				},
				deactivated(){
					console.log('组件被停用了');
				}
			});
			const App = {
				data(){
					return{
						isShow:true
					}	
				},
				template:`
					<div>
						<keep-alive>
							<Test v-if='isShow'></Test>
						</keep-alive>	
						<button @click = 'handleDestory'>改变生死</button>
					</div>
				`,
				methods:{
					handleDestory(){
						this.isShow = !this.isShow;
					}
				}
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				}
			});
		</script>
	</body>
</html>

异步组件加载

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			
		</style>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="module">
			
			const App = {
				data(){
					return{
						isShow:false
					}	
				},
				components:{
					Test:()=> import('./Test.js')
				},
				template:`
					<div>
						<button @click='asyncLoad'>异步加载</button>
						<Test v-if='isShow'></Test>
					</div>
				`,
				methods:{
					asyncLoad(){
						this.isShow = !this.isShow
					}
				}
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				}
			});
		</script>
	</body>
</html>

Test.js

export default {
	data(){
		return{
			msg:'john'
		}
	},
	template:`
	<div>
		<h3>{{msg}}</h3>
	</div>	`
};

refs的使用

访问子组件实例或子元素

尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用。

	<body>
		<div id="app">
			<App></App>
		</div>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			const Test = {
				data(){
					return{
						msg:'john'
					}
				},
				template:`
					<div>
						<h3>{{msg}}</h3>
					</div>	
					`
			};
			const App = {
				components:{
					Test
				},
				mounted(){
					//如果给标签添加ref,获取的就是真实的dom节点
					console.log(this.$refs.btn);
					//加载页面自动获取焦点
					this.$refs.input.focus();
					//如果给组件添加ref,获取的就是该组件
					console.log(this.$refs.test);
				},
				template:`
					<div>
						<Test ref='test'></Test>
						<input type="text"ref="input"/>
						<button ref='btn'>改变生死</button>
					</div>
				`,
			};
			const vm = new Vue({
				el: "#app",
				components: {
					App 
				}
			});
		</script>
	</body>

nextTick的使用

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

<body>
	<div id="app">
		<h3>{{message}}</h3>
	</div>
	<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		const vm = new Vue({
			el:'#app',
			data:{
				message:'john'
			},
		});
		vm.message = 'new message';
		// console.log(vm.$el.textContent);//john
		Vue.nextTick(()=>{
			console.log(vm.$el.textContent);//message
		});
	</script>
</body>

应用

<!DOCTYPE html>
<html lang="zh">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title></title>
</head>
<body>
	<div id="app">
		<App></App>
	</div>
	<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		/* 需求
		 在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,
		 然后我们在接口一返回数据就展示了这个浮层组件,展示的同时,
		 上报一些数据给后台(这些数据是父组件从接口拿的),
		 这个时候,神奇的事情发生了,虽然拿到了数据,但是浮层出现的时候,
		 这些数据还未更新到组件上去,上报失败*/
		const Pop = {
			// 浮层组件
			data(){
				return{
					isShow:false
				}
			},
			template:`
				<div v-if = 'isShow'>
					{{name}}
				</div>
			`,
			methods:{
				show(){
					this.isShow = true;//浮层组件展示
					console.log(this.name);
				}
			},
			props:{
				name:{
					type:String,
					default:''
				}
			}
		};
		const App = {
			data(){
				return{
					name:''
				}
			},
			template:`
				<div>
					<pop ref="pop" :name='name'></pop>
				</div>
			`,	
			components:{
				Pop
			},
			created(){
				// 模拟异步请求
				setTimeout(()=>{
					// 数据更新
					this.name = 'john';
					this.$nextTick(()=>{
						this.$refs.pop.show();
					})
					
				},1000);
			}
			
		}
		const vm = new Vue({
			el:'#app',
			data:{
				
			},
			components:{
				App
			}
		});
	</script>
</body>
</html>

对象变更检测注意事项

vue不能检测对象属性的添加删除,如果想要操作,就可以用vue提供的set()方法。

Vue.set(obj,key,val)

<body>
	<div id="app">
		<h3>{{user}}</h3>
		<button @click = 'addAge'>添加</button>
	</div>
	<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		const vm = new Vue({
			el:'#app',
			data:{
				user:{},
			},
			created(){
				setTimeout(()=>{
					this.user={
						name:'john',
					}
				},1250)
			},
			methods: {
				addAge() {
					// this.user['age']=18
					// this.user.age = 18;
					
					// this.$set是Vue.set的别名
					this.$set(this.user,'age',18);
					/* // 也可以通过Object.assign()添加多个响应式属性
					this.user = Object.assign({},this.user,{
						age:18,
						phone:1234567890
					}) */
				}
			},
			
		});
	</script>
</body>

mixin混入偷懒技术

mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。

Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。

作用

来分发vue组件中的可复用功能

<body>
	<div id="app">
		<h3>{{msg}}</h3>
	</div>
	<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		const myMixin = {
			data(){
				return{
					msg:"123"
				}
			},
			created(){
				this.sayHello()
			},
			methods:{
				sayHello(){
					console.log('hello mixin');
				}
			}
			
		}
		const vm = new Vue({
			el:'#app',
			data:{
				title:'john'
			},
			created(){
				console.log(111);
			},
			mixins:[myMixin]
		});
	</script>
</body>

应用

<!DOCTYPE html>
<html lang="zh">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title></title>
</head>
<body>
	<div id="app">
	</div>
	<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		/* 全局的mixin
		Vue.mixin({

		})
		注意事项
		每个组件实例创建时,他都会被调用 
		*/

		const toggleShow = {
			data(){
				return{
					isShow:false
				}
			},
			methods:{
				toggleShow(){
					this.isShow = !this.isShow;
				}
			}
		}
		const Modal = {
			template:`
				<div v-if='isShow'>
					<h3>模态框组件</h3>
				</div>
			`,
			// 局部的mixins
			mixins:[toggleShow]
		};
		const Prompt  = {
			template:`
				<div v-if='isShow'>
					<h2>提示框组件</h2>
				</div>
			`,
			mixins:[toggleShow]
		};
		const vm = new Vue({
			el:'#app',
			data:{
				
			},
			components:{
				Modal,
				Prompt
			},
			template:`
				<div>
					<button @click ='handleModal'>模态框</button>
					<button @click ='handlePrompt'>提示框</button>
					<Modal ref="modal"></Modal>
					<Prompt ref='hint'></Prompt>
				</div>
			`,
			methods:{
				handleModal(){
					this.$refs.modal.toggleShow();
				},
				handlePrompt(){
					this.$refs.hint.toggleShow();
				}
			}
		});
	</script>
</body>
</html>