跳到主要内容

Vue 组件化

· 阅读需 9 分钟
Yana Ching
Front End Engineer

组件化思想

标准 分治 重用 组合

组件化规范:Web Component

  • 尽可能重用代码
  • 自定义组件方式不太容易(HTML、css、js)
  • 多次使用组件可能导致冲突

通过封装好功能的定制元素解决上述问题

组件注册

组件命名

  • 短横线 Vue.component('my-component', { /* ... */ }) ( 推荐使用 )
  • 驼峰 Vue.component('myComponent', { /* ... */ })
    • 使用驼峰命名的组件只能在 模板 中使用驼峰命名的方式,如果要在 普通标签中使用,必须使用 短横线
// 全局注册组件
Vue.component(逐渐名称, {
data: 组件元素,
template: 组件模块内容
})
// 使用: 直接把 组件名称 当做 标签 使用即可
组件使用注意:
  1. 当模板比较复杂的时候,可以使用模板字符串 使用反引号

    Vue.component('component_name', {
    data: function(){
    return {}
    },// 要求是个函数
    template: `
    <div>
    <button></button>
    <button></button>
    </div>
    `
    // 当模板比较复杂的时候,可以使用模板字符串 使用反引号
    })
  2. data 参数必须是函数,并且要求返回一个对象

  3. 模板必须是单个根元素,即可由一个父级元素包含其他元素,不可在最外层使用多个兄弟元素

  4. 模板可重用,每个组件 data 属性中的值是相互独立的

  5. 局部注册的组件,只能在当前注册它的vue实例中使用

  6. 组件命名问题:使用 驼峰式(buttonCounter) 只能在 字符串模板 中用驼峰的方式使用组件,但是 在普通的标签中。必须使用 短横(button-counter) 的方式使用组件

var componentA = {
data: function(){
return {
msg: 'componentA'
}
}
}
var componentB = {
data: function(){
return {
msg: 'componentB'
}
}
}
var componentC = {
data: function(){
return {
msg: 'componentC'
}
}
}
new Vue({
el: '#app',
component: {
'component-a': componentA,
'component-b': componentB,
'component-c': componentC,
}
})
// 局部注册的组件,只能在当前注册它的vue实例中使用
// 全局组件 模板 中不可以使用局部注册的组件

Vue 调试工具用法

  • 克隆仓库
    • git clone https://github.com/vuejs/vue-devtools.git
  • 安装依赖包
    • cd vue-devtools
    • npm install
  • 构建
    • npm run build
  • 进入 chrome 扩展程序
    • 加载已解压的扩展程序
    • 进入已经编译好的 vue-devtools 文件夹,选择 shell>chrome

组件间数据交互

  • 父向子:子组件props接收,父组件给相应 props参数的属性 赋值

  • 子传父:在子模板中使用 $emit(函数名,参数)传值,父组件用v-on 监听子组件的事件,$event 专门用来接收传递过来的值

  • 兄弟之间:Vue实例 hub 作为事件中心,兄弟组件模板均使用钩子函数 mounted 监听 hub.$on(fnName,function),组件内设置方法,选择时机 使用 hub.$emit(fnName,传递的参数) 触发事件

子组件 通过 props 接收 传递过来的值

Vue.component('menu-item', {
props: ['title'],
template: '<div>{{title}}</div>'

})

父组件通过 属性值 将值传递给 子组件

<menu-item title="来自父组件的数据"></menu-item>    ------------ 直接写死

<menu-item :title="title"></menu-item> ------------------------------ 数据绑定
// 父传子
<div id="app">
<div>{{pmsg}}</div>
<menu-item title="写死的来自父组件的值"></menu-item>
<menu-item :title="ptitle" content="你好"></menu-item>

</div>
<script src="js/vue.js"></script>
<script>
// 父组件向子组件传值
Vue.component('menu-item', {
props: ['title','content'],
data: function() {
return {
msg: '子组件本身的数据',
}
},
template: '<div>{{msg + "======" + title + "=====" + content}}</div>'
})
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
ptitle: '父组件中动态绑定的数据'
}
})
</script>
<script type="text/plain">

