Skip to content
On this page

自定义指令集

点击元素外部

实现思路:

  在指令的 bind 的时候,拿到绑定的 DOM 节点,利用Node.contains,并为 document 绑定 click 事件,当点击的元素不是绑定的 DOM 的子集时,Node.contains返回 false,即点击了元素外部;

export default {
  bind: function (el, binding) {
    function documentHandler(e) {
      // node.contains可判断节点与指定节点是否存在包含关系
      if (el.contains(e.target)) return false;
      if (binding.expression) {
        binding.value(e);
      }
      el.__vueClickOutside__ = documentHandler;
      document.addEventListener("click", documentHandler);
    }
  },
  unbind: function (el) {
    // 及时销毁事件和无效引用,避免内存泄漏
    document.removeEventListener("click", el.__vueClickOutside__);
    delete el.__vueClickOutside__;
  },
};

锚点时间转换

实现思路:

  为节点绑定定时器,每秒计算锚点时间差值,并更新节点内容;

const Time = {
  // 获取当前时间戳
  getUnix: function () {
    var date = new Date();
    return date.getTime();
  },
  // 获取今天0点0分0秒的时间戳
  getTodayUnix: function () {
    var date = new Date();
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);
    return date.getTime();
  },
  // 获取今年1月1日0点0分0秒的时间戳
  getYearUnix: function () {
    var date = new Date();
    date.setMonth(0);
    date.setDate(1);
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);
    return date.getTime();
  },
  // 获取标准年月日
  getLastDate: function (time) {
    var date = new Date(time);
    var month =
      date.getMonth() + 1 < 10
        ? "0" + (date.getMonth() + 1)
        : date.getMonth() + 1;
    var day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
    return date.getFullYear() + "-" + month + "-" + day;
  },
  // 转换时间
  getFormatTime: function (timestamp) {
    var now = this.getUnix(); //当前时间戳
    var today = this.getTodayUnix(); //今天0点时间戳
    var year = this.getYearUnix(); //今年0点时间戳
    var timer = (now - timestamp) / 1000; // 转换为秒级时间戳
    var tip = "";

    if (timer <= 0) {
      tip = "刚刚";
    } else if (Math.floor(timer / 60) <= 0) {
      tip = "刚刚";
    } else if (timer < 3600) {
      tip = Math.floor(timer / 60) + "分钟前";
    } else if (timer >= 3600 && timestamp - today >= 0) {
      tip = Math.floor(timer / 3600) + "小时前";
    } else if (timer / 86400 <= 31) {
      tip = Math.ceil(timer / 86400) + "天前";
    } else {
      tip = this.getLastDate(timestamp);
    }
    return tip;
  },
};

export default {
  bind: function (el, binding) {
    el.innerHTML = Time.getFormatTime(binding.value * 1000);
    el.__timeout__ = setInterval(function () {
      el.innerHTML = Time.getFormatTime(binding.value * 1000);
    }, 60000);
  },
  unbind: function (el) {
    clearInterval(el.__timeout__);
    delete el.__timeout__;
  },
};

知识点

渲染函数

渲染函数

提到渲染函数就不由得想起 VNode,Vue 将渲染函数转换为 VNode,VNode 是一个对象,包含了节点名称、属性、子节点等信息,不管是单文件组件(SFC),还是说 JSX 组件、render 函数、函数组件,其最终的编译结果都是 VNode 数组(VNode 是虚拟 DOM 的组成单位)。

官网中介绍渲染函数的例子中,提到了动态组件,类似于内置的component组件(其实不是类似,component 组件就是组件式的渲染函数),根据这个动态组件实现方式,常见的一些表单、表格组件就可以使用这种方式来实现业务封装,如:

Vue.component("dynamicForm", {
  data() {
    return {
      formList: [
        { comp: "ElInput", props: { type: "text" } },
        {
          comp: "ElButton",
          props: { type: "primary" },
          on: { click: this.handleSubmit },
        },
      ],
    };
  },
  render(createElement) {
    return createElement(
      "div",
      {},
      this.formList.map((item) => {
        return createElement(item.comp, {
          props: item.props || {},
          on: item.on || {},
        });
      })
    );
  },
});

参考文档

Vue 官方教程

一个合格的中级前端工程师应该掌握的 20 个 Vue 技巧