[Perf] Teact: Optimize updating attributes and support dangerouslySetInnerHTML

This commit is contained in:
Alexander Zinchuk 2021-11-27 17:40:44 +01:00
parent c2d76a9990
commit 528137daff
2 changed files with 30 additions and 40 deletions

View File

@ -3,7 +3,6 @@ import React, {
memo,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
} from '../../../lib/teact/teact';
@ -168,9 +167,9 @@ type DispatchProps = Pick<GlobalActions, 'toggleMessageSelection' | 'clickInline
const NBSP = '\u00A0';
const GROUP_MESSAGE_HOVER_ATTRIBUTE = 'data-is-document-group-hover';
// eslint-disable-next-line max-len
const APPENDIX_OWN = '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#000" filter="url(#a)"/><path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#EEFFDE" class="corner"/></g></svg>';
const APPENDIX_OWN = { __html: '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#000" filter="url(#a)"/><path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#EEFFDE" class="corner"/></g></svg>' };
// eslint-disable-next-line max-len
const APPENDIX_NOT_OWN = '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z" fill="#000" filter="url(#a)"/><path d="M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z" fill="#FFF" class="corner"/></g></svg>';
const APPENDIX_NOT_OWN = { __html: '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z" fill="#000" filter="url(#a)"/><path d="M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z" fill="#FFF" class="corner"/></g></svg>' };
const APPEARANCE_DELAY = 10;
const NO_MEDIA_CORNERS_THRESHOLD = 18;
@ -233,8 +232,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
const ref = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null
const bottomMarkerRef = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null
const appendixRef = useRef<HTMLDivElement>(null);
const lang = useLang();
useOnIntersect(bottomMarkerRef, observeIntersectionForBottom);
@ -405,13 +403,6 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
message.id,
);
useFocusMessage(ref, chatId, isFocused, focusDirection, noFocusHighlight, isResizingContainer);
useLayoutEffect(() => {
if (!appendixRef.current) {
return;
}
appendixRef.current.innerHTML = isOwn ? APPENDIX_OWN : APPENDIX_NOT_OWN;
}, [isOwn, withAppendix]);
let style = '';
let calculatedWidth;
@ -766,7 +757,9 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
</Button>
) : undefined}
{withCommentButton && <CommentButton message={message} disabled={noComments} />}
{withAppendix && <div className="svg-appendix" ref={appendixRef} />}
{withAppendix && (
<div className="svg-appendix" dangerouslySetInnerHTML={isOwn ? APPENDIX_OWN : APPENDIX_NOT_OWN} />
)}
</div>
{message.inlineButtons && (
<InlineButtons message={message} onClick={clickInlineButton} />

View File

@ -223,7 +223,9 @@ function createNode($element: VirtualElement): Node {
}
Object.keys(props).forEach((key) => {
addAttribute(element, key, props[key]);
if (props[key] !== undefined) {
setAttribute(element, key, props[key]);
}
});
$element.children = children.map(($child, i) => (
@ -395,31 +397,31 @@ function updateAttributes($current: VirtualRealElement, $new: VirtualRealElement
const newKeys = Object.keys($new.props);
currentKeys.forEach((key) => {
if ($current.props[key] !== undefined && $new.props[key] === undefined) {
removeAttribute(element, key, $current.props[key]);
const currentValue = $current.props[key];
const newValue = $new.props[key];
if (
currentValue !== undefined
&& (
newValue === undefined
|| (currentValue !== newValue && key.startsWith('on'))
)
) {
removeAttribute(element, key, currentValue);
}
});
newKeys.forEach((key) => {
if ($new.props[key] === undefined) {
return;
}
const currentValue = $current.props[key];
const newValue = $new.props[key];
if ($current.props[key] !== $new.props[key]) {
if ($current.props[key] === undefined) {
addAttribute(element, key, $new.props[key]);
} else {
updateAttribute(element, key, $current.props[key], $new.props[key]);
}
if (newValue !== undefined && newValue !== currentValue) {
setAttribute(element, key, newValue);
}
});
}
function addAttribute(element: HTMLElement, key: string, value: any) {
if (value === undefined) {
return;
}
function setAttribute(element: HTMLElement, key: string, value: any) {
// An optimization attempt
if (key === 'className') {
element.className = value;
@ -428,6 +430,9 @@ function addAttribute(element: HTMLElement, key: string, value: any) {
(element as HTMLInputElement).value = value;
} else if (key === 'style') {
element.style.cssText = value;
} else if (key === 'dangerouslySetInnerHTML') {
// eslint-disable-next-line no-underscore-dangle
element.innerHTML = value.__html;
} else if (key.startsWith('on')) {
addEventListener(element, key, value, key.endsWith('Capture'));
} else if (key.startsWith('data-') || HTML_ATTRIBUTES.has(key)) {
@ -444,6 +449,8 @@ function removeAttribute(element: HTMLElement, key: string, value: any) {
(element as HTMLInputElement).value = '';
} else if (key === 'style') {
element.style.cssText = '';
} else if (key === 'dangerouslySetInnerHTML') {
element.innerHTML = '';
} else if (key.startsWith('on')) {
removeEventListener(element, key, value, key.endsWith('Capture'));
} else if (key.startsWith('data-') || HTML_ATTRIBUTES.has(key)) {
@ -453,16 +460,6 @@ function removeAttribute(element: HTMLElement, key: string, value: any) {
}
}
function updateAttribute(element: HTMLElement, key: string, oldValue: any, newValue: any) {
if (key === 'value') {
// Removing and adding value causes a cursor jump
(element as HTMLInputElement).value = newValue !== undefined ? newValue : '';
} else {
removeAttribute(element, key, oldValue);
addAttribute(element, key, newValue);
}
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function DEBUG_addToVirtualTreeSize($current: VirtualRealElement | VirtualDomHead) {
DEBUG_virtualTreeSize += $current.children.length;