mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-17 17:40:31 +08:00
perf: improve the struct tree performance when modifying
This commit is contained in:
parent
8a7028ce56
commit
2e589609b7
@ -3,16 +3,15 @@ import { Box, Text, VStack } from '@chakra-ui/react';
|
||||
import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { ComponentItemView } from './ComponentItemView';
|
||||
import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { ChildrenMap } from './StructureTree';
|
||||
import { genOperation } from '../../operations';
|
||||
import { EditorServices } from '../../types';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
type Props = {
|
||||
component: ComponentSchema;
|
||||
parentId: string | undefined;
|
||||
slot: string | undefined;
|
||||
childrenMap: ChildrenMap;
|
||||
onSelectComponent: (id: string) => void;
|
||||
onSelected?: (id: string) => void;
|
||||
services: EditorServices;
|
||||
@ -20,31 +19,14 @@ type Props = {
|
||||
depth: number;
|
||||
};
|
||||
type ComponentTreeProps = Props & {
|
||||
slotMap: Map<string, ComponentSchema[]>;
|
||||
isSelected: boolean;
|
||||
};
|
||||
|
||||
const observeSelected = (Component: React.FC<ComponentTreeProps>) => {
|
||||
const ObserveActive: React.FC<Props> = props => {
|
||||
const { services, component, onSelected } = props;
|
||||
const { editorStore } = services;
|
||||
const { selectedComponentId } = editorStore;
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedComponentId === component.id) {
|
||||
onSelected?.(selectedComponentId);
|
||||
}
|
||||
}, [selectedComponentId, component.id, onSelected]);
|
||||
|
||||
return <Component {...props} isSelected={selectedComponentId === component.id} />;
|
||||
};
|
||||
|
||||
return observer(ObserveActive);
|
||||
};
|
||||
|
||||
const ComponentTree = (props: ComponentTreeProps) => {
|
||||
const {
|
||||
component,
|
||||
childrenMap,
|
||||
slotMap,
|
||||
parentId,
|
||||
slot,
|
||||
isSelected,
|
||||
@ -71,10 +53,10 @@ const ComponentTree = (props: ComponentTreeProps) => {
|
||||
if (slots.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const children = childrenMap.get(component.id);
|
||||
|
||||
return slots.map(_slot => {
|
||||
let slotContent;
|
||||
const slotChildren = children?.get(_slot);
|
||||
const slotChildren = slotMap.get(_slot);
|
||||
if (slotChildren && slotChildren.length > 0) {
|
||||
slotContent = slotChildren.map(c => {
|
||||
return (
|
||||
@ -83,7 +65,6 @@ const ComponentTree = (props: ComponentTreeProps) => {
|
||||
component={c}
|
||||
parentId={component.id}
|
||||
slot={_slot}
|
||||
childrenMap={childrenMap}
|
||||
onSelectComponent={onSelectComponent}
|
||||
onSelected={onChildSelected}
|
||||
services={services}
|
||||
@ -130,7 +111,7 @@ const ComponentTree = (props: ComponentTreeProps) => {
|
||||
});
|
||||
}, [
|
||||
slots,
|
||||
childrenMap,
|
||||
slotMap,
|
||||
component.id,
|
||||
onSelectComponent,
|
||||
onChildSelected,
|
||||
@ -193,6 +174,51 @@ const ComponentTree = (props: ComponentTreeProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ComponentTreeWrapper: React.FC<Props> = observeSelected(
|
||||
React.memo(ComponentTree)
|
||||
const MemoComponentTree: React.FC<ComponentTreeProps> = React.memo(
|
||||
ComponentTree,
|
||||
(oldProps, props) => {
|
||||
const { slotMap: oldSlotMap, ...oldRest } = oldProps;
|
||||
const { slotMap, ...rest } = props;
|
||||
const oldKeys = [...oldSlotMap.keys()];
|
||||
const keys = [...slotMap.keys()];
|
||||
// check whether adding or removing the components
|
||||
const isHasSameSlots = oldKeys.length === keys.length;
|
||||
// check whether the properties of the child components have changed
|
||||
// if the properties aren't changed, then it must have the same object reference
|
||||
const isSameSlotMap =
|
||||
isHasSameSlots &&
|
||||
oldKeys.every(key => {
|
||||
const oldChildren = oldSlotMap.get(key) || [];
|
||||
const children = slotMap.get(key) || [];
|
||||
|
||||
return (
|
||||
oldChildren.length === children.length &&
|
||||
oldChildren.every((oldComponent, i) => oldComponent === children[i])
|
||||
);
|
||||
});
|
||||
|
||||
return isSameSlotMap && isEqual(oldRest, rest);
|
||||
}
|
||||
);
|
||||
|
||||
export const ComponentTreeWrapper: React.FC<Props> = observer(props => {
|
||||
const { services, component, onSelected } = props;
|
||||
const { editorStore } = services;
|
||||
const { selectedComponentId, resolvedComponents } = editorStore;
|
||||
const { childrenMap } = resolvedComponents;
|
||||
const slotMap = childrenMap.get(component.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedComponentId === component.id) {
|
||||
onSelected?.(selectedComponentId);
|
||||
}
|
||||
}, [selectedComponentId, component.id, onSelected]);
|
||||
|
||||
return (
|
||||
<MemoComponentTree
|
||||
{...props}
|
||||
isSelected={selectedComponentId === component.id}
|
||||
slotMap={slotMap || new Map()}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -3,10 +3,9 @@ import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { Box, Text, VStack } from '@chakra-ui/react';
|
||||
import { ComponentTreeWrapper } from './ComponentTree';
|
||||
import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { resolveApplicationComponents } from '../../utils/resolveApplicationComponents';
|
||||
import ErrorBoundary from '../ErrorBoundary';
|
||||
import { EditorServices } from '../../types';
|
||||
import { CORE_VERSION, CoreTraitName, CoreComponentName, memo } from '@sunmao-ui/shared';
|
||||
import { CORE_VERSION, CoreComponentName } from '@sunmao-ui/shared';
|
||||
import {
|
||||
AutoComplete,
|
||||
AutoCompleteInput,
|
||||
@ -15,7 +14,6 @@ import {
|
||||
type Item,
|
||||
} from '@choc-ui/chakra-autocomplete';
|
||||
import { css } from '@emotion/css';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import scrollIntoView from 'scroll-into-view';
|
||||
|
||||
export type ChildrenMap = Map<string, SlotsMap>;
|
||||
@ -32,47 +30,11 @@ const AutoCompleteStyle = css`
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
`;
|
||||
const SLOT_TYPE = `${CORE_VERSION}/${CoreTraitName.Slot}`;
|
||||
|
||||
const memoResolveApplicationComponents = memo(
|
||||
resolveApplicationComponents,
|
||||
function ([preRealComponents = []] = [], [realComponents]) {
|
||||
// if add or remove the component, the struct tree should update
|
||||
if (preRealComponents.length !== realComponents.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const i in realComponents) {
|
||||
const preComponent = preRealComponents[i];
|
||||
const component = realComponents[i];
|
||||
|
||||
if (preComponent !== component) {
|
||||
// there are two situations would cause the ids are different
|
||||
// 1. the component id is changed
|
||||
// 2. the order of components is changed. example: [a, b] -> [b, a]
|
||||
if (preComponent.id !== component.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// should check the slot container whether is changed too because move a component to a slot may not change the order
|
||||
const preSlotTrait = preComponent.traits.find(trait => trait.type === SLOT_TYPE);
|
||||
const slotTrait = component.traits.find(trait => trait.type === SLOT_TYPE);
|
||||
|
||||
if (
|
||||
!isEqual(preSlotTrait?.properties.container, slotTrait?.properties.container)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
export const StructureTree: React.FC<Props> = props => {
|
||||
const [search, setSearch] = useState('');
|
||||
const { components, onSelectComponent, services } = props;
|
||||
const { editorStore } = services;
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onSelectOption = useCallback(
|
||||
@ -109,8 +71,7 @@ export const StructureTree: React.FC<Props> = props => {
|
||||
}, [components]);
|
||||
|
||||
const componentEles = useMemo(() => {
|
||||
const { topLevelComponents, childrenMap } =
|
||||
memoResolveApplicationComponents(realComponents);
|
||||
const { topLevelComponents } = editorStore.resolvedComponents;
|
||||
|
||||
return topLevelComponents.map(c => (
|
||||
<ComponentTreeWrapper
|
||||
@ -118,7 +79,6 @@ export const StructureTree: React.FC<Props> = props => {
|
||||
component={c}
|
||||
parentId={undefined}
|
||||
slot={undefined}
|
||||
childrenMap={childrenMap}
|
||||
onSelectComponent={onSelectComponent}
|
||||
onSelected={onSelected}
|
||||
services={services}
|
||||
@ -126,7 +86,7 @@ export const StructureTree: React.FC<Props> = props => {
|
||||
depth={0}
|
||||
/>
|
||||
));
|
||||
}, [realComponents, onSelectComponent, onSelected, services]);
|
||||
}, [onSelectComponent, onSelected, services, editorStore.resolvedComponents]);
|
||||
|
||||
return (
|
||||
<VStack
|
||||
|
@ -15,6 +15,7 @@ import { ExplorerMenuTabs, ToolMenuTabs } from '../constants/enum';
|
||||
|
||||
import { CORE_VERSION, CoreComponentName } from '@sunmao-ui/shared';
|
||||
import { isEqual } from 'lodash';
|
||||
import { resolveApplicationComponents } from '../utils/resolveApplicationComponents';
|
||||
|
||||
type EditingTarget = {
|
||||
kind: 'app' | 'module';
|
||||
@ -112,6 +113,12 @@ export class EditorStore {
|
||||
this.updateCurrentEditingTarget('app', this.app.version, this.app.metadata.name);
|
||||
}
|
||||
|
||||
get resolvedComponents() {
|
||||
return resolveApplicationComponents(
|
||||
this.components.filter(c => c.type !== `${CORE_VERSION}/${CoreComponentName.Dummy}`)
|
||||
);
|
||||
}
|
||||
|
||||
get app() {
|
||||
return this.appStorage.app;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user