组件介绍
Vue 的核心概念之一,将需求划分为多个小模块,小模块通过组件来实现,各个组件之间耦合度低,复用性高,团队成员分配任务进行各个组件编写,其他开发人员只要知道当前组件需要的入参,即可使用。
全局组件
通过 Vue.component 注册全局组件,传入 template、data、methods 等属性,配置组件名称,即可通过组件名称使用组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 全局注册组件 -->
<component-one></component-one>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 全局注册组件
Vue.component('component-one', {
template: `<div id="componentOneTpl">
<span>{{text}}</span>
</div>`,
data: function () {
return {
text: 'componentOneText'
}
}
});
var app = new Vue({
el: '#app',
data: {},
methods: {
}
});
</script>
</body>
</html>
父子组件传值
-
父组件传值给子组件 – 父组件属性绑定传值,子组件 props 接收
<div id="app"> <component-son :father-msg="msg"></component-son> <!-- 子节点接受到了父节点的数据:father message --> </div>
// 子组件 Vue.component('component-son', { template: `<div id="componentOneTpl"> <span>子节点接受到了父节点的数据:{{fatherMsg}}</span> </div>`, //props: ['fatherMsg'], props: { "fatherMsg": { type: String } }, })
-
子组件传值给父组件 – 子组件 $emit 传值, 父组件 方法绑定,形参接收
<div id="app"> {{ sonMsg }} <!-- i am son message !!! --> <component-son @send="accectSonMsg"></component-son> </div>
// 子组件 Vue.component('component-son', { template: `<div id="componentOneTpl"> <button @click="send()">发送给父节点数据</button> </div>`, data: function () { return { sonMsg: 'i am son message !!!' } }, methods: { send() { this.$emit('send', this.sonMsg); } } }) var app = new Vue({ el: '#app', data: { sonMsg: '', }, methods: { accectSonMsg(data) { console.log(data); this.sonMsg = data; } } });
bus 总线进行传值
bus 总线,类似于 angularjs 里面的 $rootScope ,任一处触发 $emit() ,所有 $on 都会响应;
- var Bus = new Vue(); 将 Bus 挂载到 根实例的 data 属性上;
- 采用 this.$root.Bus.$emit() 进行触发;
- 采用 this.$root.Bus.$on() 进行接收;
<div id="app">
<!-- bus 总线 -->
父组件数据:{{message}}
<component-bus></component-bus>
<component-bus2></component-bus2>
</div>
// bus
Vue.component('component-bus', {
template: `<div>
<button @click="emitBus()">触发bus事件</button>
</div>`,
data: function () {
return {
text: 'bus 触发的数据'
}
},
methods: {
emitBus() {
this.$root.Bus.$emit('on-message', this.text);
}
}
})
// bus 2
Vue.component('component-bus2', {
template: `<div>平行组件数据监听:{{text}}</div>`,
data: function () {
return {
text: 'bus2'
}
},
mounted() {
var _this = this;
this.$root.Bus.$on('on-message', function (msg) {
_this.text = msg;
})
}
})
var Bus = new Vue();
var app = new Vue({
el: '#app',
data: {
Bus,
message: '采用bus总线进行监听',
},
mounted() {
var _this = this;
this.$root.Bus.$on('on-message', function (msg) {
_this.message = msg;
})
}
});
v-model 在组件中的使用
组件通过 props: [‘value’] 接收值;
通过 this.$emit(‘input’, event.target.value) 发送值;
<div id="app">
<div>v-model 父组件:{{modelInputText}}</div>
<component-model v-model="modelInputText"></component-model>
</div>
Vue.component('component-model', {
template: `<div><input type="text" :value="value" @input="updateVal" /><br /><span>v-model的子组件:{{value}}</span></div>`,
props: ['value'],
data: function () {
return {
text: 'componentOneText'
}
},
methods: {
updateVal(event) {
this.$emit('input', event.target.value);
}
}
})
var app = new Vue({
el: '#app',
data: {
modelInputText: 'hello v-model !',
},
});
组件高级用法 slot、 name
slot : 插槽,将引用组件内容放进插槽中;
name: 给组件命名,递归组件使用;
<div id="app">
<component-high :count="1">
<span>多个数据</span>
</component-high>
<!-- 结果展示:
多个数据
多个数据
多个数据
多个数据
多个数据
-->
</div>
Vue.component('component-high', {
name: 'component-high',
template: `<div><component-high v-if="count < 5" :count="count + 1"><slot></slot></component-high><slot></slot></div>`,
props: {
"count": {
type: Number,
default: 2
}
}
})
自定义树组件
转载树组件链接:https://blog.csdn.net/u014399368/article/details/87856322
内容简单易懂,很适合组件研究,学习使用。
先上效果图:
代码及注解如下,对应图片请自行截取,或通过原博客获取。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
h4 {
font-weight: normal;
color: #aaa;
margin-top: 20px;
line-height: 20px;
font-size: 14px;
}
.line {
margin: 20px 0;
}
#app {
padding: 20px;
}
.cus_vtree_wrap>ul:first-child {
background-image: none;
}
ul.cus_tree_ul {
padding-left: 16px;
background-image: url('./images/index.gif');
background-position: 4px 10px;
background-repeat: repeat-y;
}
li:last-child>ul.cus_tree_ul:last-child {
background-image: none;
}
ul.cus_tree_ul li {
text-align: left;
cursor: pointer;
list-style: none;
}
.cus_item_content span.treeExpandBtn {
display: inline-block;
width: 18px;
height: 18px;
margin-right: 6px;
background-image: url('./images/zTreeStandard.png');
margin: 0;
}
.cus_vtree_wrap>ul>li:first-child>.cus_item_content>span.treeExpandBtn.butopen {
background-position: -92px 0px
}
li .cus_item_content span.treeExpandBtn.butopen {
background-position: -92px -18px
}
li:last-child .cus_item_content span.treeExpandBtn.butopen {
background-position: -92px -36px
}
.cus_vtree_wrap ul.tree_root_lonely>li>.cus_item_content>span.treeExpandBtn.butopen {
background-position: -92px -54px
}
.cus_vtree_wrap>ul>li:first-child>.cus_item_content>span.treeExpandBtn.btnclose {
background-position: -74px -0px
}
li .cus_item_content span.treeExpandBtn.btnclose {
background-position: -74px -18px
}
li:last-child .cus_item_content span.treeExpandBtn.btnclose {
background-position: -74px -36px
}
.cus_vtree_wrap ul.tree_root_lonely>li>.cus_item_content>span.treeExpandBtn.btnclose {
background-position: -74px -54px
}
.cus_item_content span.treeExpandBtn.lastLine {
background-position: -56px -36px
}
.cus_item_content span.treeExpandBtn.line {
background-position: -56px -18px
}
.cus_item_content .cus_chekcbox {
display: inline-block;
width: 14px;
height: 14px;
margin-right: 6px;
background-image: url('./images/zTreeStandard.png');
background-position: -0px -0px
}
.cus_item_content .cus_chekcbox.cus_chekcbox_checked {
background-position: -14px -0px
}
.cus_item_content .cus_chekcbox.cus_chekcbox_part_checked {
background-position: 0px -42px
}
ul.cus_tree_ul li .cus_item_content {
padding: 4px;
white-space: nowrap;
overflow-x: hidden;
}
ul.cus_tree_ul li .cus_item_content:hover {
background: #8f83e1;
color: white
}
ul.cus_tree_ul li .cus_item_content.active {
background: #7663f8;
color: white;
}
.folder {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
background-image: url('./images/zTreeStandard.png');
background-position: -110px -0px
}
.file {
display: inline-block;
width: 18px;
height: 18px;
margin-right: 6px;
background-image: url('./images/zTreeStandard.png');
background-position: -110px -30px
}
</style>
</head>
<body>
<div id="app">
<vue-tree :tree-data="treeData" :check-box="checkbox"></vue-tree>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('vue-tree', {
template: `
<div class='cus_vtree_wrap' @click.capture='clickNodeWrap'>
<v-tree-item :treeData='treeData' @clickNodeCom='clickNode' :checkBox='checkBox':class='{tree_root_lonely:treeData.length === 1}' @toggleCheckBox="checkBoxFun"></v-tree-item>
</div>
`,
name: 'vue-tree',
props: ['treeData', 'checkBox'],
data() {
return {
//userData:this.treeData
}
},
mounted() {
this.initData(true, true, true);
},
updated() {
this.initData(true, true, true);
},
methods: {
initData(expandInit, activeInit, checkedInit) {
var self = this;
var modifyDataFun = function (datas) {
if (datas) {
datas.forEach((m, index) => {
// 给数据添加展开、选中等
if (expandInit) {
// 注意对象赋值写法,便于双向数据绑定生效
self.$set(m, 'expand', true);
}
if (activeInit) {
self.$set(m, 'active', false);
}
if (checkedInit) {
self.$set(m, 'checked', false);
self.$set(m, 'partchecked', false);
}
if (index === datas.length - 1) {
self.$set(m, 'last', true);
}
if (m.children) {
// 递归调用创建
modifyDataFun(m.children);
}
});
}
}
// 接收外界传递的 tree 数据进行初始化
modifyDataFun(this.treeData);
},
clickNode(data) {
this.$emit('clickNode', data);
},
clickNodeWrap() {
this.initData(false, true, false);
},
checkBoxFun(item) {
},
getCheckedNodes() {
var resultArr = [];
var getCheckedNodesFun = (datas) => {
if (datas) {
datas.forEach((m) => {
if (m.checked === true) {
resultArr.push(m);
}
if (m.children) {
getCheckedNodesFun(m.children);
}
})
}
};
getCheckedNodesFun(this.treeData);
return resultArr;
},
getRoot() {
return this.treeData[0];
},
findNode(selectedId) {
var result;
var findNodeFun = (datas) => {
if (datas) {
try {
datas.forEach((m) => {
if (m.id === selectedId) {
result = m;
throw new Error('stop');
}
if (m.children) {
findNodeFun(m.children);
}
});
} catch (e) {
}
}
};
findNodeFun(this.treeData);
return result;
},
setSelectedNode(node, clickDiv) {
var select;
var that = this;
var setSelectedFun = (datas) => {
if (datas) {
datas.forEach((m) => {
if (m.id === node.id) {
m.active = true;
select = m;
if (clickDiv) {
var dom = document.getElementById(m.id);
if (dom && dom.className.indexOf('cus_item_content') > -1) {
dom.click();
} else {
var doms = document.getElementsByClassName(
'cus_item_content');
for (var y = 0; y < doms.length; y++) {
if (doms[y].id === m.id) {
doms[y].click();
break;
}
}
}
}
} else {
m.active = false;
}
if (m.children) {
setSelectedFun(m.children);
}
});
}
};
setSelectedFun(this.treeData);
return select;
},
getSelectedNode() {
var resultNode = null;
var getSelectedFun = (datas) => {
if (datas) {
datas.forEach((m) => {
if (m.active === true) {
resultNode = m;
return;
}
if (m.children) {
getSelectedFun(m.children)
}
})
}
};
getSelectedFun(this.treeData);
return resultNode;
},
getParentNode(node) {
var resultNode = null;
var getParentNode = (datas, parent) => {
if (datas) {
try {
datas.forEach((m) => {
if (node && m.id === node.id) {
resultNode = parent;
throw new Error("Stop");
}
if (!resultNode && m.children) {
getParentNode(m.children, m);
}
});
} catch (e) {
}
}
}
getParentNode(this.treeData, null);
return resultNode;
},
getParentNodesArr(node) {
var resultArr = [];
var parentNode;
var getParent = (target) => {
parentNode = this.getParentNode(target);
if (parentNode) {
resultArr.push(parentNode);
getParent(parentNode);
}
};
getParent(node);
return resultArr;
}
},
components: {
'v-tree-item': {
name: 'v-tree-item',
template: '<ul class="cus_tree_ul" :class="{cus_tree_ulLine:(treeData && treeData.length)}"><li v-for="item in treeData"><div class="cus_item_content" :title="item.text" @click="clickNode(item)" :id= item.id :class="{active:item.active}">' +
'<span class="treeExpandBtn" @click.stop="toggleNode(item)" :class="{butopen:item.expand && item.children,btnclose:!item.expand && item.children,line: !item.last && !item.children,lastLine:item.last&&!item.children}"></span><span class="tree-icon" :class="item.icon"></span>' +
'<span v-if="checkBox" @click="checkBoxClick(item)" class="cus_chekcbox" :class="{cus_chekcbox_checked:item.checked,cus_chekcbox_part_checked:item.partchecked}"></span>{{item.text}}</div>' +
'<v-tree-item :treeData="item.children" v-if="item.expand" @clickNodeCom="clickNodeCom" :checkBox="checkBox" :class="{cus_checkbox_allchecked:item.checked}" @toggleCheckBox="checkBoxFun(item)" ></v-tree-item> </li></ul>',
methods: {
clickNode(item) {
item.active = true;
this.$emit('clickNodeCom', item);
},
toggleNode(item) {
item.expand = !item.expand;
item.active = true;
},
clickNodeCom(data) {
this.$emit('clickNodeCom', data);
},
checkBoxClick(item) {
item.partchecked = false;
item.checked = !item.checked;
//设置子元素是否勾选
var checkChildFun = (childrenDatas) => {
childrenDatas.forEach((m) => {
m.checked = item.checked;
if (m.children) {
checkChildFun(m.children);
}
})
}
if (item.children) {
checkChildFun(item.children);
}
this.$emit('toggleCheckBox');
},
checkBoxFun(item) {
var checkedNum = 0;
var partCheckedNum = 0;
item.children.forEach((li) => {
if (li.checked === true) {
checkedNum++;
} else if (li.partchecked === true) {
partCheckedNum++;
}
})
if (checkedNum === item.children.length) { //全选
item.checked = true;
item.partchecked = false;
} else if (checkedNum > 0 || partCheckedNum > 0) { //部分勾选
item.checked = false;
item.partchecked = true;
} else { //没有勾选
item.checked = false;
item.partchecked = false;
}
this.$emit('toggleCheckBox')
},
},
props: ['treeData', 'checkBox']
}
}
})
var app = new Vue({
el: '#app',
data: {
treeData: [{
text: 'xiaoming',
id: '1',
icon: 'folder',
children: [{
text: '1-1',
id: '1-1',
icon: 'folder',
children: [{
text: '1-1-1',
id: '1-1-1',
icon: 'file',
},
{
text: '1-1-2',
id: '1-1-2',
icon: 'file'
}
]
},
{
text: '1-2',
id: '1-2',
icon: 'folder',
}
]
}],
checkbox: true
},
});
</script>
</body>
</html>
总结
- 组件部分主要包括:组件注册、组件之间传值,组件逻辑编写等;
- 组件 component 配合 路由 vue-router 即可实现组件切换,单页面应用程序;
- 简单组件传值通过父子组件传值、bus总线传值可满足需求,复杂逻辑组件传值需要使用 vuex ;
- 组件设计需要关注点:入参必须明确,扩展性必须强,逻辑代码注解说明,复杂代码抽离等;