从 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. 这里使用了 $dismissButton
和 normalizeSlot
. $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: '×'
},
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 变化, 都会执行下列的函数.
逻辑:
- 先清除上次的倒计时.
- 判断 show 是否为数字, emit 'dismiss-count-down', 表示倒计时计数一次.
- 判断是否大于 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.技术总结
- 使用构造函数, 构造出节点
这里用到了BVTransition
充当 wrapper 组件
BVTransition -> dismissButton + slot
$dismissButton -> BButtonClose -> slot
return h(BVTransition, { props: { noFade: !this.fade } }, $alert);
- 使用 Mixins, 减少重复代码
modelMixin 提供了对 alert 上 show 的控制
mixins: [modelMixin, normalizeSlotMixin];
- 事件处理
实现了关闭事件, 和倒计时计数事件. 从而可以在 alert 组件上监听这两个事件, 做对应的处理.
emits: [EVENT_NAME_DISMISSED, EVENT_NAME_DISMISS_COUNT_DOWN],