3 组件化开发
为了提高代码复用性,使用组件化开发,将一串拥有独立功能、可以接受参数、可渲染控件的代码封装起来,并给与一个名字,在使用时与其他html基础元素相同。
组件其实就是可复用的Vue实例,和new Vue一样,存在data、computed、watch、methods等属性,而el是根实例特有的!
注意:vue2要求组件模板为单根元素,vue3无该要求
组件构建三步骤:
- 创建组件构造器:
Vue.extend() - 注册组件:
Vue.component() - 使用
注意:目前建议直接使用注册组件方法一步到位合并1,2两步:
Vue.component("abc",{
template:`<div></div>`,
})
组件使用:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<button-counter></button-counter>
</div>
<script>
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({
el: '#app'
})
</script>
</body>
</html>
3.1 全局组件和局部组件
全局组件:可以在所有的Vue实例下使用(可以有多个Vue实例,但是不推荐)
Vue.component('abc',abc)
局部组件:是在Vue实例下注册的组件
new Vue({
components:{
'abc':abc
}
})
3.2 父组件和子组件
在父组件构造器中注册的组件称为父组件的子组件
const cp2 = Vue.extend({
template:`
<div>
<p>222</p>
</div>
`,
})
const cp1 = Vue.extend({
template:`
<div>
<p>111</p>
<cp2></cp2>
</div>
`,
components:{
cp2:cp2
}
})
注意:如果想在当前作用域中使用自定义组件,必须在当前的作用域中注册该组件,不存在传递!
其实在父组件中,vue编译父组件的时候,子组件就被替换成对应的模板了,最终当前作用域中只存在父组件编译好的所有模板。即子组件不是在当前作用域中编译解析的,所以在当前作用域中需要使用子组件必须再次注册。
3.2.1 父组件直接访问子组件 $children
父组件如果向直接访问子组件可以使用:$children 或$refs
this.$children 获取到的是当前组件的所有子组件,可以通过遍历来访问子组件中的方法或者属性
更好的目的性更明确地是使用$refs ,使用方法:
- 在需要访问的组件上使用ref定义属性:
<abc ref="a"></abc> - 在需要访问的地方使用:
this.$refs.a 即可
3.2.2 子组件访问父组件 $parent
使用方法:this.$parent
不建议使用,耦合性太高了
3.2.3 直接访问Vue实例(顶层)$root
this.$root
3.3 模板抽离
将模板抽离出js内,放到html中去写,有两种方式
- 使用
script 标签,类型为text/x-template
<script type="text/x-template" id="cp1">
<div>
<h2>abc</h2>
</div>
</script>
<script>
Vue.component('cp1',{template:'#cp1'}
</script>
- 使用
template 标签
<template id="cp1">
<div>
<h2>abc</h2>
</div>
</template>
<script>
Vue.component('cp1',{template:'#cp1'}
</script>
3.4 组件data
组件的data 必须是一个函数! 不能是一个对象,这样每个组件实例才能获得一份独立的数据拷贝!否则所有组件状态会相同!
data: function () {
return {
count: 0
}
}
因为如果不是函数,则每次复用组件会导致组件内部状态一样,因为获取到的是同一个对象
3.5 组件传值props(父组件–>子组件)
Prop可以在组件上注册自定义的属性,props 属性接收一个变量数组[] :
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
<blog-post title="My journey with Vue"></blog-post>
<blog-post :title="msg"></blog-post>
props支持验证和默认值设置,这需要使用对象来传递
Vue.component('blog-post', {
props: {
title: String,
message: [String, Number],
name: {
type: String,
required: true,
default: "pp"
}
hobby: {
type: Array,
default: function(){return ["jump","eat"]}
}
other: {
type: Object,
default: function(){return {abc:"aaa"}}
}
},
template: '<h3>{{ title }}</h3>'
})
注意:这几种props写法都可以,一种是数组,一种是对象。其中对象写法可以定义类型和是否必须,当类型为数组或者对象时,对于低版本Vue的默认值必须是一个函数 类型可以定义为:String|Number|Boolean|Array|Object|Date|Function|Symbol
当然也可以自定义类型:
function Person(a, b){
this.a = a
this.b = b
}
props:{
author: Person
}
注意:在标签属性中不支持驼峰命名,如果在vue对象中的变量是驼峰命名,可以在v-bind的属性上使用- 进行分割,如:v-bind:abC="" 转化成v-bind:ab-c=""
3.6 组件事件监听$emit(子组件–>父组件)
情景:当子组件事件需要让父组件监听行为并作出一些改变时: 方法一:子组件无需复杂操作,仅仅传递参数给父组件:使用v-on:自定义事件="函数" 和v-on:事件=$emit("自定义事件") 组合来实现 方法二:子组件需要复杂操作,并传递参数给父组件:使用props传递函数
3.6.1 方法一
父组件:定义监听自定义事件
<blog-post v-on:enlarge-text="run"></blog-post>
子组件:定义触发方法,抛出对应的自定义事件名
<button v-on:click="$emit('enlarge-text')">Enlarge text</button>
<button v-on:click="crun">Enlarge text</button>
相当于子组件通过标准事件抛出自定义事件,然后父组件监听到该自定义事件,并触发相应函数进行操作
有时候,需要对自定义事件传参,则须要用到$emit(自定义事件名,参数) ,使用第二个参数进行传参,传递的参数在监听函数中使用$event 来获取,此处的$event 就是参数,可以直接使用
<button v-on:click="$emit('enlarge-text', 0.1)">Enlarge text</button>
<blog-post v-on:enlarge-text="postFontSize += $event"></blog-post>
更常用的是通过函数参数来获取
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
3.6.2 方法二
当然,对于子组件向父组件发起控制也可以通过props来做,具体思路:(由于回调函数定义在父组件中,可以更改父组件状态,而触发操作在子组件中,相当于子组件控制父组件)
- 父组件定义一个回调函数,将这个回调函数传递给子组件的绑定属性
- 子组件某个操作调用过程中调用父组件传过来的回调函数,并传递值即可
3.7 组件插槽slot
插槽:让组件扩展性更强,抽取共性,插槽用来替换个性 插槽用于在自定义组件中放置可以显示在自定义标签中间的内容,即自定义标签中间的部分会替换到插槽位置
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
<alert-box>
Something bad happened.
</alert-box>
插槽中间可以放其他标签,作为默认值,如果自定义标签中间存在标签,则默认值被该标签替换,否则显示默认标签 <slot><div>123</div></slot>
如果存在多个插槽,则会重复替换,如果有多个标签,则会一起替换到插槽位置
特别注意:插槽上不要写css或者判断,因为会被直接替换掉!可以采取间接方法用div包裹插槽,然后在div上进行判断和样式
3.7.1 具名插槽
多插槽情况,如果插槽具有name 属性,则必须替换时使用slot 属性指定替换的插槽:
<abc>
<div slot="a">444</div>
</abc>
<div class="abc">
<strong>Error!</strong>
<slot name="a"></slot>
<slot name="b"></slot>
<slot name="c"></slot>
</div>
3.7.2 作用域插槽
作用域插槽相当于将本作用域的一些数据提供到外部使用,即暴露本作用域中的内容
<template id="myabc">
<div>
<slot :mydata="abc">
<span>默认显示:{{abc}}</span>
</slot>
</div>
</template>
<script>
..
myabc: {
template: "#myabc",
data:function(){
return{
abc: 1111
}
}
}
...
</script>
<myabc>
<template slot-scope="myslot">
<span>改写显示:{{myslot}}</span>
</template>
</myabc>
分析使用过程:组件中在slot 标签上定义自定义属性,然后赋值,在组件外部(即使用该组建的组件中)可以通过:slot-scope 获取到内部的slot 上定义的自定义属性!最终展示结果为:改写显示:{ "mydata": 1111 }
注意:如果组件中定义了多个slot ,则会分别替换,且每个替换得到的slot 对象和对应slot的暴露属性一致
3.8 动态组件
动态组件用于在事件触发后动态加载组件,改变is 的值即可改变渲染的对象,接收的对象都被视为Vue组件(value需要加.prop)
<component v-bind:is="currentTabComponent"></component>
4 模块化开发
原生js进行编写时会导致全局变量同名问题,还有引入顺序也会导致一些问题,解决方法:模块化封装
模块化封装基本思想:使用匿名函数立即调用形式+闭包:(function(){})() ,因为函数有自己的作用域,不会导致同名,但是又出现代码复用性无的问题,因为拿不到函数内的变量或者函数,所以还需要通过闭包
var moduleA = (
function(){
var obj = {}
var f = function(x){console.log(x)}
var name = "pp"
obj.name = name
obj.f = f
return obj
}
)()
常见的模块化规范:CommonJS | AMD | CMD | ES6Modules nodejs中模块化使用的CommonJS规范
模块化规范核心:导入、导出
4.1 CommonJS
module.export = {
flag: true,
run(){},
name: "pp"
}
let {flag,run,name} = require('./xxx.js)
let x = require('./xxx.js)
let flag = x.flag
4.2 ES6 export/import
export let name = 'pp
export let age = 12
let name = "pp"
let age = 213
export {name,age}
import {flag,sum} from './xxx.js'
console.log(flag)
<script src="./xxx.js" type="module"></script>
4.2.1 export default
导出默认模块,普通导出:导出的名字和导入的名字必须相同才能正确获取
export default 导出则为匿名,可以在导入的时候去自定义名字,一个文件default 导出的只能存在一个,导入的时候不需要{} ,表示导入默认导出项
export default x
import y from 'xxx.js'
4.2.2 统一全部导出
由于对于非default 导入需要 保证导入的变量名和导出的相同,当导入变量很多,则可以采用导入全部作为一个变量,然后去使用
import * as x from 'xxx.js'
console.log(x.name)
|