记录一次vue2函数式组件开发+单粒模式

前言

最近遇到个需求,需要一个类似于钉钉头部的菜单栏,因为钉钉的头部可以通过函数来渲染需要组件的样式,以及回调函数。所以,第一眼想到了,用单粒模式来模仿这个。那么我们就需要一个全局组件

组件编写过程

首先我们编写一个基础的 vue 组件,来作为基础的布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<template>
<div class="header-container-wrapper">
<plugin :render="renderLeftFunc" v-show="this.renderLeft"></plugin>

<div class="path-nav-name">{{ navPath }}</div>

<plugin :render="renderRightFunc" v-show="this.renderRight"></plugin>
</div>
</template>

<script>
import plugin from "./render.js";

export default {
components: {
plugin,
},

computed: {
renderLeftFunc() {
return this.renderLeft || function () {};
},

renderRightFunc() {
return this.renderRight || function () {};
},
},

data() {
//header.js中data的数据定义一样,这里只是方便观察修改组件

return {
navPath: "",

showLeftContent: false,

showRightContent: false,

renderLeft: null,

renderRight: null,

pluginName: "",
};
},
};
</script>

<style scoped lang="scss">
.header-container-wrapper {
display: flex;

align-items: center;

justify-content: space-around;

height: 60px;

.left-container,
.right-container {
padding: 16px 24px;

height: 100%;
}

.path-nav-name {
font-weight: 700;

font-size: 22px;

color: #333333;

margin-left: auto;

flex: 1 1 auto;

text-align: center;
}
}
</style>

为了能在函数中使用 render 语法渲染组件,这里封装一个 render.js 来渲染组件

1
2
3
4
5
6
7
8
9
10
11
export default {
//vue2的render示例
functional: true,
name: "plugin",
props: {
render: Function,
},
render: (h, ctx) => {
return ctx.props.render(h);
},
};

这个时候我们通过 vue.extend 这个操作语句来生产一个组件对象。并且我们可以在这个对象上操作前面的基础组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import Vue from "vue";
import Header from "./index.vue";

const Component = Vue.extend(Header);
let instance = null;
function HeaderBox(tarEl, options) {
if (!instance) {
instance = new Component({
el: document.createElement("div"),
data() {
return options;
},
});
//将头部元素插入到第一位
//返回生成的元素,然后把它挂载到需要挂载到dom元素上
tarEl.insertBefore(instance.$el, tarEl.firstChild);
} else {
// 存在实例,则合并options,更新视图
Object.assign(instance, options);
}
}
//添加返回按钮
HeaderBox.addLeftBtn = (renderObj, callback) => {
if (instance) {
instance.showLeftContent = true;
instance.renderLeft = () => {
let h = instance.$createElement;
return h(
"div",
{
class: "left-container",
on: {
click: callback,
},
},
[
h("Icon", {
props: {
type: "ios-arrow-back",
},
style: "font-size:24px;",
}),
h(
"span",
{
style: "font-size:18px;",
},
"返回"
),
]
);
};
}
};
//移除返回按钮
HeaderBox.removeLeftBtn = () => {
if (instance) {
instance.renderLeft = null;
}
};

HeaderBox.addTopTitle = (title) => {
if (instance) {
instance.navPath = title;
}
};
HeaderBox.removeTopTitle = () => {
if (instance) {
instance.navPath = "";
}
};

export default HeaderBox;

上面的代码示例中,我们通过 instance 对象可以操控到基础组件里面的 data 定义的参数。通过操作 data 里面的参数,传递render语法来控制渲染组件。其中 h 由于没有自动注入,所以直接使用时 undefined,为了让他能正常工作,参考官网给的代码: const h = this.createElement;我们通过instance对象上的$createElement来赋值 h。

最后我们通过在 main.js 中全局注册

1
2
3
import HeaderBox from "@/components/header/header.js";

Vue.prototype.$HeaderBox = HeaderBox;

这样的话任何页面都能通过 this.$HeaderBox.addTopTitle(“测试表头”);这样来生成自己的样式,也可以通过扩展 header.js 里面的 HeaderBox 对象的属性来扩充基础的组件。

使用案例如下代码

1
2
3
4
5
6
7
8
9
10
//vue2 中的操作
mounted(){
// 第一次使用初始化,就是你要挂载到哪里到节点下
this.$HeaderBox(this.$refs.rightHeader);
this.$nextTick(() => {
this.$HeaderBox.addLeftBtn({}, () => {
this.$router.go(-1);
});
});
}