Teact: Implement createContext and useContextSignal (#4619)
This commit is contained in:
parent
9d2a928968
commit
a4bfdad768
@ -4,9 +4,9 @@ import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
import useSelectorSignal from '../../hooks/data/useSelectorSignal';
|
||||
import useDerivedState from '../../hooks/useDerivedState';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import useSelectorSignal from '../../hooks/useSelectorSignal';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import ListItem from '../ui/ListItem';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useMemo, useRef, useState,
|
||||
memo, useEffect, useMemo, useRef, useSignal, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
@ -118,7 +118,6 @@ import usePrevious from '../../hooks/usePrevious';
|
||||
import useSchedule from '../../hooks/useSchedule';
|
||||
import useSendMessageAction from '../../hooks/useSendMessageAction';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useSignal from '../../hooks/useSignal';
|
||||
import { useStateRef } from '../../hooks/useStateRef';
|
||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||
import useAttachmentModal from '../middle/composer/hooks/useAttachmentModal';
|
||||
|
||||
@ -12,13 +12,13 @@ import {
|
||||
} from '../../../util/dates/workHours';
|
||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||
|
||||
import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
|
||||
import useInterval from '../../../hooks/schedulers/useInterval';
|
||||
import useDerivedState from '../../../hooks/useDerivedState';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import useSelectorSignal from '../../../hooks/useSelectorSignal';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Transition, { ACTIVE_SLIDE_CLASS_NAME, TO_SLIDE_CLASS_NAME } from '../../ui/Transition';
|
||||
|
||||
@ -50,13 +50,13 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import { createLocationHash } from '../../../util/routing';
|
||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
|
||||
|
||||
import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useSelectorSignal from '../../../hooks/useSelectorSignal';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useChatListEntry from './hooks/useChatListEntry';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useLayoutEffect, useRef, useState,
|
||||
memo, useEffect, useLayoutEffect, useRef, useSignal, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import type { MediaViewerOrigin, ThreadId } from '../../types';
|
||||
@ -24,7 +24,6 @@ import useDerivedState from '../../hooks/useDerivedState';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import useSignal from '../../hooks/useSignal';
|
||||
import { useSignalRef } from '../../hooks/useSignalRef';
|
||||
import { useFullscreenStatus } from '../../hooks/window/useFullscreen';
|
||||
import useWindowSize from '../../hooks/window/useWindowSize';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useLayoutEffect,
|
||||
useMemo, useRef, useState,
|
||||
useMemo, useRef, useSignal, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiDimensions } from '../../api/types';
|
||||
@ -18,7 +18,6 @@ import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||
import { useThrottledSignal } from '../../hooks/useAsyncResolvers';
|
||||
import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useSignal from '../../hooks/useSignal';
|
||||
import useVideoWaitingSignal from './hooks/useVideoWaitingSignal';
|
||||
|
||||
import ShowTransition from '../ui/ShowTransition';
|
||||
|
||||
@ -2,6 +2,7 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useLayoutEffect,
|
||||
useMemo,
|
||||
useSignal,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiDimensions } from '../../api/types';
|
||||
@ -18,7 +19,6 @@ import useDerivedState from '../../hooks/useDerivedState';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import useSignal from '../../hooks/useSignal';
|
||||
import useControlsSignal from './hooks/useControlsSignal';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useEffect, useRef } from '../../../lib/teact/teact';
|
||||
import { useEffect, useRef, useSignal } from '../../../lib/teact/teact';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
import useSignal from '../../../hooks/useSignal';
|
||||
|
||||
export default function useContainerHeight(containerRef: RefObject<HTMLDivElement>, isComposerVisible: boolean) {
|
||||
const [getContainerHeight, setContainerHeight] = useSignal<number | undefined>();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from '../../../lib/teact/teact';
|
||||
import { useEffect, useRef, useSignal } from '../../../lib/teact/teact';
|
||||
import { getGlobal } from '../../../global';
|
||||
|
||||
import type { ThreadId } from '../../../types';
|
||||
@ -13,7 +13,6 @@ import { unique } from '../../../util/iteratees';
|
||||
import { clamp } from '../../../util/math';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useSignal from '../../../hooks/useSignal';
|
||||
|
||||
type PinnedIntersectionChangedParams = {
|
||||
viewportPinnedIdsToAdd?: number[];
|
||||
|
||||
@ -3,6 +3,7 @@ import React, {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useSignal,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
@ -28,7 +29,6 @@ import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useSignal from '../../../hooks/useSignal';
|
||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
|
||||
@ -15,9 +15,9 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import { formatDateTimeToString } from '../../../util/dates/dateFormat';
|
||||
import { CUSTOM_PEER_PREMIUM } from '../../../util/objects/customPeer';
|
||||
|
||||
import useSelector from '../../../hooks/data/useSelector';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import useSelector from '../../../hooks/useSelector';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
||||
memo, useEffect, useLayoutEffect, useMemo, useRef, useSignal, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
@ -33,7 +33,6 @@ import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useSignal from '../../hooks/useSignal';
|
||||
import useWindowSize from '../../hooks/window/useWindowSize';
|
||||
import useSlideSizes from './hooks/useSlideSizes';
|
||||
|
||||
|
||||
79
src/components/test/TestContext.tsx
Normal file
79
src/components/test/TestContext.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React, {
|
||||
createContext, memo, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import useContext from '../../hooks/data/useContext';
|
||||
|
||||
const TestingContext = createContext('default value');
|
||||
|
||||
const ContextConsumer = ({ children, debugKey }: { children?: any; debugKey?: string }) => {
|
||||
const value = useContext(TestingContext);
|
||||
if (debugKey) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`ContextConsumer ${debugKey}`, value);
|
||||
}
|
||||
return (
|
||||
<div style="color: red">
|
||||
{`Current context value: ${value}`}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoizedWrapper = memo(({ children } : { children: any }) => {
|
||||
return <div style="background-color: aquamarine">{children}</div>;
|
||||
});
|
||||
|
||||
const ContextSwapper = ({ value, children } : { value: string; children: any }) => {
|
||||
return (
|
||||
<div style="border: 1px solid red">
|
||||
Swapped {value}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TestContext = () => {
|
||||
const [value, setValue] = useState(Math.random().toString());
|
||||
const [isSwapping, setIsSwapping] = useState(false);
|
||||
|
||||
const Wrapper = isSwapping ? ContextSwapper : TestingContext.Provider;
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setValue(Math.random().toString())}>Change context value</button>
|
||||
<button onClick={() => setIsSwapping((prev) => !prev)}>Swap context wrapper</button>
|
||||
<ContextConsumer />
|
||||
<Wrapper value={value}>
|
||||
<div>
|
||||
<ContextConsumer debugKey="A">
|
||||
<ContextConsumer debugKey="B">
|
||||
<ContextConsumer debugKey="C" />
|
||||
</ContextConsumer>
|
||||
</ContextConsumer>
|
||||
</div>
|
||||
<TestingContext.Provider value={`sibling ${value}`}>
|
||||
<ContextConsumer />
|
||||
</TestingContext.Provider>
|
||||
<MemoizedWrapper>
|
||||
<ContextConsumer />
|
||||
</MemoizedWrapper>
|
||||
<>
|
||||
<ContextConsumer />
|
||||
<ContextConsumer />
|
||||
</>
|
||||
<TestingContext.Provider value={`fastlist ${value}`}>
|
||||
<div style="background-color: antiquewhite" teactFastList>
|
||||
<ContextConsumer key="first" />
|
||||
{!isSwapping && <div key="second">Fast list item</div>}
|
||||
<ContextConsumer key="third" />
|
||||
</div>
|
||||
</TestingContext.Provider>
|
||||
<TestingContext.Provider value="overriden value">
|
||||
<ContextConsumer />
|
||||
</TestingContext.Provider>
|
||||
</Wrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestContext;
|
||||
8
src/hooks/data/useContext.ts
Normal file
8
src/hooks/data/useContext.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { type Context, useContextSignal } from '../../lib/teact/teact';
|
||||
|
||||
import useDerivedState from '../useDerivedState';
|
||||
|
||||
export default function useContext<T>(context: Context<T>) {
|
||||
const signal = useContextSignal(context);
|
||||
return useDerivedState(signal);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import type { GlobalState } from '../global/types';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
import useDerivedState from './useDerivedState';
|
||||
import useDerivedState from '../useDerivedState';
|
||||
import useSelectorSignal from './useSelectorSignal';
|
||||
|
||||
type Selector<T extends unknown> = (global: GlobalState) => T;
|
||||
@ -1,11 +1,11 @@
|
||||
import { addCallback } from '../lib/teact/teactn';
|
||||
import { getGlobal } from '../global';
|
||||
import { addCallback } from '../../lib/teact/teactn';
|
||||
import { getGlobal } from '../../global';
|
||||
|
||||
import type { GlobalState } from '../global/types';
|
||||
import type { Signal, SignalSetter } from '../util/signals';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
import type { Signal, SignalSetter } from '../../util/signals';
|
||||
|
||||
import { createSignal } from '../util/signals';
|
||||
import useEffectOnce from './useEffectOnce';
|
||||
import { createSignal } from '../../util/signals';
|
||||
import useEffectOnce from '../useEffectOnce';
|
||||
|
||||
/*
|
||||
This hook is a more performant variation of the standard React `useSelector` hook. It allows to:
|
||||
@ -1,6 +1,7 @@
|
||||
import { useSignal } from '../lib/teact/teact';
|
||||
|
||||
import type { Signal } from '../util/signals';
|
||||
|
||||
import useSignal from './useSignal';
|
||||
import { useSignalEffect } from './useSignalEffect';
|
||||
import { useStateRef } from './useStateRef';
|
||||
import useSyncEffect from './useSyncEffect';
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
|
||||
import useSignal from './useSignal';
|
||||
import { useEffect, useSignal } from '../lib/teact/teact';
|
||||
|
||||
export default function useGetSelectionRange(inputSelector: string) {
|
||||
const [getRange, setRange] = useSignal<Range | undefined>();
|
||||
|
||||
@ -4,8 +4,8 @@ import { ApiMediaFormat } from '../api/types';
|
||||
|
||||
import { selectIsSynced } from '../global/selectors';
|
||||
import * as mediaLoader from '../util/mediaLoader';
|
||||
import useSelector from './data/useSelector';
|
||||
import useForceUpdate from './useForceUpdate';
|
||||
import useSelector from './useSelector';
|
||||
|
||||
const useMedia = (
|
||||
mediaHash: string | false | undefined,
|
||||
|
||||
@ -8,8 +8,8 @@ import { selectIsSynced } from '../global/selectors';
|
||||
import * as mediaLoader from '../util/mediaLoader';
|
||||
import { throttle } from '../util/schedulers';
|
||||
import { IS_PROGRESSIVE_SUPPORTED } from '../util/windowEnvironment';
|
||||
import useSelector from './data/useSelector';
|
||||
import useForceUpdate from './useForceUpdate';
|
||||
import useSelector from './useSelector';
|
||||
import useUniqueId from './useUniqueId';
|
||||
|
||||
const STREAMING_PROGRESS = 0.75;
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { useRef } from '../lib/teact/teact';
|
||||
|
||||
import { createSignal } from '../util/signals';
|
||||
|
||||
export default function useSignal<T>(initial?: T) {
|
||||
const signalRef = useRef<ReturnType<typeof createSignal<T>>>();
|
||||
signalRef.current ??= createSignal<T>(initial);
|
||||
return signalRef.current;
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ChangeEvent } from 'react';
|
||||
|
||||
import type { Signal } from '../../util/signals';
|
||||
import type {
|
||||
VirtualElement,
|
||||
VirtualElementChildren,
|
||||
@ -34,6 +35,8 @@ interface SelectionState {
|
||||
isCaretAtEnd: boolean;
|
||||
}
|
||||
|
||||
type CurrentContext = Record<string, Signal<unknown>>;
|
||||
|
||||
type DOMElement = HTMLElement | SVGElement;
|
||||
|
||||
const FILTERED_ATTRIBUTES = new Set(['key', 'ref', 'teactFastList', 'teactOrderKey']);
|
||||
@ -59,7 +62,7 @@ function render($element: VirtualElement | undefined, parentEl: HTMLElement) {
|
||||
|
||||
const runImmediateEffects = captureImmediateEffects();
|
||||
const $head = headsByElement.get(parentEl)!;
|
||||
const $renderedChild = renderWithVirtual(parentEl, $head.children[0], $element, $head, 0);
|
||||
const $renderedChild = renderWithVirtual(parentEl, $head.children[0], $element, $head, {}, 0);
|
||||
runImmediateEffects?.();
|
||||
|
||||
$head.children = $renderedChild ? [$renderedChild] : [];
|
||||
@ -79,6 +82,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
$current: VirtualElement | undefined,
|
||||
$new: T,
|
||||
$parent: VirtualElementParent | VirtualDomHead,
|
||||
currentContext: CurrentContext,
|
||||
index: number,
|
||||
options: {
|
||||
skipComponentUpdate?: boolean;
|
||||
@ -116,7 +120,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
&& isNewComponent
|
||||
&& ($new as VirtualElementComponent).componentInstance.mountState === MountState.Mounted
|
||||
) {
|
||||
setupComponentUpdateListener(parentEl, $new as VirtualElementComponent, $parent, index);
|
||||
setupComponentUpdateListener(parentEl, $new as VirtualElementComponent, $parent, currentContext, index);
|
||||
}
|
||||
|
||||
if ($current === $new) {
|
||||
@ -133,10 +137,13 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
if (!$current && $new) {
|
||||
if (isNewComponent || isNewFragment) {
|
||||
if (isNewComponent) {
|
||||
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as unknown as typeof $new;
|
||||
$new = initComponent(
|
||||
parentEl, $new as VirtualElementComponent, $parent, currentContext, index,
|
||||
) as unknown as typeof $new;
|
||||
currentContext = ($new as VirtualElementComponent).componentInstance.context ?? currentContext;
|
||||
}
|
||||
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, {
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, currentContext, {
|
||||
nextSibling, fragment, isSvg,
|
||||
});
|
||||
} else {
|
||||
@ -150,7 +157,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
parentEl.textContent = $newAsReal.value;
|
||||
$newAsReal.target = parentEl.firstChild!;
|
||||
} else {
|
||||
const node = createNode($newAsReal, isSvg);
|
||||
const node = createNode($newAsReal, currentContext, isSvg);
|
||||
$newAsReal.target = node;
|
||||
insertBefore(fragment || parentEl, node, nextSibling);
|
||||
|
||||
@ -160,7 +167,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
}
|
||||
}
|
||||
} else if ($current && !$new) {
|
||||
remount(parentEl, $current, undefined);
|
||||
remount(parentEl, $current, currentContext, undefined);
|
||||
} else if ($current && $new) {
|
||||
if (hasElementChanged($current, $new)) {
|
||||
if (!nextSibling) {
|
||||
@ -169,17 +176,20 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
|
||||
if (isNewComponent || isNewFragment) {
|
||||
if (isNewComponent) {
|
||||
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as unknown as typeof $new;
|
||||
$new = initComponent(
|
||||
parentEl, $new as VirtualElementComponent, $parent, currentContext, index,
|
||||
) as unknown as typeof $new;
|
||||
currentContext = ($new as VirtualElementComponent).componentInstance.context ?? currentContext;
|
||||
}
|
||||
|
||||
remount(parentEl, $current, undefined);
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, {
|
||||
remount(parentEl, $current, currentContext, undefined);
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, currentContext, {
|
||||
nextSibling, fragment, isSvg,
|
||||
});
|
||||
} else {
|
||||
const node = createNode($newAsReal, isSvg);
|
||||
const node = createNode($newAsReal, currentContext, isSvg);
|
||||
$newAsReal.target = node;
|
||||
remount(parentEl, $current, node, nextSibling);
|
||||
remount(parentEl, $current, currentContext, node, nextSibling);
|
||||
|
||||
if ($newAsReal.type === VirtualType.Tag) {
|
||||
setElementRef($newAsReal, node as DOMElement);
|
||||
@ -193,6 +203,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
renderChildren(
|
||||
$current,
|
||||
$new as VirtualElementComponent | VirtualElementFragment,
|
||||
currentContext,
|
||||
parentEl,
|
||||
nextSibling,
|
||||
options.forceMoveToEnd,
|
||||
@ -216,7 +227,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
}
|
||||
|
||||
updateAttributes($current, $newAsTag, currentTarget as DOMElement, isSvg);
|
||||
renderChildren($current, $newAsTag, currentTarget as DOMElement, undefined, undefined, isSvg);
|
||||
renderChildren($current, $newAsTag, currentContext, currentTarget as DOMElement, undefined, undefined, isSvg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,13 +240,16 @@ function initComponent(
|
||||
parentEl: DOMElement,
|
||||
$element: VirtualElementComponent,
|
||||
$parent: VirtualElementParent | VirtualDomHead,
|
||||
currentContext: CurrentContext,
|
||||
index: number,
|
||||
) {
|
||||
const { componentInstance } = $element;
|
||||
|
||||
$element.componentInstance.context = currentContext;
|
||||
|
||||
if (componentInstance.mountState === MountState.New) {
|
||||
$element = mountComponent(componentInstance);
|
||||
setupComponentUpdateListener(parentEl, $element, $parent, index);
|
||||
setupComponentUpdateListener(parentEl, $element, $parent, currentContext, index);
|
||||
}
|
||||
|
||||
return $element;
|
||||
@ -251,6 +265,7 @@ function setupComponentUpdateListener(
|
||||
parentEl: DOMElement,
|
||||
$element: VirtualElementComponent,
|
||||
$parent: VirtualElementParent | VirtualDomHead,
|
||||
currentContext: CurrentContext,
|
||||
index: number,
|
||||
) {
|
||||
const { componentInstance } = $element;
|
||||
@ -261,6 +276,7 @@ function setupComponentUpdateListener(
|
||||
$parent.children[index],
|
||||
componentInstance.$element,
|
||||
$parent,
|
||||
currentContext,
|
||||
index,
|
||||
{ skipComponentUpdate: true },
|
||||
);
|
||||
@ -270,6 +286,7 @@ function setupComponentUpdateListener(
|
||||
function mountChildren(
|
||||
parentEl: DOMElement,
|
||||
$element: VirtualElementComponent | VirtualElementFragment,
|
||||
currentContext: CurrentContext,
|
||||
options: {
|
||||
nextSibling?: ChildNode;
|
||||
fragment?: DocumentFragment;
|
||||
@ -279,20 +296,22 @@ function mountChildren(
|
||||
const { children } = $element;
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
const $child = children[i];
|
||||
const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $element, i, options);
|
||||
const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $element, currentContext, i, options);
|
||||
if ($renderedChild !== $child) {
|
||||
children[i] = $renderedChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unmountChildren(parentEl: DOMElement, $element: VirtualElementComponent | VirtualElementFragment) {
|
||||
function unmountChildren(
|
||||
parentEl: DOMElement, $element: VirtualElementComponent | VirtualElementFragment, currentContext: CurrentContext,
|
||||
) {
|
||||
for (const $child of $element.children) {
|
||||
renderWithVirtual(parentEl, $child, undefined, $element, -1);
|
||||
renderWithVirtual(parentEl, $child, undefined, $element, currentContext, -1);
|
||||
}
|
||||
}
|
||||
|
||||
function createNode($element: VirtualElementReal, isSvg?: true): Node {
|
||||
function createNode($element: VirtualElementReal, currentContext: CurrentContext, isSvg?: true): Node {
|
||||
if ($element.type === VirtualType.Empty) {
|
||||
return document.createTextNode('');
|
||||
}
|
||||
@ -319,7 +338,7 @@ function createNode($element: VirtualElementReal, isSvg?: true): Node {
|
||||
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
const $child = children[i];
|
||||
const $renderedChild = renderWithVirtual(element, undefined, $child, $element, i, { isSvg });
|
||||
const $renderedChild = renderWithVirtual(element, undefined, $child, $element, currentContext, i, { isSvg });
|
||||
if ($renderedChild !== $child) {
|
||||
children[i] = $renderedChild;
|
||||
}
|
||||
@ -331,6 +350,7 @@ function createNode($element: VirtualElementReal, isSvg?: true): Node {
|
||||
function remount(
|
||||
parentEl: DOMElement,
|
||||
$current: VirtualElement,
|
||||
currentContext: CurrentContext,
|
||||
node: Node | undefined,
|
||||
componentNextSibling?: ChildNode,
|
||||
) {
|
||||
@ -342,7 +362,7 @@ function remount(
|
||||
unmountComponent($current.componentInstance);
|
||||
}
|
||||
|
||||
unmountChildren(parentEl, $current);
|
||||
unmountChildren(parentEl, $current, currentContext);
|
||||
|
||||
if (node) {
|
||||
insertBefore(parentEl, node, componentNextSibling);
|
||||
@ -400,6 +420,7 @@ function getNextSibling($current: VirtualElement): ChildNode | undefined {
|
||||
function renderChildren(
|
||||
$current: VirtualElementParent,
|
||||
$new: VirtualElementParent,
|
||||
currentContext: CurrentContext,
|
||||
currentEl: DOMElement,
|
||||
nextSibling?: ChildNode,
|
||||
forceMoveToEnd = false,
|
||||
@ -410,7 +431,7 @@ function renderChildren(
|
||||
}
|
||||
|
||||
if (('props' in $new) && $new.props.teactFastList) {
|
||||
renderFastListChildren($current, $new, currentEl);
|
||||
renderFastListChildren($current, $new, currentContext, currentEl);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -433,6 +454,7 @@ function renderChildren(
|
||||
currentChildren[i],
|
||||
newChildren[i],
|
||||
$new,
|
||||
currentContext,
|
||||
i,
|
||||
i >= currentChildrenLength ? { fragment, isSvg } : { nextSibling, forceMoveToEnd, isSvg },
|
||||
);
|
||||
@ -449,7 +471,9 @@ function renderChildren(
|
||||
|
||||
// This function allows to prepend/append a bunch of new DOM nodes to the top/bottom of preserved ones.
|
||||
// It also allows to selectively move particular preserved nodes within their DOM list.
|
||||
function renderFastListChildren($current: VirtualElementParent, $new: VirtualElementParent, currentEl: DOMElement) {
|
||||
function renderFastListChildren(
|
||||
$current: VirtualElementParent, $new: VirtualElementParent, currentContext: CurrentContext, currentEl: DOMElement,
|
||||
) {
|
||||
const currentChildren = $current.children;
|
||||
const newChildren = $new.children;
|
||||
|
||||
@ -484,7 +508,7 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle
|
||||
|
||||
// First we process removed children
|
||||
if (isKeyPresent && !newKeys.has(key)) {
|
||||
renderWithVirtual(currentEl, $currentChild, undefined, $new, -1);
|
||||
renderWithVirtual(currentEl, $currentChild, undefined, $new, currentContext, -1);
|
||||
|
||||
continue;
|
||||
} else if (!isKeyPresent) {
|
||||
@ -495,7 +519,7 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle
|
||||
key = `${INDEX_KEY_PREFIX}${i}`;
|
||||
// Otherwise, we just remove it
|
||||
} else {
|
||||
renderWithVirtual(currentEl, $currentChild, undefined, $new, -1);
|
||||
renderWithVirtual(currentEl, $currentChild, undefined, $new, currentContext, -1);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -531,7 +555,7 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle
|
||||
|
||||
// This prepends new children to the top
|
||||
if (fragmentSize) {
|
||||
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new);
|
||||
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new, currentContext);
|
||||
fragmentSize = undefined;
|
||||
fragmentIndex = undefined;
|
||||
}
|
||||
@ -551,7 +575,9 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle
|
||||
const nextSibling = currentEl.childNodes[isMovingDown ? i + 1 : i];
|
||||
const options = shouldMoveNode ? (nextSibling ? { nextSibling } : { forceMoveToEnd: true }) : undefined;
|
||||
|
||||
const $renderedChild = renderWithVirtual(currentEl, currentChildInfo.$element, $newChild, $new, i, options);
|
||||
const $renderedChild = renderWithVirtual(
|
||||
currentEl, currentChildInfo.$element, $newChild, $new, currentContext, i, options,
|
||||
);
|
||||
if ($renderedChild !== $newChild) {
|
||||
newChildren[i] = $renderedChild;
|
||||
}
|
||||
@ -559,18 +585,24 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle
|
||||
|
||||
// This appends new children to the bottom
|
||||
if (fragmentSize) {
|
||||
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new);
|
||||
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new, currentContext);
|
||||
}
|
||||
}
|
||||
|
||||
function renderFragment(
|
||||
fragmentIndex: number, fragmentSize: number, parentEl: DOMElement, $parent: VirtualElementParent,
|
||||
fragmentIndex: number,
|
||||
fragmentSize: number,
|
||||
parentEl: DOMElement,
|
||||
$parent: VirtualElementParent,
|
||||
currentContext: CurrentContext,
|
||||
) {
|
||||
const nextSibling = parentEl.childNodes[fragmentIndex];
|
||||
|
||||
if (fragmentSize === 1) {
|
||||
const $child = $parent.children[fragmentIndex];
|
||||
const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $parent, fragmentIndex, { nextSibling });
|
||||
const $renderedChild = renderWithVirtual(
|
||||
parentEl, undefined, $child, $parent, currentContext, fragmentIndex, { nextSibling },
|
||||
);
|
||||
if ($renderedChild !== $child) {
|
||||
$parent.children[fragmentIndex] = $renderedChild;
|
||||
}
|
||||
@ -582,7 +614,7 @@ function renderFragment(
|
||||
|
||||
for (let i = fragmentIndex; i < fragmentIndex + fragmentSize; i++) {
|
||||
const $child = $parent.children[i];
|
||||
const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $parent, i, { fragment });
|
||||
const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $parent, currentContext, i, { fragment });
|
||||
if ($renderedChild !== $child) {
|
||||
$parent.children[i] = $renderedChild;
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { incrementOverlayCounter } from '../../util/debugOverlay';
|
||||
import { orderBy } from '../../util/iteratees';
|
||||
import safeExec from '../../util/safeExec';
|
||||
import { throttleWith } from '../../util/schedulers';
|
||||
import { isSignal } from '../../util/signals';
|
||||
import { createSignal, isSignal, type Signal } from '../../util/signals';
|
||||
import { requestMeasure, requestMutation } from '../fasterdom/fasterdom';
|
||||
|
||||
export type Props = AnyLiteral;
|
||||
@ -71,6 +71,7 @@ interface ComponentInstance {
|
||||
props: Props;
|
||||
renderedValue?: any;
|
||||
mountState: MountState;
|
||||
context?: Record<string, Signal<unknown>>;
|
||||
hooks?: {
|
||||
state?: {
|
||||
cursor: number;
|
||||
@ -132,12 +133,20 @@ export type TeactNode =
|
||||
type Effect = () => (NoneToVoidFunction | void);
|
||||
type EffectCleanup = NoneToVoidFunction;
|
||||
|
||||
export type Context<T> = {
|
||||
defaultValue?: T;
|
||||
contextId: string;
|
||||
Provider: FC<{ value: T; children: TeactNode }>;
|
||||
};
|
||||
|
||||
const Fragment = Symbol('Fragment');
|
||||
|
||||
const DEBUG_RENDER_THRESHOLD = 7;
|
||||
const DEBUG_EFFECT_THRESHOLD = 7;
|
||||
const DEBUG_SILENT_RENDERS_FOR = new Set(['TeactMemoWrapper', 'TeactNContainer', 'Button', 'ListItem', 'MenuItem']);
|
||||
|
||||
let contextCounter = 0;
|
||||
|
||||
let lastComponentId = 0;
|
||||
let renderingInstance: ComponentInstance;
|
||||
|
||||
@ -176,7 +185,7 @@ function createComponentInstance(Component: FC, props: Props, children: any[]):
|
||||
}
|
||||
|
||||
const componentInstance: ComponentInstance = {
|
||||
id: ++lastComponentId,
|
||||
id: -1,
|
||||
$element: undefined as unknown as VirtualElementComponent,
|
||||
Component,
|
||||
name: Component.name,
|
||||
@ -476,6 +485,7 @@ export function hasElementChanged($old: VirtualElement, $new: VirtualElement) {
|
||||
}
|
||||
|
||||
export function mountComponent(componentInstance: ComponentInstance) {
|
||||
componentInstance.id = ++lastComponentId;
|
||||
renderComponent(componentInstance);
|
||||
componentInstance.mountState = MountState.Mounted;
|
||||
return componentInstance.$element;
|
||||
@ -900,6 +910,42 @@ export function useRef<T>(initial?: T | null) {
|
||||
return byCursor[cursor];
|
||||
}
|
||||
|
||||
export function createContext<T>(defaultValue?: T): Context<T> {
|
||||
const contextId = String(contextCounter++);
|
||||
|
||||
function TeactContextProvider(props: { value: T; children: TeactNode }) {
|
||||
const [getValue, setValue] = useSignal(props.value ?? defaultValue);
|
||||
// Create a new object to avoid mutations in the parent context
|
||||
renderingInstance.context = { ...renderingInstance.context };
|
||||
|
||||
renderingInstance.context[contextId] = getValue;
|
||||
setValue(props.value);
|
||||
return props.children;
|
||||
}
|
||||
|
||||
TeactContextProvider.DEBUG_contentComponentName = contextId;
|
||||
|
||||
const context = {
|
||||
defaultValue,
|
||||
contextId,
|
||||
Provider: TeactContextProvider,
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export function useContextSignal<T>(context: Context<T>) {
|
||||
const [getDefaultValue] = useSignal(context.defaultValue);
|
||||
|
||||
return renderingInstance.context?.[context.contextId] || getDefaultValue;
|
||||
}
|
||||
|
||||
export function useSignal<T>(initial?: T) {
|
||||
const signalRef = useRef<ReturnType<typeof createSignal<T>>>();
|
||||
signalRef.current ??= createSignal<T>(initial);
|
||||
return signalRef.current;
|
||||
}
|
||||
|
||||
export function memo<T extends FC_withDebug>(Component: T, debugKey?: string) {
|
||||
function TeactMemoWrapper(props: Props) {
|
||||
return useMemo(
|
||||
@ -929,6 +975,10 @@ export function DEBUG_resolveComponentName(Component: FC_withDebug) {
|
||||
return `memo>${DEBUG_contentComponentName}`;
|
||||
}
|
||||
|
||||
if (name === 'TeactContextProvider') {
|
||||
return `context>id${DEBUG_contentComponentName}`;
|
||||
}
|
||||
|
||||
return name + (DEBUG_contentComponentName ? `>${DEBUG_contentComponentName}` : '');
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user