内置模块有以下几个
snabbdom这种实现解构了基础和上层模块能力,上层模块可以按照职责单一原则进行拆分,然后进行注册,通过钩子参与构建过程(怎么感觉和webpack基于tapable类似,是吧)
下面分别看看每个module都做些什么事情
modules-documentation
属性相关
attributes
介绍和使用
与 props 相同,但是是使用 attr 替代 prop。
代码语言:javascript复制h("a", { attrs: { href: "/foo" } }, "Go to Foo");
Attr 通过 setAttribute
实现添加及更新操作,对于已经添加过的属性,如果该属性不存在于 attrs
对象中那么将通过 removeAttribute
将其从 DOM 元素的 attribute 列表中移除。
对于布尔值属性(如:disabled
, hidden
,selected
...),这一类属性并不依赖于 Attr 的值(true
或 false
),而是取决于 DOM 元素本身是否存在该属性。模块对于这类属性的处理方式有些许不同,当一个布尔值属性被赋为 假值 (0
, -0
, null
, false
,NaN
, undefined
, or the empty string(""
)),那么该属性同样会直接从 DOM 元素的 attribute 列表中移除。
源码分析
代码语言:javascript复制export const attributesModule = {
create: updateAttrs,
update: updateAttrs,
};
代码语言:javascript复制function updateAttrs(oldVnode, vnode) {
// 逻辑很简单,对比新老vnode.data.attrs,然后通过setAttribute和removeAttribute设置和移除
const elm = vnode.elm;
let oldAttrs = oldVnode.data.attrs;
let attrs = vnode.data.attrs;
//...
// update modified attributes, add new attributes
for (key in attrs) {
const cur = attrs[key];
const old = oldAttrs[key];
//... 如果值不同 则调用setAttribute设置新值
}
// ... 调用removeAttribute删除oldAttrs上的属性
}
看到该模块关注cteate
和update
阶段
- cteate:createElm创建完DOM时会调用[cbs].create
- update:patchVnode时会调用[cbs].update(进入到patchVnode说明已经判断为sameVnode即真实DOM会被复用,因此此时触发更新钩子)
props
介绍和使用
该模块允许你设置 DOM 元素的属性。
代码语言:javascript复制h("a", { props: { href: "/foo" } }, "Go to Foo");
属性只能被设置不能被移除,即使浏览器允许自定义添加或删除属性,该模块也不会尝试删除。这是因为原生 DOM 的属性也同样不支持被移除,如果你是通过自定义属性来存储信息或者引用对象,那么请考虑使用 data-* attributes 代替,参考后面的data-set
模块。
源码分析
代码语言:javascript复制export const propsModule = { create: updateProps, update: updateProps };
代码语言:javascript复制function updateProps(oldVnode, vnode) {
//...
const elm = vnode.elm;
let oldProps = oldVnode.data.props;
let props = vnode.data.props;
//...
oldProps = oldProps || {};
props = props || {};
for (key in props) {
cur = props[key];
old = oldProps[key];
if (old !== cur && (key !== "value" || elm[key] !== cur)) {
elm[key] = cur;
}
}
}
小结
看到attributes和properties是两个东西,设置方式都不一样。attributes 和 properties 的区别: Difference HTML properties and attributes
结论:
- Attributes are defined by HTML. Properties are defined by the DOM (Document Object Model).
- Attributes initialize DOM properties and then they are done. Property values can change; attribute values can't. The HTML attribute
value
specifies the initial value; the DOMvalue
property is the current value. - The
disabled
attribute is another peculiar example. A button'sdisabled
property isfalse
by default so the button is enabled. When you add thedisabled
attribute, its presence alone initializes the button'sdisabled
property totrue
so the button is disabled. Adding and removing thedisabled
attribute disables and enables the button. The value of the attribute is irrelevant, which is why you cannot enable a button by writing<button disabled="false">Still Disabled</button>.
Setting the button'sdisabled
property disables or enables the button. The value of the property matters.
The HTML attribute and the DOM property are not the same thing, even when they have the same name.
样式相关
class
介绍和使用
class 模块提供了一种简单的方式来动态配置元素的 class 属性,这个模块值为一个对象形式的 class 数据,对象中类名需要映射为布尔值,以此来表示该类名是否应该出现在节点上。
代码语言:javascript复制h("a", { class: { active: true, selected: false } }, "Toggle");
源码分析
class.ts
代码语言:javascript复制export const classModule = { create: updateClass, update: updateClass };
function updateClass(oldVnode, vnode) {
//... 新老class 对比,调用 classList[add/remove]来修改
// 不细说了
}
style
介绍和使用
style 模块用于让动画更加平滑,它的核心是允许你在元素上设置 CSS 属性。 js
代码语言:javascript复制h(
"span",
{
style: {
border: "1px solid #bada55",
color: "#c0ffee",
fontWeight: "bold",
delayed: { 延迟样式 },
destroy: { 销毁样式 },
remove: { 移除样式 },
},
},
"Say my name, and every colour illuminates"
);
支持自定义属性设置即css变量
代码语言:javascript复制h(
"div",
{
style: { "--warnColor": "yellow" },
},
"Warning"
);
delayed、destroy、remove,参考
源码分析
style.ts
代码语言:javascript复制export const styleModule = {
pre: forceReflow,
create: updateStyle,
update: updateStyle,
destroy: applyDestroyStyle,
remove: applyRemoveStyle,
};
关键点:
- 一样是新老样式对比,移除老的,添加新的。在updateStyle方法上
- 对于自定义属性的设置或者移除有专用的api: elm.style.removeProperty、elm.style.setProperty;而普通样式直接 elm.style['xxx'] = 'xxx'
- 其他的逻辑都是为了支持delayed、destroy、remove三个属性的
- delayed:延迟样式设置:window.requestAnimationFrame.bind(window)) || setTimeout;
- destroy和remove:主要针对动画样式,使得元素有时间慢慢退出,而不是突然消失。其中remove的实现是会通过transitionend事件监听动画是否执行结束,而destroy样式是直接设置。看起来remove主要是针对动画场景,destroy并不是。
- 另外一个值得注意的点是:
applyRemoveStyle
会去 强制重排(reflow) 一次当前元素,这里涉及的两个提交如下:- Force page reflow before applying styles
- Fix element removal on transition end in Firefox70
- 所以另外的问题是为什么这里要在对oldVnode应用样式之前强制重绘❓❓❓
涉及的钩子:
- destroy钩子:调用
removeVnodes
会触发(patch时oldVnode不被复用时会调用(此时还未删除)) - remove钩子:如果提供了remove钩子,删除的动作交给开发者提供的钩子回调(会给回调传入一个删除节点的方法,直接调用即可),如果没有提供默认自动删除;
其他
dataset
HTMLElement - dataset的使用
介绍和使用
这个模块允许你在 DOM 元素上设置自定义 data 属性,然后通过 HTMLElement.dataset 来访问这些属性。
代码语言:javascript复制h("button", { dataset: { action: "reset" } }, "Reset");
源码分析
data-set.ts
代码语言:javascript复制export const datasetModule: Module = {
create: updateDataset,
update: updateDataset,
};
function updateDataset(oldVnode: VNode, vnode: VNode): void {
// 对比更新,主要考虑兼容性问题
}
- 注意:dataset的处理,
elm.dataset
存在与否(兼容性考虑)的处理方式有差异 - 设置属性时,它的值总是转化为一个字符串。例如:
element.dataset.example = null
被转化为data-example="null"
。
eventlisteners
介绍和使用
eventlisteners 模块提供了一个功能强大的事件监听器。
你可以通过给 on
提供一个对象以此来将事件函数绑定到 vnode 上,对象包含你要监听的事件名称和对应函数,函数将会在事件发生时触发并传递相应的事件对象。
function clickHandler(ev) {
console.log("got clicked");
}
h("div", { on: { click: clickHandler } });
Snabbdom 允许在 renders 之间交换事件处理,这种情况发生时并没有实际触发 DOM 的事件处理。
但是,当你在 vnode 之间共享事件函数时需要谨慎一点,因为从技术层面上我们避免了事件处理函数重复绑定到 DOM 上。(总的来说,我们无法保证在 vnode 间共享数据一定能正常工作,因为模块允许对给定的数据进行修改)。
源码分析
eventlisteners.ts
代码语言:javascript复制export const eventListenersModule = {
create: updateEventListeners,
update: updateEventListeners,
destroy: updateEventListeners,
};
function updateEventListeners(oldVnode, vnode) {
//... 对比 更新
// vnode.addEventListener/removeEventListener
}
总结
- attributes和props得区别
- attributes、props、class、dataset、eventlisteners 模块逻辑都很简单,直接新旧对比调用api更新。
- eventlisteners 事件需要销毁