Vue 基础笔记 - 组件

组件的使用主要内容包括:

  1. 组件定义
  2. 组件注册
  3. 组件组件间通信(传值)

1. 基本示例

	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	
	<div id="components-demo">
	  <!-- 使用组件 -->
	  <button-counter></button-counter>
	</div>
	
	<script>
		// 定义新组件 ComponentA 
		var ComponentA = {
		  data: function () {
		    return {
		      count: 0
		    }
		  },
	    template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
	  }
	 
	 //全局注册定义的组件,并将它取名为 button-counter
	 Vue.component('button-counter', ComponentA);
	   
	 new Vue({ el: '#components-demo' })
    </script>
})

2. 组件的定义

2.1 组件结构

完整的组件包括三个部分:

  • template(必须)
    template 部分主要是描述视图,可以使用 HTML 标签或自定义组件标签,CSS 等描述组件显示。
  • script(可选)
    与组件相关的 JS 脚本,一般包括 data,methods,props,watchs 等。
  • style(可选)
    可以将组件的显示样式定义统一定义在这个部分

2.2 使用 JavaScript 定义组件

	var ComponentA = {
		data: function () {
		    return {
		      count: 0
		    }
		  },
	    template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
    }

2.3 使用 Vue 文件定义组件

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String
  }
};
</script>

<style scoped lang="stylus">
h1
  margin 40px 0 0
  color #42b983
</style>

3. 组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:
在这里插入图片描述例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。

3.1 全局注册

通过 Vue.component 全局注册

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

全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。

3.2 局部注册

在组件 ComponentB 中使用 ComponentA , 在ComponentB 的 components 选项中注册想要使用的组件:

<template>
	...
	<ComponentA />
	...
</template>

<script>
	import ComponentA from "@/views/ComponentA";

	export default {
		components: {
		'ComponentA': ComponentA    // 组件名称:选项对象
		},
	// ...
	}
</script>

4. 通过 Prop 向子组件传递数据

Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。

	<blog-post v-bind:title="title"></blog-post>
	
	Vue.component('blog-post', {
	  props: ['title'],
	  template: '<h3>{{ title }}</h3>'
	})
	new Vue({
		...
		data:{
			title: "hello"
		}
	})

2.3 定义 template

每个组件必须只有一个根元素, 你可以将模板的内容包裹在一个父元素内

	Vue.component('blog-post', {
	  props: ['post'],
	  template: `
	    <div class="blog-post">
	      <h3>{{ post.title }}</h3>
	      <div v-html="post.content"></div>
	    </div>
	  `
	})

3. 组件事件

3.1 基本使用

	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	
	<div id="components-demo" :style="{ fontSize: postFontSize + 'em' }">
		 <blog-post
			  v-for="post in posts"
			  v-bind:key="post.id"
			  v-bind:post="post"
			  v-on:enlarge-text="postFontSize += 0.1"
			></blog-post>
	</div>
	
	<script>
		
		Vue.component('blog-post', {
		  props: ['post'],
		  template: `
			<div class="blog-post">
			  <h3>{{ post.title }}</h3>
			  <button v-on:click="$emit('enlarge-text')">
				Enlarge text
			  </button>
			  <div v-html="post.content"></div>
			</div>
		  `
		})
	
	
		new Vue({ 
			el: '#components-demo',
			data: {
				posts: [
				  { id: 1, title: 'My journey with Vue' },
				  { id: 2, title: 'Blogging with Vue' },
				  { id: 3, title: 'Why Vue is so fun' }
				],
	
				postFontSize: 1
			}
		})
	</script>

简单说明:

  1. 组件定义 button 通过调用内建的 $emit 方法并传入事件名称来触发一个事件
  2. 组件使用时, 监听事件并使用 JS 代码修改 font size

3.2 使用事件抛出一个值

	<button v-on:click="$emit('enlarge-text', 0.1)">
	  Enlarge text
	</button>
	<blog-post v-on:enlarge-text="postFontSize += $event"></blog-post>


	# 或者使用处理函数
	<blog-post v-on:enlarge-text="onEnlargeText"></blog-post>
	methods: {
	  onEnlargeText: function (enlargeAmount) {
	    this.postFontSize += enlargeAmount
	  }
	}

