一、组件化认识
1.1、什么事组件化
1.2、组件化思想
二、组件注册使用
2.1、注册步骤
<div id="app"> <!--3.使用组件--> <my-cpn></my-cpn> <div> <my-cpn></my-cpn> </div> </div> <script src="../js/vue.js"></script> <script> //1.创建组件构造对象 const cpnC = Vue.extend({ template: ` <div> <h2>标题</h2> <p>内容1</p> <p>内容2</p> </div>` }) //2.注册组件 Vue.component('my-cpn',cpnC) const app = new Vue({ el: '#app', data: { message: 'Hello Vuejs' } }) </script>
2.2、注册解析
1)Vue.extend():
- 调用Vue.extend()创建的是一个组件构造器。
- 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
- 该模板就是在使用到组件的地方,要显示的HTML代码。
- 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
2)Vue.component():
- 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
- 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
3.组件必须挂载在某个Vue实例下,否则它不会生效
三、全局组件和局部组件
3.1、全局组件
当我们通过调用Vue.component()注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。
3.2、局部组件
我们注册的组件是挂载在某个实例中, 那么就是一个局部组件
四、父子组件
<div id="app"> <parent-cpn></parent-cpn> </div> <script src="../js/vue.js"></script> <script> //创建一个子组件 const childCpn = Vue.extend({ template: ` <div>子组件内容</div>` }); //创建一个父组件 const parentCpn = Vue.extend({ template: ` <div> <h2>父组件标题</h2> <child-cpn></child-cpn> </div>`, components: { 'child-cpn': childCpn } }) //root组件 const app = new Vue({ el: '#app', components: { 'parent-cpn': parentCpn } }) </script>
五、注册组件语法糖
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替
<div id="app"> <cpn1></cpn1> <cpn2></cpn2> </div> <script src="../js/vue.js"></script> <script> // 2.注册全局组件 Vue.component('cpn1', { template: ` <div> <h2>我是标题1</h2> <p>我是内容, 哈哈哈哈</p> </div> ` }) // 2.注册局部组件的语法糖 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { 'cpn2': { template: ` <div> <h2>我是标题2</h2> <p>我是内容, 呵呵呵</p> </div>` } } }) </script>
六、模板的分离
如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
Vue提供了两种方案来定义HTML模块内容:
- 使用<script>标签
- 使用<template>标签
6.1、使用<script>标签
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <cpn></cpn> </div> <!--1.script标签, 注意:类型必须是text/x-template--> <script type="text/x-template" id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,哈哈哈</p> </div> </script> <script src="../js/vue.js"></script> <script> // 1.注册一个全局组件 Vue.component('cpn', { template: '#cpn' }) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script> </body> </html>
6.2、使用<template>标签
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <cpn></cpn> </div> <!--2.template标签--> <template id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,呵呵呵</p> </div> </template> <script src="../js/vue.js"></script> <script> // 1.注册一个全局组件 Vue.component('cpn', { template: '#cpn' }) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script> </body> </html>
七、组件数据存放
7.1、组件可以访问Vue实例数据吗?
7.2、组件数据的存放
组件对象也有一个data属性,只是这个data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据
<div id="app"> <my-cpn></my-cpn> </div> <template id="cpn"> <div>消息: {{message}}</div> </template> <script src="../js/vue.js"></script> <script> // 1.注册一个全局组件 Vue.component('my-cpn', { template: '#cpn', data() { return { message: 'Hello' } } }) const app = new Vue({ el: '#app', data: { message: 'Hello Vuejs' } }) </script>
为什么data在组件中必须是一个函数呢?
- 首先,如果不是一个函数,Vue直接就会报错。
- 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--组件实例对象--> <div id="app"> <cpn></cpn> <cpn></cpn> <cpn></cpn> </div> <template id="cpn"> <div> <h2>当前计数: {{counter}}</h2> <button @click="increment">+</button> <button @click="decrement">-</button> </div> </template> <script src="../js/vue.js"></script> <script> // 1.注册组件 const obj = { counter: 0 } Vue.component('cpn', { template: '#cpn', // data() { // return { // counter: 0 // } // }, data() { return obj }, methods: { increment() { this.counter++ }, decrement() { this.counter-- } } }) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script> </body> </html>
八、父子之间通信
8.1、父传子-props基本用法
在子组件中,使用选项props来声明需要从父级接收到的数据。
props的值有两种方式:
- 方式一:字符串数组,数组中的字符串就是传递时的名称。
- 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
我们先来看一个最简单的props传递:
<div id="app"> <cpn :cmessage="message" :cmovies="movies"></cpn> </div> <template id="cpn"> <div> <ul> <li v-for="item in cmovies">{{item}}</li> </ul> <h2>{{cmessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> // 父传子: props const cpn = { template: '#cpn', props: ['cmovies', 'cmessage'] } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, components: { cpn } }) </script>
8.2、父传子-props数据验证
<div id="app"> <cpn :cmessage="message" :cmovies="movies"></cpn> </div> <template id="cpn"> <div> <ul> <li v-for="item in cmovies">{{item}}</li> </ul> <h2>{{cmessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> // 父传子: props const cpn = { template: '#cpn', props: { // 提供一些默认值, 以及必传值 cmessage: { type: String, default: 'aaaaaaaa', required: true }, // 类型是对象或者数组时, 默认值必须是一个函数 cmovies: { type: Array, default() { return [] } } } } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, components: { cpn } }) </script>
8.3、父传子-pros中的驼峰标识
<div id="app"> <cpn :c-info="info" :child-my-message="message" v-bind:class></cpn> </div> <template id="cpn"> <div> <h2>{{cInfo}}</h2> <h2>{{childMyMessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> const cpn = { template: '#cpn', props: { cInfo: { type: Object, default() { return {} } }, childMyMessage: { type: String, default: '' } } } const app = new Vue({ el: '#app', data: { info: { name: 'why', age: 18, height: 1.88 }, message: 'aaaaaa' }, components: { cpn } }) </script>
8.4、子传父-自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件了。v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件
自定义事件的流程:
- 在子组件中,通过$emit()来触发事件。
- 在父组件中,通过v-on来监听子组件事件。
<div id="app"> <child-cpn @increment="changeTotal" @decrement="changeTotal"></child-cpn> <h2>点击次数: {{total}}</h2> </div> <template id="childCpn"> <div> <button @click="increment">+1</button> <button @click="decrement">-1</button> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { total: 0 }, methods: { changeTotal(counter) { this.total = counter } }, components: { 'child-cpn': { template: '#childCpn', data () { return { counter: 0 } }, methods: { increment() { this.counter++; this.$emit('increment',this.counter) }, decrement() { this.counter--; this.$emit('decrement',this.counter) } } } } }) </script>
8.5、父子组件的访问方式: $children
父组件访问子组件:使用$children或$refs reference(引用)
子组件访问父组件:使用$parent
this.$children是一个数组类型,它包含所有子组件对象
$children的缺陷:
- 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
- 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
- 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
8.6、父子组件的访问方式: $refs
$refs和ref指令通常是一起使用的。
首先,我们通过ref给某一个子组件绑定一个特定的ID。
其次,通过this.$refs.ID就可以访问到该组件了。
8.7、父子组件的访问方式: $parent
九、插槽slot
组件的插槽:
- 组件的插槽也是为了让我们封装的组件更加具有扩展性。
- 让使用者可以决定组件内部的一些内容到底展示什么
例子:移动网站中的导航栏。
- 移动开发中,几乎每个页面都有导航栏。
- 导航栏我们必然会封装成一个插件,比如nav-bar组件。
- 一旦有了这个组件,我们就可以在多个页面中复用了。
9.1、插槽的基本使用
在子组件中,使用特殊的元素<slot>就可以为子组件开启一个插槽。该插槽插入什么内容取决于父组件如何使用
9.2、具名插槽slot
9.3、编译作用域
9.4、作用域插槽:使用
<div id="app"> <!--默认方式--> <cpn></cpn> <cpn> <!--获取子组件中的pLanguage--> <template slot-scope="slot"> <span>{{slot.data.join(' - ')}}</span> </template> </cpn> <cpn> <!--获取子组件中的pLanguage--> <template slot-scope="slot"> <span>{{slot.data.join(' * ')}}</span> </template> </cpn> </div> <template id="cpn"> <div> <slot :data="pLanguage"> <ul> <li v-for="item in pLanguage">{{item}}</li> </ul> </slot> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: 'Hello Vuejs' }, components: { cpn: { template: '#cpn', data() { return { pLanguage: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift'] } } } } }) </script>