mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-17 17:40:31 +08:00
feat: avoid the unnecessary render when the selected component changes
This commit is contained in:
parent
c6c5fae8b2
commit
7c463749e7
@ -6,26 +6,43 @@ import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { ChildrenMap } from './StructureTree';
|
||||
import { genOperation } from '../../operations';
|
||||
import { EditorServices } from '../../types';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
type Props = {
|
||||
component: ComponentSchema;
|
||||
parentId: string | undefined;
|
||||
slot: string | undefined;
|
||||
childrenMap: ChildrenMap;
|
||||
selectedComponentId: string;
|
||||
onSelectComponent: (id: string) => void;
|
||||
services: EditorServices;
|
||||
isAncestorDragging: boolean;
|
||||
depth: number;
|
||||
};
|
||||
type ComponentTreeProps = Props & {
|
||||
isSelected: boolean;
|
||||
};
|
||||
|
||||
export const ComponentTree: React.FC<Props> = props => {
|
||||
const observeSelected = (Component: React.FC<ComponentTreeProps>) => {
|
||||
const ObserveActive: React.FC<Props> = props => {
|
||||
const { services } = props;
|
||||
const { editorStore } = services;
|
||||
const { selectedComponentId } = editorStore;
|
||||
|
||||
return (
|
||||
<Component {...props} isSelected={selectedComponentId === props.component.id} />
|
||||
);
|
||||
};
|
||||
|
||||
return observer(ObserveActive);
|
||||
};
|
||||
|
||||
const ComponentTree = (props: ComponentTreeProps) => {
|
||||
const {
|
||||
component,
|
||||
childrenMap,
|
||||
parentId,
|
||||
slot,
|
||||
selectedComponentId,
|
||||
isSelected,
|
||||
onSelectComponent,
|
||||
services,
|
||||
isAncestorDragging,
|
||||
@ -47,13 +64,12 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
if (slotChildren && slotChildren.length > 0) {
|
||||
slotContent = slotChildren.map(c => {
|
||||
return (
|
||||
<ComponentTree
|
||||
<ComponentTreeWrapper
|
||||
key={c.id}
|
||||
component={c}
|
||||
parentId={component.id}
|
||||
slot={_slot}
|
||||
childrenMap={childrenMap}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={onSelectComponent}
|
||||
services={services}
|
||||
isAncestorDragging={isAncestorDragging || isDragging}
|
||||
@ -89,7 +105,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
return (
|
||||
<Box key={_slot} paddingLeft="3" width="full">
|
||||
{/* although component can have multiple slots, but for now, most components have only one slot
|
||||
so we hide slot name to save more view area */}
|
||||
so we hide slot name to save more view area */}
|
||||
{slots.length > 1 ? slotName : undefined}
|
||||
<VStack spacing="0" width="full" alignItems="start">
|
||||
{slotContent}
|
||||
@ -101,7 +117,6 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
slots,
|
||||
childrenMap,
|
||||
component.id,
|
||||
selectedComponentId,
|
||||
onSelectComponent,
|
||||
services,
|
||||
isAncestorDragging,
|
||||
@ -146,7 +161,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
<ComponentItemView
|
||||
id={component.id}
|
||||
title={component.id}
|
||||
isSelected={component.id === selectedComponentId}
|
||||
isSelected={isSelected}
|
||||
onClick={onClickItem}
|
||||
onClickRemove={onClickRemove}
|
||||
noChevron={slots.length === 0}
|
||||
@ -161,3 +176,11 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ComponentTreeWrapper: React.FC<Props> = observeSelected(
|
||||
React.memo(ComponentTree, (prevProps, nextProps) => {
|
||||
return (Object.keys(prevProps) as (keyof typeof prevProps)[]).every(
|
||||
key => prevProps[key] === nextProps[key]
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import React, { useMemo, useRef, useState, useCallback } from 'react';
|
||||
import { genOperation } from '../../operations';
|
||||
import { EditorServices } from '../../types';
|
||||
|
||||
@ -31,83 +31,99 @@ export const DropComponentWrapper: React.FC<Props> = props => {
|
||||
const [dragDirection, setDragDirection] = useState<'prev' | 'next' | undefined>();
|
||||
const [isDragOver, setIsDragOver] = useState<boolean>(false);
|
||||
|
||||
const onDragOver = (e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
setIsDragOver(true);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const onDragOver = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
setIsDragOver(true);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isDropInOnly) return;
|
||||
if (isDropInOnly) return;
|
||||
|
||||
const rect = ref.current?.getBoundingClientRect();
|
||||
const rect = ref.current?.getBoundingClientRect();
|
||||
|
||||
if (!rect) return;
|
||||
if (!rect) return;
|
||||
|
||||
if (e.clientY < rect.top + rect.height / 2) {
|
||||
setDragDirection('prev');
|
||||
} else if (e.clientY >= rect.top + rect.height / 2) {
|
||||
setDragDirection('next');
|
||||
}
|
||||
};
|
||||
if (e.clientY < rect.top + rect.height / 2) {
|
||||
setDragDirection('prev');
|
||||
} else if (e.clientY >= rect.top + rect.height / 2) {
|
||||
setDragDirection('next');
|
||||
}
|
||||
},
|
||||
[droppable, isDropInOnly]
|
||||
);
|
||||
|
||||
const onDragLeave = () => {
|
||||
const onDragLeave = useCallback(() => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
setDragDirection(undefined);
|
||||
setIsDragOver(false);
|
||||
};
|
||||
}, [droppable]);
|
||||
|
||||
const onDrop = (e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const creatingComponent = e.dataTransfer?.getData('component') || '';
|
||||
const movingComponent = e.dataTransfer?.getData('moveComponent') || '';
|
||||
const onDrop = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const creatingComponent = e.dataTransfer?.getData('component') || '';
|
||||
const movingComponent = e.dataTransfer?.getData('moveComponent') || '';
|
||||
|
||||
let targetParentId = parentId;
|
||||
let targetParentSlot = parentSlot;
|
||||
let targetId = componentId;
|
||||
if (dragDirection === 'next' && isExpanded && hasSlot) {
|
||||
targetParentId = componentId;
|
||||
targetParentSlot = 'content';
|
||||
targetId = undefined;
|
||||
}
|
||||
let targetParentId = parentId;
|
||||
let targetParentSlot = parentSlot;
|
||||
let targetId = componentId;
|
||||
if (dragDirection === 'next' && isExpanded && hasSlot) {
|
||||
targetParentId = componentId;
|
||||
targetParentSlot = 'content';
|
||||
targetId = undefined;
|
||||
}
|
||||
|
||||
// move component before or after currentComponent
|
||||
if (movingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'moveComponent', {
|
||||
fromId: movingComponent,
|
||||
toId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
// move component before or after currentComponent
|
||||
if (movingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'moveComponent', {
|
||||
fromId: movingComponent,
|
||||
toId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// create component as children
|
||||
if (creatingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'createComponent', {
|
||||
componentType: creatingComponent,
|
||||
parentId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
setDragDirection(undefined);
|
||||
setIsDragOver(false);
|
||||
};
|
||||
// create component as children
|
||||
if (creatingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'createComponent', {
|
||||
componentType: creatingComponent,
|
||||
parentId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
setDragDirection(undefined);
|
||||
setIsDragOver(false);
|
||||
},
|
||||
[
|
||||
droppable,
|
||||
dragDirection,
|
||||
isExpanded,
|
||||
hasSlot,
|
||||
componentId,
|
||||
parentId,
|
||||
parentSlot,
|
||||
eventBus,
|
||||
registry,
|
||||
]
|
||||
);
|
||||
|
||||
const boxShadow = useMemo(() => {
|
||||
if (isDropInOnly) return '';
|
||||
@ -124,7 +140,7 @@ export const DropComponentWrapper: React.FC<Props> = props => {
|
||||
ref={ref}
|
||||
width="full"
|
||||
boxShadow={boxShadow}
|
||||
background={ isDragOver ? '#ffc6c6' : undefined}
|
||||
background={isDragOver ? '#ffc6c6' : undefined}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useMemo, useRef, useEffect } from 'react';
|
||||
import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { Box, Text, VStack } from '@chakra-ui/react';
|
||||
import { ComponentTree } from './ComponentTree';
|
||||
import { ComponentTreeWrapper } from './ComponentTree';
|
||||
import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { resolveApplicationComponents } from '../../utils/resolveApplicationComponents';
|
||||
import ErrorBoundary from '../ErrorBoundary';
|
||||
@ -33,20 +33,19 @@ export const StructureTree: React.FC<Props> = props => {
|
||||
resolveApplicationComponents(realComponents);
|
||||
|
||||
return topLevelComponents.map(c => (
|
||||
<ComponentTree
|
||||
<ComponentTreeWrapper
|
||||
key={c.id}
|
||||
component={c}
|
||||
parentId={undefined}
|
||||
slot={undefined}
|
||||
childrenMap={childrenMap}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={onSelectComponent}
|
||||
services={services}
|
||||
isAncestorDragging={false}
|
||||
depth={0}
|
||||
/>
|
||||
));
|
||||
}, [realComponents, selectedComponentId, onSelectComponent, services]);
|
||||
}, [realComponents, onSelectComponent, services]);
|
||||
|
||||
useEffect(() => {
|
||||
wrapperRef.current
|
||||
|
Loading…
Reference in New Issue
Block a user