4. 使用 v-model

4.1 v-model 直接使用

	<input v-model="searchText">

等价于

	<input
	  v-bind:value="searchText"
	  v-on:input="searchText = $event.target.value"
	>

4.2 v-model 用于组件

	<custom-input
	  v-bind:value="searchText"
	  v-on:input="searchText = $event"
	></custom-input>

为了让它正常工作,这个组件内的 必须:

  • 将其 value attribute 绑定到一个名叫 value 的 prop
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出

写成代码之后是这样的:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

现在 v-model 就应该可以在这个组件上完美地工作起来了:

<custom-input v-model="searchText"></custom-input>

5. 插槽

定义在组件模板中的 <slot></slot> 会在渲染时替换成自定义组件组件起始标签和结束标签之间的内容。

	<blog-post>会替换 slot 的内容</blog-post>
	Vue.component('blog-post', {
	  props: ['post'],
	  template: `
		<div class="blog-post">
		  <h3>{{ post.title }}</h3>
		  <button v-on:click="$emit('enlarge-text')">
			Enlarge text
		  </button>
		  <div v-html="post.content"></div>
		  <slot><slot>
		</div>
	  `
	})

5.1 编译作用域

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。简单来说,在 slot 中无法访问实例的作用域。

5.2 后备内容

有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。

5.3 具名插槽

有时我们需要多个插槽, 元素有一个特殊的 attribute:name v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slot 和 slot-scope attribute 的 API 替代方案

  • 具名插槽的缩写
    即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:
	<template v-slot:header>
	    <h1>Here might be a page title</h1>
	 </template>

	#替换成

	 <template #header>
	    <h1>Here might be a page title</h1>
	 </template>

5.4 插槽 prop

允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。

	<cc v-for="p in posts" 
		:key="p.id"
		:todo="p">	
	</cc>
	Vue.component('cc', {
	  props: ['todo'],
	  template: `
		<p>
		 <slot v-bind:todo="todo">
		{{todo}}
		 </slot>
		</p>
	  `
	})

6. 保持控件状态

当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。我们可以用一个 <keep-alive> 元素将其动态组件包裹起来。

7. 异步组件

暂时看不懂, 待了解

8. 处理边界情况

8.1 访问元素 & 组件

8.1.1 访问根实例

对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,我们强烈推荐使用 Vuex 来管理应用的状态。

// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

8.1.2 访问父级组件实例

$parent property 可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。

8.1.3 访问子组件实例或子元素

你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用,可以通过this.$refs.xxx 来访问这个实例, 你应该避免在模板或计算属性中访问 $refs。

8.1.4 依赖注入

	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	
	<div id="components-demo" :style="{ fontSize: postFontSize + 'em' }">
		<cc v-for="p in posts" 
			:key="p.id"
			:todo="p">	
		</cc>
	</div>
	
	<script>
		
		Vue.component('cc', {
		  props: ['todo'],
		  
		  //我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property
		  inject: ['getMap'],
		  template: `
			<p>
			<button @click="getMap">a button</button>
			 <slot v-bind:todo="todo">
			{{todo}}
			 </slot>
			</p>
		  `
		})
	
	
		var vue = new Vue({ 
			el: '#components-demo',
			data: {
				posts: [
				  { id: 1, title: 'My journey with Vue' },
				  { id: 2, title: 'Blogging with Vue' },
				  { id: 3, title: 'Why Vue is so fun' }
				],
	
				postFontSize: 1,
				name: 'abc'
			},
	
			methods:{
				getMap: function(){ console.log('a') }
			},
			
			// provide 选项允许我们指定我们想要提供给后代组件的数据/方法。	
			provide: function () {
				return {
					getMap: this.getMap
				}
			}
		})
	
	</script>

相比 $parent 来说,这个用法可以让我们在任意后代组件中访问 getMap,而不需要暴露整 实例。这允许我们更好的持续研发该组件,而不需要担心我们可能会改变/移除一些子组件依赖的东西。同时这些组件之间的接口是始终明确定义的,就和 props 一样。
负面影响

  • 它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
  • 所提供的 property 是非响应式的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值