snabbdom (三)
大约 3 分钟
attrbutes, class, dataset, props 定义
这里补充几个自己的知识盲区
- Record 将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型
// 源码
type Record<K extends keyof any, T> = {
[P in K]: T;
};
- Partial 将一个类型中所有的属性 都变成可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
- Required
type Required<T> = {
[P in keyof T]-?: T[P];
};
- Readonly 将一个类型中所有的属性 都变成只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
- Pick
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
attrbutes.ts
import { VNode, VNodeData } from '../vnode';
import { Module } from './module';
export type Attrs = Record<string, string | number | boolean>;
const xlinkNS = 'http://www.w3.org/1999/xlink';
const xmlNS = 'http://www.w3.org/XML/1998/namespace';
const colonChar = 58;
const xChar = 120;
function updateAttrs(oldVnode: VNode, vnode: VNode): void {
var key: string;
var elm: Element = vnode.elm as Element;
var oldAttrs = (oldVnode.data as VNodeData).attrs; // 旧attr
var attrs = (vnode.data as VNodeData).attrs; // 新attr
if (!oldAttrs && !attrs) return; // 如果都不存在 则返回
if (oldAttrs === attrs) return; // 是一样的 则返回
oldAttrs = oldAttrs || {};
attrs = attrs || {};
// update modified attributes, add new attributes
for (key in attrs) {
// 遍历新attrs中的key
const cur = attrs[key];
const old = oldAttrs[key];
if (old !== cur) {
// 两个相同键名对应的值不一样
if (cur === true) {
// 属性值可以是 string | number | boolean
elm.setAttribute(key, '');
} else if (cur === false) {
elm.removeAttribute(key);
} else {
if (key.charCodeAt(0) !== xChar) {
// svg 命名空间 xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"
elm.setAttribute(key, cur as any);
} else if (key.charCodeAt(3) === colonChar) {
//xml
elm.setAttributeNS(xmlNS, key, cur as any);
} else if (key.charCodeAt(5) === colonChar) {
//xlink
elm.setAttributeNS(xlinkNS, key, cur as any);
} else {
elm.setAttribute(key, cur as any);
}
}
}
}
// remove removed attributes
// use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value)
// the other option is to remove all attributes with value == undefined
for (key in oldAttrs) {
// 将移出新attrs 中没有的
if (!(key in attrs)) {
elm.removeAttribute(key);
}
}
}
export const attributesModule: Module = {
create: updateAttrs,
update: updateAttrs,
};
class.ts
import { VNode, VNodeData } from '../vnode';
import { Module } from './module';
export type Classes = Record<string, boolean>;
function updateClass(oldVnode: VNode, vnode: VNode): void {
var cur: any;
var name: string;
var elm: Element = vnode.elm as Element;
var oldClass = (oldVnode.data as VNodeData).class;
var klass = (vnode.data as VNodeData).class;
if (!oldClass && !klass) return;
if (oldClass === klass) return;
oldClass = oldClass || {};
klass = klass || {};
for (name in oldClass) {
if (
oldClass[name] &&
!Object.prototype.hasOwnProperty.call(klass, name) // 如果在旧中存在 在新中存在 则移出该class
) {
// was `true` and now not provided
elm.classList.remove(name);
}
}
for (name in klass) {
cur = klass[name];
if (cur !== oldClass[name]) {
(elm.classList as any)[cur ? 'add' : 'remove'](name); // add class
}
}
}
export const classModule: Module = { create: updateClass, update: updateClass };
dataset.ts
import { VNode, VNodeData } from '../vnode';
import { Module } from './module';
export type Dataset = Record<string, string>;
const CAPS_REGEX = /[A-Z]/g;
function updateDataset(oldVnode: VNode, vnode: VNode): void {
const elm: HTMLElement = vnode.elm as HTMLElement;
let oldDataset = (oldVnode.data as VNodeData).dataset;
let dataset = (vnode.data as VNodeData).dataset;
let key: string;
if (!oldDataset && !dataset) return;
if (oldDataset === dataset) return;
oldDataset = oldDataset || {};
dataset = dataset || {};
const d = elm.dataset;
for (key in oldDataset) {
if (!dataset[key]) {
// 新里面没有
if (d) {
// 真实DOM中有dataset属性
if (key in d) {
// 如果真实DOM中dataset中存在
delete d[key]; // 删除
}
} else {
// 真实DOM中没有有dataset属性
elm.removeAttribute(
'data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(),
); // 真实DOM移出data-xxx属性
}
}
}
for (key in dataset) {
if (oldDataset[key] !== dataset[key]) {
// 旧不等于新
if (d) {
// 真实DOM中存在dataset
d[key] = dataset[key]; // 重置为新
} else {
elm.setAttribute(
'data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(),
dataset[key],
); // 设置新的 data-xxx
}
}
}
}
export const datasetModule: Module = {
create: updateDataset,
update: updateDataset,
};
props.ts
import { VNode, VNodeData } from '../vnode';
import { Module } from './module';
export type Props = Record<string, any>;
function updateProps(oldVnode: VNode, vnode: VNode): void {
var key: string;
var cur: any;
var old: any;
var elm = vnode.elm;
var oldProps = (oldVnode.data as VNodeData).props;
var props = (vnode.data as VNodeData).props;
if (!oldProps && !props) return;
if (oldProps === props) return;
oldProps = oldProps || {};
props = props || {};
for (key in props) {
cur = props[key];
old = oldProps[key];
if (old !== cur && (key !== 'value' || (elm as any)[key] !== cur)) {
// 旧不等于新 && (键值不为value || 真实DOM中的对应的值 不等于新值)
(elm as any)[key] = cur;
}
}
}
export const propsModule: Module = { create: updateProps, update: updateProps };