原文鏈接:https://blog.csdn.net/qq_33988065/article/details/85124428
什么叫做組件化
vue.js 有兩大法寶固额,一個是數(shù)據(jù)驅(qū)動费就,另一個就是組件化祝辣,那么問題來了嘉竟,什么叫做組件化二蓝,為什么要組件化?接下來我就針對這兩個問題一一解答惩猫,所謂組件化霉颠,就是把頁面拆分成多個組件,每個組件依賴的 CSS事期、JS滥壕、模板、圖片等資源放在一起開發(fā)和維護兽泣。 因為組件是資源獨立的绎橘,所以組件在系統(tǒng)內(nèi)部可復(fù)用,組件和組件之間可以嵌套唠倦,如果項目比較復(fù)雜金踪,可以極大簡化代碼量,并且對后期的需求變更和維護也更加友好牵敷。
如何進行組件化開發(fā)
先看下圖:
這是 vue.js 中的一個報錯胡岔,原因是使用了一個未經(jīng)注冊的組件 lx-xxx
,這個報錯告訴我們一個道理:使用自定義組件之前必須注冊枷餐。
那么如何注冊一個組件呢靶瘸? Vue.js 提供了 2 種組件的注冊方式,全局注冊和局部注冊毛肋。
- 全局注冊
在 vue.js 中我們可以使用 Vue.component(tagName, options) 進行全局注冊怨咪,例如
Vue.component('my-component', {
// 選項
})
- 局部注冊
Vue.js 也同樣支持局部注冊,我們可以在一個組件內(nèi)部使用 components 選項做組件的局部注冊润匙,例如:
import HelloWorld from './components/HelloWorld'
export default {
components: {
HelloWorld
}
}
區(qū)別:全局組件是掛載在 Vue.options.components 下诗眨,而局部組件是掛載在 vm.$options.components 下,這也是全局注冊的組件能被任意使用的原因孕讳。
組件化開發(fā)必備知識
所謂工欲善其事匠楚,必先利其器,在正式開發(fā)一個組件之前厂财,我們先要掌握一些必備的知識芋簿,這里我只會簡單介紹一下,詳情參考官網(wǎng)璃饱。
name
組件的名稱与斤,必填
<lx-niu/>
<lx-niu></lx-niu/>
name: 'lxNiu'
js 中使用駝峰式命令,HTML 使用kebab-case命名。
props
組件屬性撩穿,用于父子組件通信磷支,可通過this.msg訪問
<div>{{msg}}</div>
props: {
msg: {
type: String,
default: ''
}
}
show: Boolean // 默認false
msg: [String, Boolean] // 多種類型
computed
處理data或者props中的屬性,并返回一個新屬性
<div>{{newMsg}}</div>
computed: {
newMsg() {
return 'hello ' + this.msg
}
},
注:因為props食寡,data和computed在編譯階段都會作為vm的屬性合并齐唆,所以不可重名
render
用render函數(shù)描述template
<lx-niu tag='button'>hello world</lx-niu>
<script type="text/javascript">
export default {
name: 'lxNiu',
props: {
tag: {
type: String,
default: 'div'
},
},
// h: createElement
render(h) {
return h(this.tag,
{class: 'demo'},
this.$slots.default)
}
}
</script>
render 中的 h 其實就是 createElement,它接受三個參數(shù)冻河,返回一個 vnode
h 參數(shù)解釋:
args1: {string | Function | Object} 用于提供DOM的html內(nèi)容
args2: {Object} 設(shè)置DOM樣式箍邮、屬性、綁定事件之類
args3: {array} 用于設(shè)置分發(fā)的內(nèi)容
注:vue編譯順序: template–> compile --> render --> vnode --> patch --> DOM
slot
分發(fā)內(nèi)容叨叙,有傳入時顯示锭弊,無傳入 DOM 時顯示默認,分為無名和具名兩種擂错,this. solts.default默 認 指 向 無 名 插 槽 味滞, 多 個 s l o t 時 用 法 this. solts.default默認指向無名插槽,多個slot時用法this.slots.name
<lx-niu>
<div slot='header'>header</div>
<div class="body" slot='body'>
<input type="text">
</div>
<div slot='footer'>footer</div>
<button class='btn'>button</button>
</lx-niu>
<template>
<div>
<slot name='header'></slot>
<slot name='body'></slot>
<slot name='footer'></slot>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'lxNiu',
mounted() {
this.$slots.header // 包含了slot="foo"的內(nèi)容
this.$slots.default // 得到一個vnode钮呀,沒有被包含在具名插槽中的節(jié)點剑鞍,這里是button
}
}
</script>
class
定義子組件的類名
// 父組件
<lx-niu round type='big'/>
// 子組件
<div :class="[
type ? 'lx-niu__' + type : '',
{'is-round': round},
]">控制</div>
//真實DOM
<div class='lx-niu__big is-round'>hello</div>
其他屬性
$attrs
v-bind="$attrs" 將除class和style外的屬性添加到父組件上,如定義input:
<input v-bind="$attrs">
v-once
組件只渲染一次爽醋,后面即使數(shù)據(jù)發(fā)生變化也不會重新渲染蚁署,比如例子中val不會變成456
<template>
<div>
<button @click="show = !show">button</button>
<button @click="val = '456'">button</button>
<div v-once v-if="show">
<span>{{val}}</span>
</div>
</div>
</template>
<script>
export default {
data() {
return{
show: false,
val: '123'
}
},
};
</script>
mixins
// mixin.js
export default {
data() {
return{
msg: 'hello world'
}
},
methods: {
clickBtn() {
console.log(this.msg)
}
},
}
// index.vue
<button @click="clickBtn">button</button>
import actionMixin from "./mixin.js";
export default {
methods: {},
mixins: [actionMixin]
}
實例演示
比如我們要注冊一個 lx-button 這樣一個組件,那么目錄和偽代碼如下:
index.vue
<template>
<button>lxButton</button>
</template>
<script>
export default {
name: 'lxButton'
}
</script>
index.js
import lxButton from './src/index'
lxButton.install = (Vue) => {
Vue.component(lxButton.name, lxButton)
}
export default lxButton
其中 install 是 Vue.js 提供了一個公開方法蚂四,這個方法的第一個參數(shù)是 Vue 構(gòu)造器光戈,第二個參數(shù)是一個可選的選項對象筐带。
MyPlugin.install = function (Vue, options){}
watch-彈窗實現(xiàn)原理
<button @click="dialogVisible = true">顯示</button>
<lx-niu :visible.sync="dialogVisible"></lx-niu>
<script>
export default {
data() {
return {
dialogVisible: false
}
},
watch: {
dialogVisible(val) {
console.log('father change', val)
}
}
}
</script>
定義組件
<template>
<div v-show="visible">
<button @click="hide">關(guān)閉</button>
</div>
</template>
<script>
export default {
name: 'lxNiu',
props: {
visible: Boolean
},
watch: {
visible(val) {
console.log('child change:', val)
}
},
methods: {
hide() {
this.$emit('update:visible', false);
}
},
}
</script>
點擊父組件中的顯示按鈕暇仲,改變傳入子組件中的值,點擊子組件中的關(guān)閉桂敛,改變父組件中值跷睦。
注:@click=“dialogVisible = true” 點擊時將dialogVisible的值改為true
注::visible.sync: 雙向數(shù)據(jù)綁定筷弦,配合update:visible使用,實現(xiàn)子組件修改父組件中的值
col組件實例
export default {
name: 'ElCol',
props: {
span: {
type: Number,
default: 24
},
tag: {
type: String,
default: 'div'
},
offset: Number,
pull: Number,
push: Number,
xs: [Number, Object],
sm: [Number, Object],
md: [Number, Object],
lg: [Number, Object],
xl: [Number, Object]
},
computed: {
gutter() {
let parent = this.$parent;
while (parent && parent.$options.componentName !== 'ElRow') {
parent = parent.$parent;
}
return parent ? parent.gutter : 0;
}
},
render(h) {
let classList = [];
let style = {};
if (this.gutter) {
style.paddingLeft = this.gutter / 2 + 'px';
style.paddingRight = style.paddingLeft;
}
['span', 'offset', 'pull', 'push'].forEach(prop => {
if (this[prop] || this[prop] === 0) {
classList.push(
prop !== 'span'
? `el-col-${prop}-${this[prop]}`
: `el-col-${this[prop]}`
);
}
});
['xs', 'sm', 'md', 'lg', 'xl'].forEach(size => {
if (typeof this[size] === 'number') {
classList.push(`el-col-${size}-${this[size]}`);
} else if (typeof this[size] === 'object') {
let props = this[size];
Object.keys(props).forEach(prop => {
classList.push(
prop !== 'span'
? `el-col-${size}-${prop}-${props[prop]}`
: `el-col-${size}-${props[prop]}`
);
});
}
});
return h(this.tag, {
class: ['el-col', classList],
style
}, this.$slots.default);
}
};
col組件使用render函數(shù)抑诸,而不是template來實現(xiàn)組件烂琴,原因有兩個:
1.該組件有大量的類判斷,如果采用template代碼比較冗余哼鬓,使用js代碼更加簡潔
2.直接render描述性能更好
button組件實例
<template>
<button
class="el-button"
@click="handleClick"
:disabled="buttonDisabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': buttonDisabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle
}
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
export default {
name: 'ElButton',
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
props: {
type: {
type: String,
default: 'default'
},
size: String,
icon: {
type: String,
default: ''
},
nativeType: {
type: String,
default: 'button'
},
loading: Boolean,
disabled: Boolean,
plain: Boolean,
autofocus: Boolean,
round: Boolean,
circle: Boolean
},
computed: {
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
buttonSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
buttonDisabled() {
return this.disabled || (this.elForm || {}).disabled;
}
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
};
</script>
icon組件
1.組件封裝
<template>
<svg
:class="getClassName"
:width="width"
:height="height"
aria-hidden="true">
<use :xlink:href="getName"></use>
</svg>
</template>
<script>
export default {
name: 'icon-svg',
props: {
name: {
type: String,
required: true
},
className: {
type: String
},
width: {
type: String
},
height: {
type: String
}
},
computed: {
getName () {
return `#icon-${this.name}`
},
getClassName () {
return [
'icon-svg',
`icon-svg__${this.name}`,
this.className && /\S/.test(this.className) ? `${this.className}` : ''
]
}
}
}
</script>
<style>
.icon-svg {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
</style>
2.全局注冊使用
svg文件夾下是靜態(tài)svg圖片
/**
* 字體圖標, 統(tǒng)一使用SVG Sprite矢量圖標(http://www.iconfont.cn/)
*
* 使用:
* 1. 在阿里矢量圖標站創(chuàng)建一個項目, 并添加圖標(這一步非必須, 創(chuàng)建方便項目圖標管理)
* 2-1. 添加icon, 選中新增的icon圖標, 復(fù)制代碼 -> 下載 -> SVG下載 -> 粘貼代碼(重命名)
* 2-2. 添加icons, 下載圖標庫對應(yīng)[iconfont.js]文件, 替換項目[./iconfont.js]文件
* 3. 組件模版中使用 [<icon-svg name="canyin"></icon-svg>]
*
* 注意:
* 1. 通過2-2 添加icons, getNameList方法無法返回對應(yīng)數(shù)據(jù)
*/
import Vue from 'vue'
import IconSvg from '@/components/icon-svg'
import './iconfont.js'
//全局注入
Vue.component('IconSvg', IconSvg)
const svgFiles = require.context('./svg', true, /\.svg$/)
const iconList = svgFiles.keys().map(item => svgFiles(item))
export default {
// 獲取圖標icon-(*).svg名稱列表, 例如[shouye, xitong, zhedie, ...]
getNameList () {
return iconList.map(item => item.default.id.replace('icon-', ''))
}
}
在main.js 中引入
3.在頁面中使用
name的值监右,為svg下面的靜態(tài)svg圖片名稱
<icon-svg name="zhedie"></icon-svg>