从 0 到 1 实现一个框架BootstrapVue: Lesson 3

69 阅读2分钟

从 0 到 1 实现一个框架BootstrapVue: Lesson 3

如何实现一个<BAlert />

1.首先看最核心的 render function

这里 h 的第一项参数是 Component 类型, 用的是BVTransition组件, 给需要过渡效果的组件做 wrapper.

这里 h 的第二项参数是要传递的属性, 这里只有一个noFade prop, 用于控制是否需要 fade 过渡效果.

这里 h 的第三项参数是本节点里面的 children, 这里是$alert, 是一个 VNode.

return h(BVTransition, { props: { noFade: !this.fade } }, $alert);

$alert

1.这里 h 的第一项参数是 String 类型, 用的是 div.

2.这里 h 的第二项参数是要传递的属性.

staticClass 和 class 都是表示渲染元素上的 class, 用于控制样式.

这里有两个参数, dismissible 和 variant. dismissible 用于控制这个 alert 是否能关闭, variant 控制 alert 的颜色主题.

attrs 主要是给 aria 无障碍技术用的.

key 用来表示这个组件的独特性

3.这里 h 的第三项参数是本节点里面的 children. 这里使用了 $dismissButtonnormalizeSlot. $dismissButton 表示关闭 alert 的按钮

$alert = h(
  "div",
  {
    staticClass: "alert",
    class: {
      "alert-dismissible": dismissible,
      [`alert-${variant}`]: !!variant,
    },
    attrs: {
      role: "alert",
      "aria-live": "polite",
      "aria-atomic": true,
    },
    key: this[COMPONENT_UID_KEY],
  },
  [$dismissButton, this.normalizeSlot()]
);

$dismissButton

1.这里 h 的第一项参数是 Component 类型, 用的是BButtonClose组件, 表示关闭按钮.

2.这里 h 的第二项参数是要传递的属性, 这里 attrs 主要是给 aria 无障碍技术用的. on 用于监听 click 事件, 执行 dismiss 操作(关闭).

3.这里 h 的第三项参数是本节点里面的 children, 这里是 dismiss slot, 是一个 named slot. 运行时可以将内容动态插入里面.

$dismissButton = h(
  BButtonClose,
  {
    attrs: { "aria-label": this.dismissLabel },
    on: { click: this.dismiss },
  },
  [this.normalizeSlot("dismiss")]
);

BButtonClose

我们这里顺带来看一下BButtonClose这个组件.

1.这里 h 的第一项参数是 String 类型, 用的是'button'. 表示 button 元素.

2.这里 h 的第二项参数是要传递的属性, 使用了 mergeData 进行合并.

3.这里 h 的第三项参数是本节点里面的 children, 使用了 normalizeSlot 进行合并.

return h(
  "button",
  mergeData(data, componentData),
  normalizeSlot(SLOT_NAME_DEFAULT, {}, $scopedSlots, $slots)
);

BButtonClose的 props 主要是这几个.

content 表示里面的内容, 默认是'x'. disabled 表示 button 是否可用. textVariant 表示文字的颜色. undefined 时会继承上层元素的颜色.

content: {
  type: String,
  default: '&times;'
},
disabled: {
  type: Boolean,
  default: false
},
ariaLabel: {
  type: String,
  default: 'Close'
},
textVariant: {
  type: String
  // `textVariant` is `undefined` to inherit the current text color
  // default: undefined
}

2.看一下 mixins

这里使用了两个 mixins. mixins 的作用是复用代码.

mixins: [modelMixin, normalizeSlotMixin],

modelMixin 是用于 v-model 双向绑定的. 在 vue3 里面是绑定 modelValue 和'update:modelValue'.

这里 makeModelMixin 的参数是 prop 的名称. 然后 object 里面解构出来的 EVENT_NAME_UPDATE_SHOW, 可用于组件的 emit.

const { mixin: modelMixin, event: EVENT_NAME_UPDATE_SHOW } = makeModelMixin(
  PROP_NAME_SHOW
);

3.过一遍 props

这里的 PROP_NAME_SHOW 是'show'. 当为 Boolean 时, 控制 alert 的显示. 当为 Number 时, 表示 alert 要在这个倒计时结束后关闭.

variant, dismissible, dismissLabel, fade 前面讲过.

[PROP_NAME_SHOW]: {
  type: [Boolean, Number, String],
  default: false
},
variant: {
  type: String,
  default: 'info'
},
dismissible: {
  type: Boolean,
  default: false
},
dismissLabel: {
  type: String,
  default: 'Close'
},
fade: {
  type: Boolean,
  default: false
}

4.看两个关键的 helper function

parseCountDown 是将 show 转化成对应的数字. 空字符串或者是 bool 类型时, 返回 0, 否则返回数字本身.

// Convert `show` value to a number
const parseCountDown = (show) => {
  if (show === "" || isBoolean(show)) {
    return 0;
  }
  show = toInteger(show, 0);
  return show > 0 ? show : 0;
};

parseShow 是将 show 转化成对应的 bool. 空字符串或者是 bool 类型时, 返回 true. 如果为数字, 大于等于 1 时才返回 true, 否则返回 false.

// Convert `show` value to a boolean
const parseShow = (show) => {
  if (show === "" || show === true) {
    return true;
  }
  if (toInteger(show, 0) < 1) {
    // Boolean will always return false for the above comparison
    return false;
  }
  return !!show;
};

5.看 emits

EVENT_NAME_DISMISSED 表示 'dismissed', 即 alert 关闭.

EVENT_NAME_DISMISS_COUNT_DOWN 表示 'dismiss-count-down', 每次倒计时计数都会触发一次.

emits: [EVENT_NAME_DISMISSED, EVENT_NAME_DISMISS_COUNT_DOWN],

现在看一下倒计时的逻辑. countDown 在 watch option 里面. 表示每次 countDown 变化, 都会执行下列的函数.

逻辑:

  1. 先清除上次的倒计时.
  2. 判断 show 是否为数字, emit 'dismiss-count-down', 表示倒计时计数一次.
  3. 判断是否大于 0, 如果小于等于 0, 则关闭 alert, 否则倒计时减 1.
countDown(newValue) {
  this.clearCountDownInterval()
  const show = this[PROP_NAME_SHOW]
  if (isNumeric(show)) {
    // Ignore if this.show transitions to a boolean value.
    this.$emit(EVENT_NAME_DISMISS_COUNT_DOWN, newValue)
    if (show !== newValue) {
      // Update the v-model if needed
      this.$emit(EVENT_NAME_UPDATE_SHOW, newValue)
    }
    if (newValue > 0) {
      this.localShow = true
      this.$_countDownTimeout = setTimeout(() => {
        this.countDown--
      }, 1000)
    } else {
      // Slightly delay the hide to allow any UI updates
      this.$nextTick(() => {
        requestAF(() => {
          this.localShow = false
        })
      })
    }
  }
},

6.技术总结

  1. 使用构造函数, 构造出节点

这里用到了BVTransition充当 wrapper 组件

BVTransition -> alert>alert -> dismissButton + slot

$dismissButton -> BButtonClose -> slot

return h(BVTransition, { props: { noFade: !this.fade } }, $alert);
  1. 使用 Mixins, 减少重复代码

modelMixin 提供了对 alert 上 show 的控制

mixins: [modelMixin, normalizeSlotMixin];
  1. 事件处理

实现了关闭事件, 和倒计时计数事件. 从而可以在 alert 组件上监听这两个事件, 做对应的处理.

emits: [EVENT_NAME_DISMISSED, EVENT_NAME_DISMISS_COUNT_DOWN],