前言 最近遇到个需求,需要一个类似于钉钉头部的菜单栏,因为钉钉的头部可以通过函数来渲染需要组件的样式,以及回调函数。所以,第一眼想到了,用单粒模式来模仿这个。那么我们就需要一个全局组件
组件编写过程 首先我们编写一个基础的 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 ( ) { 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 { 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; }, }); tarEl.insertBefore (instance.$el , tarEl.firstChild ); } else { 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 mounted ( ){ this .$HeaderBox(this .$refs .rightHeader ); this .$nextTick(() => { this .$HeaderBox .addLeftBtn ({}, () => { this .$router .go (-1 ); }); }); }