内置模块有以下几个
snabbdom这种实现解构了基础和上层模块能力,上层模块可以按照职责单一原则进行拆分,然后进行注册,通过钩子参与构建过程(怎么感觉和webpack基于tapable类似,是吧)
下面分别看看每个module都做些什么事情
与 props 相同,但是是使用 attr 替代 prop。
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 列表中移除。
export const attributesModule = {
create: updateAttrs,
update: updateAttrs,
};
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
阶段
该模块允许你设置 DOM 元素的属性。
h("a", { props: { href: "/foo" } }, "Go to Foo");
属性只能被设置不能被移除,即使浏览器允许自定义添加或删除属性,该模块也不会尝试删除。这是因为原生 DOM 的属性也同样不支持被移除,如果你是通过自定义属性来存储信息或者引用对象,那么请考虑使用 data-* attributes 代替,参考后面的data-set
模块。
export const propsModule = { create: updateProps, update: updateProps };
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
结论:
value
specifies the initial value; the DOM value
property is the current value.disabled
attribute is another peculiar example. A button's disabled
property is false
by default so the button is enabled. When you add the disabled
attribute, its presence alone initializes the button's disabled
property to true
so the button is disabled. Adding and removing the disabled
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's disabled
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 数据,对象中类名需要映射为布尔值,以此来表示该类名是否应该出现在节点上。
h("a", { class: { active: true, selected: false } }, "Toggle");
export const classModule = { create: updateClass, update: updateClass };
function updateClass(oldVnode, vnode) {
//... 新老class 对比,调用 classList[add/remove]来修改
// 不细说了
}
style 模块用于让动画更加平滑,它的核心是允许你在元素上设置 CSS 属性。 js
h(
"span",
{
style: {
border: "1px solid #bada55",
color: "#c0ffee",
fontWeight: "bold",
delayed: { 延迟样式 },
destroy: { 销毁样式 },
remove: { 移除样式 },
},
},
"Say my name, and every colour illuminates"
);
支持自定义属性设置即css变量
h(
"div",
{
style: { "--warnColor": "yellow" },
},
"Warning"
);
delayed、destroy、remove,参考
export const styleModule = {
pre: forceReflow,
create: updateStyle,
update: updateStyle,
destroy: applyDestroyStyle,
remove: applyRemoveStyle,
};
关键点:
applyRemoveStyle
会去 强制重排(reflow) 一次当前元素,这里涉及的两个提交如下:涉及的钩子:
removeVnodes
会触发(patch时oldVnode不被复用时会调用(此时还未删除))这个模块允许你在 DOM 元素上设置自定义 data 属性,然后通过 HTMLElement.dataset 来访问这些属性。
h("button", { dataset: { action: "reset" } }, "Reset");
export const datasetModule: Module = {
create: updateDataset,
update: updateDataset,
};
function updateDataset(oldVnode: VNode, vnode: VNode): void {
// 对比更新,主要考虑兼容性问题
}
elm.dataset
存在与否(兼容性考虑)的处理方式有差异
element.dataset.example = null
被转化为 data-example="null"
。
eventlisteners 模块提供了一个功能强大的事件监听器。
你可以通过给 on
提供一个对象以此来将事件函数绑定到 vnode 上,对象包含你要监听的事件名称和对应函数,函数将会在事件发生时触发并传递相应的事件对象。
function clickHandler(ev) {
console.log("got clicked");
}
h("div", { on: { click: clickHandler } });
Snabbdom 允许在 renders 之间交换事件处理,这种情况发生时并没有实际触发 DOM 的事件处理。
但是,当你在 vnode 之间共享事件函数时需要谨慎一点,因为从技术层面上我们避免了事件处理函数重复绑定到 DOM 上。(总的来说,我们无法保证在 vnode 间共享数据一定能正常工作,因为模块允许对给定的数据进行修改)。
export const eventListenersModule = {
create: updateEventListeners,
update: updateEventListeners,
destroy: updateEventListeners,
};
function updateEventListeners(oldVnode, vnode) {
//... 对比 更新
// vnode.addEventListener/removeEventListener
}