Vue 組件開發中的(de)小技巧
發表時(shí)間:2021-1-5
發布人(rén):融晨科技
浏覽次數:46
日常開發中,我們會用到(dào)很多第三方組件庫,學習組件開發最好的(de)方法就(jiù)是(shì)看這(zhè)些組件庫的(de)源碼,并從中學到(dào)一些小技巧
element-ui 大(dà)家基本都用過,總結一下組件庫中 Tree 和(hé / huò) Collapse 用到(dào)的(de)小技巧,下圖爲(wéi / wèi)簡易版實現演示效果
先來(lái)看下使用到(dào)的(de) API:
- provide/inject
- $emit
- $on
- 使用 provide/inject 将 祖先組件實例 作爲(wéi / wèi)依賴,注入到(dào)子(zǐ)孫組件中
- 因爲(wéi / wèi)子(zǐ)組件是(shì)采用 slot 方式插入,所以(yǐ)要(yào / yāo)使用 祖先組件實例 發起自定義事件
<mx-collapse v-model="activeNames" @change="handleChange" accordion>
<mx-collapse-item title="一緻性 Consistency" name="1">
<div>
與現實生活一緻:與現實生活的(de)流程、邏輯保持一緻,遵循用戶習慣的(de)語言和(hé / huò)概念;
div>
<div>
在(zài)界面中一緻:所有的(de)元素和(hé / huò)結構需保持一緻,比如:設計樣式、圖标和(hé / huò)文本、元素的(de)位置等。
div>
mx-collapse-item>
......
mx-collapse>
複制代碼
- 将 父組件 實例作爲(wéi / wèi)依賴,注入到(dào)子(zǐ)組件中, 在(zài)子(zǐ)組件中使用父組件實例 發起自定義事件
- 父組件中使用 $on 監聽 item-click 事件,并接收子(zǐ)組件傳回來(lái)的(de)數據做進一步處理
provide() {
return {
collapse: this,
};
},
created() {
// 自定義事件監聽
this.$on('item-click', this.handleItemClick);
},
//...
複制代碼
- 在(zài)子(zǐ)組件 collapse-item 中使用 父組件實例 觸發點擊事件,發送當前組件數據
inject: ['collapse'],
methods: {
// 使用父組件實例觸發自定義事件
handleHeaderClick() {
this.collapse.$emit('item-click', this);
},
},
//...
複制代碼
- collapse.vue 完整代碼
<div class="mx-collapse">
<slot>slot>
div>
<script>
export default {
name: 'MxCollapse',
componentName: 'MxCollapse',
props: {
accordion: Boolean,
value: {
type: [Array, String, Number],
default() {
return [];
},
},
},
data() {
return {
activeNames: [].concat(this.value),
};
},
// 将當前組件實例作爲(wéi / wèi)依賴,用于(yú)注入到(dào)子(zǐ)組件中,
// 在(zài)子(zǐ)孫後代中可以(yǐ)使用祖先組件實例 發起自定義事件
provide() {
return {
collapse: this,
};
},
watch: {
value(value) {
this.activeNames = [].concat(value);
},
},
created() {
// 自定義事件監聽
this.$on('item-click', this.handleItemClick);
},
methods: {
/**
* item-click 自定義事件處理
* 1. 手風琴模式下,展開的(de)元素隻有一個(gè)
* 2. 普通模式可以(yǐ)多個(gè)展開
*/
handleItemClick(item) {
const { name } = item;
// 手風琴模式
if (this.accordion) {
this.setActiveNames(
(this.activeNames[0] || this.activeNames[0] === 0) &&
this.activeNames[0] === name
? ''
: name
);
}
// 普通模式
else {
const activeNames = this.activeNames.slice(0);
const index = activeNames.indexOf(name);
if (index > -1) {
activeNames.splice(index, 1);
} else {
activeNames.push(name);
}
this.setActiveNames(activeNames);
}
},
/**
* 實時(shí)修改 activeNames 值
* 當 activeNames 數據變化時(shí),觸發組件自定義 change 事件
*/
setActiveNames(activeNames) {
activeNames = [].concat(activeNames);
this.activeNames = activeNames;
const value = http://www.wxapp-union.com/this.accordion ? activeNames[0] : activeNames;
this.$emit('change', value);
},
},
};
script>
<style lang="less" scoped>
.mx-collapse {
border-top: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
}
style>
複制代碼
- collapse-item.vue 完整代碼
<div class="mx-collapse-item">
<div class="mx-collapse-item__header" @click="handleHeaderClick">
<slot name="title">{{ title }}slot>
div>
<transition name="fade" mode="in-out">
<div class="mx-collapse-item__content" v-show="isActive">
<slot>slot>
div>
transition>
div>
<script>
export default {
name: 'MxCollapseItem',
componentName: 'MxCollapseItem',
data() {
return {};
},
inject: ['collapse'],
props: {
disabled: Boolean,
title: String,
name: {
type: [String, Number],
},
},
computed: {
isActive() {
return this.collapse.activeNames.indexOf(this.name) > -1;
},
},
methods: {
// 使用父組件實例觸發自定義事件,并将本組件數據回傳
handleHeaderClick() {
this.collapse.$emit('item-click', this);
},
},
};
script>
<style lang="less" scoped>
.mx-collapse-item {
font-size: 13px;
user-select: none;
&:last-child {
margin-bottom: -1px;
}
.mx-collapse-item__header {
height: 48px;
line-height: 48px;
color: #303133;
cursor: pointer;
border-bottom: 1px solid #ebeef5;
font-weight: bold;
outline: 0;
}
.mx-collapse-item__content {
padding: 25px 0;
color: #303133;
line-height: 1.769230769230769;
}
}
style>
複制代碼
Tree
組件循環引用,遞歸組件
- 使用遞歸組件,調用組件自身完成樹結構渲染
- 必須指定組件 name 屬性,并将子(zǐ)數據元素作爲(wéi / wèi)遞歸的(de)數據源傳入
- tree.vue 完整代碼
<script>
export default {
// 遞歸組件必須指定name
name: 'MxTree',
componentName: 'MxTree',
props: {
treeData: {
type: Object,
required: true,
},
},
data() {
return {
open: true,
};
},
computed: {
// 計算是(shì)否可以(yǐ)展開 or 收起
isFolder() {
return this.treeData.children && this.treeData.children.length;
},
},
methods: {
// 展開 or 收起
toggle() {
if (this.isFolder) {
this.open = !this.open;
}
},
},
};
script>
<style lang="less" scoped>
.mx-tree-label {
text-align: left;
font-size: 13px;
}
style>
複制代碼
Install
安裝 Vue.js 插件
- 如果插件是(shì)一個(gè)對象,必須提供 install 方法
- 如果插件是(shì)一個(gè)函數,它會被作爲(wéi / wèi) install 方法
- install 方法調用時(shí),會将 Vue 作爲(wéi / wèi)參數傳入
- 該方法需要(yào / yāo)在(zài)調用 new Vue() 之(zhī)前被調用
- 當 install 方法被同一個(gè)插件多次調用,插件将隻會被安裝一次
- 每個(gè)組件都是(shì)獨立的(de)模塊,目錄結構如下
mx
├── collapse
│ ├── index.js
│ └── src
│ └── collapse.vue
├── collapse-item
│ ├── index.js
│ └── src
│ └── collapse-item.vue
├── index.js
└── tree
├── index.js
└── src
└── tree.vue
複制代碼
- 組件文件夾中 index.js 爲(wéi / wèi)入口文件,在(zài)其中定義 install 方法并将模塊暴露
import MxTree from './src/tree';
MxTree.install = function(Vue) {
Vue.component(MxTree.name);
};
export default MxTree;
複制代碼
- 根目錄下的(de) index.js 爲(wéi / wèi)總入口文件,在(zài)其中将所有組件集中,定義 install 方法并将模塊暴露
import Collapse from './collapse/index';
import CollapseItem from './collapse-item/index';
import Tree from './tree/index';
const components = [Collapse, CollapseItem, Tree];
const install = function(Vue, options = {}) {
components.forEach(component => {
Vue.component(component.name, component);
});
};
export default {
install,
};
複制代碼
- 最後一步,像使用 element-ui 一樣的(de)方式,來(lái)使用自己的(de)組件庫
import Vue from 'vue';
import App from './App.vue';
import MxUI from './mx/index';
Vue.config.productionTip = false;
Vue.use(MxUI)
new Vue({
render: h => h(App),
}).$mount('#app');
複制代碼
- 入口頁面完整代碼
<div id="app">
<div>
<img class="logo" alt="Vue logo" src="./assets/logo.png" />
div>
<mx-collapse v-model="activeNames" @change="handleChange" accordion>
<mx-collapse-item title="一緻性 Consistency" name="1">
<div>
與現實生活一緻:與現實生活的(de)流程、邏輯保持一緻,遵循用戶習慣的(de)語言和(hé / huò)概念;
div>
<div>
在(zài)界面中一緻:所有的(de)元素和(hé / huò)結構需保持一緻,比如:設計樣式、圖标和(hé / huò)文本、元素的(de)位置等。
div>
mx-collapse-item>
<mx-collapse-item title="反饋 Feedback" name="2">
<div>
控制反饋:通過界面樣式和(hé / huò)交互動效讓用戶可以(yǐ)清晰的(de)感知自己的(de)操作;
div>
<div>頁面反饋:操作後,通過頁面元素的(de)變化清晰地(dì / de)展現當前狀态。div>
mx-collapse-item>
<mx-collapse-item title="效率 Efficiency" name="3">
<div>簡化流程:設計簡潔直觀的(de)操作流程;div>
<div>
清晰明确:語言表達清晰且表意明确,讓用戶快速理解進而(ér)作出(chū)決策;
div>
<div>
幫助用戶識别:界面簡單直白,讓用戶快速識别而(ér)非回憶,減少用戶記憶負擔。
div>
mx-collapse-item>
<mx-collapse-item title="可控 Controllability" name="4">
<div>
用戶決策:根據場景可給予用戶操作建議或安全提示,但不(bù)能代替用戶進行決策;
div>
<div>
結果可控:用戶可以(yǐ)自由的(de)進行操作,包括撤銷、回退和(hé / huò)終止當前操作等。
div>
mx-collapse-item>
mx-collapse>
<br />
<mx-tree :treeData="treeData" />
div>
<script>
export default {
name: 'App',
data() {
return {
activeNames: ['1'],
treeData: {
label: 'JavaScript',
children: [
{
label: '數據類型',
children: [
{
label: 'string',
},
{
label: 'number',
},
{
label: 'boolean',
},
{
label: 'null',
},
{
label: 'undefined',
},
{
label: 'symbol',
},
{
label: 'object',
},
],
},
{
label: '變量聲明',
children: [
{
label: 'var',
},
{
label: 'let',
},
{
label: 'const',
},
],
},
],
},
};
},
methods: {
handleChange(val) {
console.log(`activeNames: ${val}`);
},
},
};
script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin: 40px;
.logo {
width: 40px;
margin-bottom: 30px;
}
.mx-collapse {
float: right;
width: 70%;
height: 600px;
}
.mx-tree:first {
float: left;
width: 30%;
height: 600px;
}
.fade-enter-active {
transition: opacity 0.3s;
}
.fade-leave-active {
transition: opacity 0.1s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
}
style>
複制代碼
總結
- provide/inject 依賴注入,祖先與子(zǐ)孫之(zhī)間的(de)配合
- $emit $on 自定義事件,需注意 slot 方式子(zǐ)組件與父組件通訊的(de)方式
- 遞歸組件 必須指定 name 選項
- Vue.use 配合 install 将自己的(de)組件添加到(dào) Vue 中
作者:__mxin
來(lái)源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出(chū)處。