父组件向子组件传值
1. Vue实例本身就是一个根组件
2. 全局定义一个子组件
3. 通过给 子组件属性 传值,子组件通过 props 以数组形式接受值 的方式 完成 =>子 的传值过程
4. props 是一个数组
</script>
// 子传父
<div id="app">
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
</div>

<script src="js/vue.js"></script>
<script>
// 子组件向父组件传值 - 携带参数
Vue.component('menu-item', {
props: ['parr'],
// data: function(){
// return {

// }
// },
template: `
<div>
<ul>
<li :key='index' v-for='(item, index) in parr'>{{item}}</li>
</ul>
<button @click="$emit('enlarge-text', 5)">扩大父组件中字体大小</button>
<button @click="$emit('enlarge-text', 10)">扩大父组件中字体大小</button>
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
parr: ['apple', 'pear', 'banana'],
fontSize: 10
},
methods: {
handle: function(val){
// 扩大字体大小
this.fontSize += val
}
}
})
</script>
// 兄弟
<div id="app">
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script src="js/vue.js"></script>
<script>
// 1. 提供事件中心
var hub = new Vue()



Vue.component('test-tom', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>TOM {{num}}</div>
<div>
<button @click="handle">点击</button>
</div>
</div>
`,
methods: {
handle: function(){
hub.$emit('jerry-event', 10)
}
},
mounted: function(){
// 模板加载完毕之后,监听事件 tom-event
hub.$on('tom-event', (val) => {
this.num += val
})
}
})

Vue.component('test-jerry', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>JERRY {{num}}</div>
<div>
<button @click="handle">点击</button>
</div>
</div>
`,
methods: {
handle: function(){
hub.$emit('tom-event', 5)
}
},
mounted: function(){
hub.$on('jerry-event', (val) => {
this.num += val
})
}
})

var vm = new Vue({
el: '#app',
data: {},
methods: {}
})
</script>
<script type="text/plain">
兄弟组件传值借助 事件中心 (Vue实例)来监听事件
1. 提供事件中心 var hub = new Vue()
2. 加载完模板之后 即 通过 mounted(){} 钩子函数(模板加载完毕) 设置监听器 hub.$on(方法名,函数)
3. 组件内设置方法,方法内部 使用 hub 事件中心 触发事件 hub.$emit(方法名, 传递的数据)
</script>

组件插槽

匿名插槽

父组件向子组件传递模板,通过子组件在自身模板中使用 slot 标签获取父组件传入的内容

子组件模板中 slot 标签填入内容的话,当父子间没有传入内容的时候,默认使用子组件内的内容

<div id="app">
<menu-item>{{msg}}</menu-item>
<menu-item></menu-item>
<menu-item>html</menu-item>

</div>
<script src="js/vue.js"></script>
<script>
Vue.component('menu-item', {
template: `
<div>
<strong>ERROR!</strong>
<slot>hello</slot>
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
msg: '你好'
},
methods: {

}
})
</script>

具名插槽

<div id="app">
<base-layout>
<p slot="header"></p>
<p ></p>
<p slot="footer"></p>
<p slot="para1">段落</p>
</base-layout>

<!-- 当多个标签都要渲染到某个模板位置的时候,可以使用 template 标签做暂时包裹,该标签不会渲染到页面上的 -->
<base-layout>
<template slot="footer">
<p>footer</p>
<p>footer</p>
<p>footer</p>
<p>footer</p>
</template>
<template slot="header">
<p>header</p>
<p>header</p>
<p>header</p>
<p>header</p>
</template>
<template>
<p>000</p>
<p>000</p>
<p>000</p>
<p>000</p>
</template>
</base-layout>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component('base-layout', {
template: `
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
<slot name="para1"></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
})
var vm = new Vue({
el: '#app',

})
</script>
<script type="text/plain">
1. 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
2. 具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
</script>