4. 「snabbdom@3.5.1 源码分析」内置模块

2023-02-24 10:04:59 浏览数 (1)

内置模块有以下几个

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 列表中移除。

对于布尔值属性(如:disabledhidden,selected ...),这一类属性并不依赖于 Attr 的值(true 或 false),而是取决于 DOM 元素本身是否存在该属性。模块对于这类属性的处理方式有些许不同,当一个布尔值属性被赋为 假值 (0-0nullfalse,NaNundefined, 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上的属性
}

看到该模块关注cteateupdate阶段

  • 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

结论:

  1. Attributes are defined by HTML. Properties are defined by the DOM (Document Object Model).
  2. 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 DOM value property is the current value.
  3. The 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 属性,这个模块值为一个对象形式的 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 上,对象包含你要监听的事件名称和对应函数,函数将会在事件发生时触发并传递相应的事件对象。

代码语言:javascript复制
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
}

总结

  1. attributes和props得区别
  2. attributes、props、class、dataset、eventlisteners 模块逻辑都很简单,直接新旧对比调用api更新。
  3. eventlisteners 事件需要销毁

0 人点赞