[Perf] Composer: Optimize message typing

This commit is contained in:
Alexander Zinchuk 2022-01-21 17:29:29 +01:00
parent fbda8e8094
commit fc0365d5b9
8 changed files with 78 additions and 38 deletions

View File

@ -59,7 +59,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
loadNotificationSettings();
}, [loadNotificationSettings]);
const runDebounced = useDebounce(500, false, true);
const runDebounced = useDebounce(500, true);
const handleSettingsChange = useCallback((
e: ChangeEvent<HTMLInputElement>,

View File

@ -104,9 +104,9 @@ const MediaViewerSlides: FC<OwnProps> = ({
forceUpdate();
}, [forceUpdate]);
const debounceSetMessage = useDebounce(DEBOUNCE_MESSAGE, false);
const debounceSwipe = useDebounce(DEBOUNCE_SWIPE, false);
const debounceActive = useDebounce(DEBOUNCE_ACTIVE, false);
const debounceSetMessage = useDebounce(DEBOUNCE_MESSAGE, true);
const debounceSwipe = useDebounce(DEBOUNCE_SWIPE, true);
const debounceActive = useDebounce(DEBOUNCE_ACTIVE, true);
const handleToggleFooterVisibility = useCallback(() => {
if (IS_TOUCH_ENV && (isPhoto || isGif) && hasFooter) {

View File

@ -1,6 +1,4 @@
import React, {
FC, memo, useEffect, useMemo,
} from '../../../lib/teact/teact';
import React, { FC, memo, useEffect } from '../../../lib/teact/teact';
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
import { ApiMessage, ApiMessageEntityTypes, ApiWebPage } from '../../../api/types';
@ -12,6 +10,7 @@ import parseMessageInput from '../../../util/parseMessageInput';
import useOnChange from '../../../hooks/useOnChange';
import useShowTransition from '../../../hooks/useShowTransition';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import useDebouncedMemo from '../../../hooks/useDebouncedMemo';
import buildClassName from '../../../util/buildClassName';
import WebPage from '../message/WebPage';
@ -32,6 +31,7 @@ type StateProps = {
theme: ISettings['theme'];
};
const DEBOUNCE_MS = 300;
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
const WebPagePreview: FC<OwnProps & StateProps> = ({
@ -49,7 +49,7 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
toggleMessageWebPage,
} = getDispatch();
const link = useMemo(() => {
const link = useDebouncedMemo(() => {
const { text, entities } = parseMessageInput(messageText);
const linkEntity = entities && entities.find(({ type }) => type === ApiMessageEntityTypes.TextUrl);
@ -63,7 +63,7 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
}
return undefined;
}, [messageText]);
}, DEBOUNCE_MS, [messageText]);
useEffect(() => {
if (link) {

View File

@ -3,25 +3,29 @@ import { getDispatch } from '../../../../lib/teact/teactn';
import { InlineBotSettings } from '../../../../types';
import useFlag from '../../../../hooks/useFlag';
import usePrevious from '../../../../hooks/usePrevious';
import useDebouncedMemo from '../../../../hooks/useDebouncedMemo';
const tempEl = document.createElement('div');
const DEBOUNCE_MS = 300;
const INLINE_BOT_QUERY_REGEXP = /^@([a-z0-9_]{1,32})[\u00A0\u0020]+(.*)/i;
const HAS_NEW_LINE = /^@([a-z0-9_]{1,32})[\u00A0\u0020]+\n{2,}/i;
const tempEl = document.createElement('div');
export default function useInlineBotTooltip(
isAllowed: boolean,
chatId: string,
html: string,
inlineBots?: Record<string, false | InlineBotSettings>,
) {
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
const text = getPlainText(html);
const { queryInlineBot, resetInlineBot } = getDispatch();
const { username, query, canShowHelp } = parseStartWithUsernameString(text);
const usernameLowered = username.toLowerCase();
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
const {
username, query, canShowHelp, usernameLowered,
} = useDebouncedMemo(() => parseBotQuery(html), DEBOUNCE_MS, [html]) || {};
const prevQuery = usePrevious(query);
const prevUsername = usePrevious(username);
const inlineBotData = inlineBots?.[usernameLowered];
const inlineBotData = usernameLowered ? inlineBots?.[usernameLowered] : undefined;
const {
id: botId,
switchPm,
@ -63,14 +67,33 @@ export default function useInlineBotTooltip(
return {
isOpen,
closeTooltip: unmarkIsOpen,
loadMore,
username,
id: botId,
isGallery,
switchPm,
results,
closeTooltip: unmarkIsOpen,
help: canShowHelp && help ? `@${username} ${help}` : undefined,
loadMore,
};
}
function parseBotQuery(html: string) {
const text = getPlainText(html);
const result = text.match(INLINE_BOT_QUERY_REGEXP);
if (!result) {
return {
username: '',
query: '',
canShowHelp: false,
usernameLowered: '',
};
}
return {
username: result[1],
query: result[2],
canShowHelp: result[2] === '' && !text.match(HAS_NEW_LINE),
usernameLowered: result[1].toLowerCase(),
};
}
@ -79,16 +102,3 @@ function getPlainText(html: string) {
return tempEl.innerText;
}
function parseStartWithUsernameString(text: string) {
const result = text.match(INLINE_BOT_QUERY_REGEXP);
if (!result) {
return { username: '', query: '', canShowHelp: false };
}
return {
username: result[1],
query: result[2],
canShowHelp: result[2] === '' && !text.match(HAS_NEW_LINE),
};
}

View File

@ -13,7 +13,7 @@ export default function useStickyDates() {
// so we will add `position: sticky` only after first scroll. There would be no animation on the first show though.
const [isScrolled, markIsScrolled] = useFlag(false);
const runDebounced = useDebounce(DEBOUNCE, false);
const runDebounced = useDebounce(DEBOUNCE, true);
const updateStickyDates = useCallback((container: HTMLDivElement, hasTools?: boolean) => {
markIsScrolled();

View File

@ -2,8 +2,8 @@ import { useMemo } from '../lib/teact/teact';
import { debounce } from '../util/schedulers';
export default function useDebounce(ms: number, shouldRunFirst?: boolean, shouldRunLast?: boolean) {
export default function useDebounce(ms: number, noFirst?: boolean, noLast?: boolean) {
return useMemo(() => {
return debounce((cb) => cb(), ms, shouldRunFirst, shouldRunLast);
}, [ms, shouldRunFirst, shouldRunLast]);
return debounce((cb) => cb(), ms, !noFirst, !noLast);
}, [ms, noFirst, noLast]);
}

View File

@ -0,0 +1,28 @@
import { useState } from '../lib/teact/teact';
import useDebounce from './useDebounce';
import useOnChange from './useOnChange';
import useHeavyAnimationCheck from './useHeavyAnimationCheck';
import useFlag from './useFlag';
export default function useDebouncedMemo<R extends any, D extends any[]>(
resolverFn: () => R, ms: number, dependencies: D,
): R | undefined {
const runDebounced = useDebounce(ms, true);
const [value, setValue] = useState<R>();
const [isFrozen, freeze, unfreeze] = useFlag();
useHeavyAnimationCheck(freeze, unfreeze);
useOnChange(() => {
if (isFrozen) {
return;
}
runDebounced(() => {
setValue(resolverFn());
});
}, [...dependencies, isFrozen]);
return value;
}

View File

@ -5,7 +5,9 @@ import useOnChange from './useOnChange';
import useHeavyAnimationCheck from './useHeavyAnimationCheck';
import useFlag from './useFlag';
export default <R extends any, D extends any[]>(resolverFn: () => R, ms: number, dependencies: D) => {
export default function useThrottledMemo<R extends any, D extends any[]>(
resolverFn: () => R, ms: number, dependencies: D,
): R | undefined {
const runThrottled = useThrottle(ms, true);
const [value, setValue] = useState<R>();
const [isFrozen, freeze, unfreeze] = useFlag();
@ -20,7 +22,7 @@ export default <R extends any, D extends any[]>(resolverFn: () => R, ms: number,
runThrottled(() => {
setValue(resolverFn());
});
}, dependencies.concat([isFrozen]));
}, [...dependencies, isFrozen]);
return value;
};
}