前言

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

组件编写过程

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

<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 来渲染组件

```javascript
export default {
  //vue2的render示例
  functional: true,
  name: "plugin",
  props: {
    render: Function,
  },
  render: (h, ctx) => {
    return ctx.props.render(h);
  },
};

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

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 中全局注册

import HeaderBox from "@/components/header/header.js";

Vue.prototype.$HeaderBox = HeaderBox;

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

使用案例如下代码

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