Vue 组件开发中的小技巧
发表时间:2021-1-5
发布人:葵宇科技
浏览次数:40
日常开发中,我们会用到很多第三方组件库,学习组件开发最好的方法就是看这些组件库的源码,并从中学到一些小技巧
element-ui 大家基本都用过,总结一下组件库中 Tree 和 Collapse 用到的小技巧,下图为简易版实现演示效果
先来看下使用到的 API:
- provide/inject
- $emit
- $on
- 使用 provide/inject 将 祖先组件实例 作为依赖,注入到子孙组件中
- 因为子组件是采用 slot 方式插入,所以要使用 祖先组件实例 发起自定义事件
<mx-collapse v-model="activeNames" @change="handleChange" accordion>
<mx-collapse-item title="一致性 Consistency" name="1">
<div>
与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;
div>
<div>
在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。
div>
mx-collapse-item>
......
mx-collapse>
复制代码
- 将 父组件 实例作为依赖,注入到子组件中, 在子组件中使用父组件实例 发起自定义事件
- 父组件中使用 $on 监听 item-click 事件,并接收子组件传回来的数据做进一步处理
provide() {
return {
collapse: this,
};
},
created() {
// 自定义事件监听
this.$on('item-click', this.handleItemClick);
},
//...
复制代码
- 在子组件 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),
};
},
// 将当前组件实例作为依赖,用于注入到子组件中,
// 在子孙后代中可以使用祖先组件实例 发起自定义事件
provide() {
return {
collapse: this,
};
},
watch: {
value(value) {
this.activeNames = [].concat(value);
},
},
created() {
// 自定义事件监听
this.$on('item-click', this.handleItemClick);
},
methods: {
/**
* item-click 自定义事件处理
* 1. 手风琴模式下,展开的元素只有一个
* 2. 普通模式可以多个展开
*/
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);
}
},
/**
* 实时修改 activeNames 值
* 当 activeNames 数据变化时,触发组件自定义 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 属性,并将子数据元素作为递归的数据源传入
- tree.vue 完整代码
<script>
export default {
// 递归组件必须指定name
name: 'MxTree',
componentName: 'MxTree',
props: {
treeData: {
type: Object,
required: true,
},
},
data() {
return {
open: true,
};
},
computed: {
// 计算是否可以展开 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 插件
- 如果插件是一个对象,必须提供 install 方法
- 如果插件是一个函数,它会被作为 install 方法
- install 方法调用时,会将 Vue 作为参数传入
- 该方法需要在调用 new Vue() 之前被调用
- 当 install 方法被同一个插件多次调用,插件将只会被安装一次
- 每个组件都是独立的模块,目录结构如下
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 为入口文件,在其中定义 install 方法并将模块暴露
import MxTree from './src/tree';
MxTree.install = function(Vue) {
Vue.component(MxTree.name);
};
export default MxTree;
复制代码
- 根目录下的 index.js 为总入口文件,在其中将所有组件集中,定义 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 一样的方式,来使用自己的组件库
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>
与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;
div>
<div>
在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。
div>
mx-collapse-item>
<mx-collapse-item title="反馈 Feedback" name="2">
<div>
控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;
div>
<div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。div>
mx-collapse-item>
<mx-collapse-item title="效率 Efficiency" name="3">
<div>简化流程:设计简洁直观的操作流程;div>
<div>
清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;
div>
<div>
帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。
div>
mx-collapse-item>
<mx-collapse-item title="可控 Controllability" name="4">
<div>
用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;
div>
<div>
结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。
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 依赖注入,祖先与子孙之间的配合
- $emit $on 自定义事件,需注意 slot 方式子组件与父组件通讯的方式
- 递归组件 必须指定 name 选项
- Vue.use 配合 install 将自己的组件添加到 Vue 中
作者:__mxin
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。