patchProp属性补丁解析
# 概览
在《createApp创建实例解析》中提到pathProp
用于更新DOM元素的属性,本文会相信解析下patchProp
方法中是如何更新不同类型的属性。
const patchProp = (el, key, prevValue, nextValue, namespace, parentComponent) => {
// el:目标DOM元素 key:属性名 preValue:旧值 nextValue:新值 namespace:命名空间 parentComponent:父组件实例
// isSVG表示是否是SVG元素
const isSVG = namespace === 'svg';
if (key === 'class') {
// 处理class属性
patchClass(el, nextValue, isSVG);
} else if (key === "style") {
// 处理style样式属性
patchStyle(key, prevValue, nextValue);
} else if (shared.isOn(key)) {
// 处理事件监听器,isOn判断属性名即事件名称是否以on开头
if (!shared.isModeListener(key)) {
// isModeListener过滤掉onUpdate特殊事件
patchEvent(el, key, prevValue, nextValue, parentComponent);
}
} else if (key[0] === '.' ? (key = key.slice(1), true) :
key[0] === "^" ? (key = key.slice(1), false) :
shouldSetAsProp(el, key, nextValue, isSVG)) {
// 处理DOM属性
patchDOMProp(el, key, nextValue);
if (!el.tagName.includes("-") && (key === "value" || key === "checked" || key === "selected")) {
// 处理特殊属性 value/checked/selected
patchAttr(el, key, nextValue, isSVG, parentComponent, key !== "value")
}
} else if (el._isVueCE && (/[A-Z]/.test(key) || !shared.isstring(nextValue))) {
// 处理Web Components属性
patchDOMProp(el, shared, camelize(key), nextValue, parentComponent, key)
} else {
// 处理其他属性
if (key === 'true-value') {
el._trueValue = nextValue;
} else if (key === 'false-value') {
el._falseValue = nextValue;
}
patchAttr(el, key, nextValue, isSVG)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 更新方法
# patchClass
function patchClass(el, value, isSVG) {
const transitionClasses = el[vtcKey];
if (transitionClasses) {
value = (value ? [value, ...transitionClasses] : [...transitionClasses]).join(" ");
}
if (value == null) {
el.removeAttribute("class");
} else if (isSVG) {
el.setAttribute("class", value);
} else {
el.className = value;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# patchStyle
const displayRE = /(^|;)\s*display\s*:/;
function patchStyle(el, prev, next) {
const style = el.style;
const isCssString = shared.isString(next);
let hasControlledDisplay = false;
if (next && !isCssString) {
if (prev) {
if (!shared.isString(prev)) {
for (const key in prev) {
if (next[key] == null) {
setStyle(style, key, "");
}
}
} else {
for (const prevStyle of prev.split(";")) {
const key = prevStyle.slice(0, prevStyle.indexOf(":")).trim();
if (next[key] == null) {
setStyle(style, key, "");
}
}
}
}
for (const key in next) {
if (key === "display") {
hasControlledDisplay = true;
}
setStyle(style, key, next[key]);
}
} else {
if (isCssString) {
if (prev !== next) {
const cssVarText = style[CSS_VAR_TEXT];
if (cssVarText) {
next += ";" + cssVarText;
}
style.cssText = next;
hasControlledDisplay = displayRE.test(next);
}
} else if (prev) {
el.removeAttribute("style");
}
}
if (vShowOriginalDisplay in el) {
el[vShowOriginalDisplay] = hasControlledDisplay ? style.display : "";
if (el[vShowHidden]) {
style.display = "none";
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# patchEvent
const veiKey = Symbol("_vei");
function patchEvent(el, rawName, prevValue, nextValue, instance = null) {
const invokers = el[veiKey] || (el[veiKey] = {});
const existingInvoker = invokers[rawName];
if (nextValue && existingInvoker) {
existingInvoker.value = nextValue;
} else {
const [name, options] = parseName(rawName);
if (nextValue) {
const invoker = invokers[rawName] = createInvoker(
nextValue,
instance
);
addEventListener(el, name, invoker, options);
} else if (existingInvoker) {
removeEventListener(el, name, existingInvoker, options);
invokers[rawName] = void 0;
}
}
}
function addEventListener(el, event, handler, options) {
el.addEventListener(event, handler, options);
}
function removeEventListener(el, event, handler, options) {
el.removeEventListener(event, handler, options);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# patchDOMProp
function patchDOMProp(el, key, value, parentComponent, attrName) {
if (key === "innerHTML" || key === "textContent") {
if (value != null) {
el[key] = key === "innerHTML" ? unsafeToTrustedHTML(value) : value;
}
return;
}
const tag = el.tagName;
if (key === "value" && tag !== "PROGRESS" && // custom elements may use _value internally
!tag.includes("-")) {
const oldValue = tag === "OPTION" ? el.getAttribute("value") || "" : el.value;
const newValue = value == null ? (
// #11647: value should be set as empty string for null and undefined,
// but <input type="checkbox"> should be set as 'on'.
el.type === "checkbox" ? "on" : ""
) : String(value);
if (oldValue !== newValue || !("_value" in el)) {
el.value = newValue;
}
if (value == null) {
el.removeAttribute(key);
}
el._value = value;
return;
}
let needRemove = false;
if (value === "" || value == null) {
const type = typeof el[key];
if (type === "boolean") {
value = shared.includeBooleanAttr(value);
} else if (value == null && type === "string") {
value = "";
needRemove = true;
} else if (type === "number") {
value = 0;
needRemove = true;
}
}
try {
el[key] = value;
} catch (e) {
}
needRemove && el.removeAttribute(attrName || key);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# patchAttr
const xlinkNS = "http://www.w3.org/1999/xlink";
function patchAttr(el, key, value, isSVG, instance, isBoolean = shared.isSpecialBooleanAttr(key)) {
if (isSVG && key.startsWith("xlink:")) {
if (value == null) {
el.removeAttributeNS(xlinkNS, key.slice(6, key.length));
} else {
el.setAttributeNS(xlinkNS, key, value);
}
} else {
if (value == null || isBoolean && !shared.includeBooleanAttr(value)) {
el.removeAttribute(key);
} else {
el.setAttribute(
key,
isBoolean ? "" : shared.isSymbol(value) ? String(value) : value
);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# shared.isOn
# shared.isModeListener
编辑 (opens new window)
上次更新: 2025/09/16, 07:26:19