From 67fb8a00654881e1a8cf57ab71b9940b884750bb Mon Sep 17 00:00:00 2001 From: MrWindlike Date: Thu, 12 May 2022 13:58:10 +0800 Subject: [PATCH] feat: improve the editor's performance avoid the unnecessary render and add the lazy load to some widgets --- .../src/components/Form/ArrayTable.tsx | 112 +++++++++++------- .../src/components/Widgets/EventWidget.tsx | 47 +++++--- .../src/components/Widgets/PopoverWidget.tsx | 36 ++++-- .../EventTraitForm/EventHandlerForm.tsx | 86 +++++++------- packages/editor/src/components/Editor.tsx | 15 ++- .../StructureTree/ComponentTree.tsx | 22 ++-- 6 files changed, 191 insertions(+), 127 deletions(-) diff --git a/packages/editor-sdk/src/components/Form/ArrayTable.tsx b/packages/editor-sdk/src/components/Form/ArrayTable.tsx index f33f5dc4..94a1c984 100644 --- a/packages/editor-sdk/src/components/Form/ArrayTable.tsx +++ b/packages/editor-sdk/src/components/Form/ArrayTable.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo, useCallback } from 'react'; import { css } from '@emotion/css'; import { IconButton, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react'; import { AddIcon } from '@chakra-ui/icons'; @@ -27,13 +27,74 @@ const TableRowStyle = css` } `; -type Props = WidgetProps & { +type ArrayTableProps = WidgetProps & { itemSpec: JSONSchema7; }; +type RowProps = ArrayTableProps & { + itemValue: any; + itemIndex: number; +}; -export const ArrayTable: React.FC = props => { - const { value, itemSpec, spec, level, path, children, onChange } = props; +const DEFAULT_KEYS = ['index']; + +const TableRow: React.FC = props => { + const { value, itemSpec, spec, level, path, children, itemValue, itemIndex, onChange } = + props; const { expressionOptions, displayedKeys = [] } = spec.widgetOptions || {}; + const keys = displayedKeys.length ? displayedKeys : DEFAULT_KEYS; + const mergedSpec = useMemo( + () => + mergeWidgetOptionsIntoSpec( + { + ...itemSpec, + title: '', + }, + { + expressionOptions, + } + ), + [itemSpec, expressionOptions] + ); + const nextPath = useMemo(() => path.concat(String(itemIndex)), [path, itemIndex]); + const onPopoverWidgetChange = useCallback( + (newItemValue: any) => { + const newValue = [...value]; + newValue[itemIndex] = newItemValue; + onChange(newValue); + }, + [itemIndex, onChange, value] + ); + + return ( + + + + {typeof children === 'function' ? children(props, itemValue, itemIndex) : null} + + + {keys.map((key: string) => { + const propertyValue = + key === 'index' ? itemValue[key] ?? itemIndex : itemValue[key]; + + return {propertyValue}; + })} + + + + + ); +}; + +export const ArrayTable: React.FC = props => { + const { value, itemSpec, spec, onChange } = props; + const { displayedKeys = [] } = spec.widgetOptions || {}; const keys = displayedKeys.length ? displayedKeys : ['index']; return ( @@ -63,43 +124,12 @@ export const ArrayTable: React.FC = props => { {value.map((itemValue: any, itemIndex: number) => ( - - - { - const newValue = [...value]; - newValue[itemIndex] = newItemValue; - onChange(newValue); - }} - > - {typeof children === 'function' - ? children(props, itemValue, itemIndex) - : null} - - - {keys.map((key: string) => { - const propertyValue = - key === 'index' ? itemValue[key] ?? itemIndex : itemValue[key]; - - return {propertyValue}; - })} - - - - + ))} diff --git a/packages/editor-sdk/src/components/Widgets/EventWidget.tsx b/packages/editor-sdk/src/components/Widgets/EventWidget.tsx index 45c2528f..9e4b7344 100644 --- a/packages/editor-sdk/src/components/Widgets/EventWidget.tsx +++ b/packages/editor-sdk/src/components/Widgets/EventWidget.tsx @@ -93,6 +93,10 @@ export const EventWidget: React.FC> = observ return params; }, [formik.values.method.name]); + const parametersPath = useMemo(()=> path.concat('method', 'parameters'), [path]); + const parametersSpec = useMemo(()=> mergeWidgetOptionsIntoSpec(paramsSpec, { onlySetValue: true }), [paramsSpec]); + const disabledPath = useMemo(()=> path.concat('disabled'), [path]); + const disabledSpec = useMemo(()=> Type.Boolean({ widgetOptions: { isShowAsideExpressionButton: true } }), []); const updateMethods = useCallback( (componentId: string) => { @@ -127,18 +131,29 @@ export const EventWidget: React.FC> = observ } }, [value, updateMethods]); - const onTargetComponentChange = (e: React.ChangeEvent) => { + const onTargetComponentChange = useCallback((e: React.ChangeEvent) => { updateMethods(e.target.value); formik.handleChange(e); formik.setFieldValue('method', { name: '', parameters: {} }); - }; + }, [updateMethods, formik]); + const onSubmit = useCallback(() => { + formik.submitForm(); + }, [formik]); + const onParametersChange = useCallback(json => { + formik.setFieldValue('method.parameters', json); + formik.submitForm(); + }, [formik]); + const onDisabledChange = useCallback(value => { + formik.setFieldValue('disabled', value); + formik.submitForm(); + }, [formik]); const typeField = ( Event Type formik.submitForm()} + onBlur={onSubmit} onChange={onTargetComponentChange} placeholder="Select Target Component" value={formik.values.componentId} @@ -174,7 +189,7 @@ export const EventWidget: React.FC> = observ Method formik.submitForm()} + onBlur={onSubmit} onChange={formik.handleChange} value={formik.values.wait?.type} > @@ -227,7 +239,7 @@ export const EventWidget: React.FC> = observ Wait Time formik.submitForm()} + onBlur={onSubmit} onChange={formik.handleChange} value={formik.values.wait?.time} /> @@ -239,14 +251,11 @@ export const EventWidget: React.FC> = observ Disabled { - formik.setFieldValue('disabled', value); - formik.submitForm(); - }} + onChange={onDisabledChange} /> ); diff --git a/packages/editor-sdk/src/components/Widgets/PopoverWidget.tsx b/packages/editor-sdk/src/components/Widgets/PopoverWidget.tsx index b5ec7b5b..0f3e2b74 100644 --- a/packages/editor-sdk/src/components/Widgets/PopoverWidget.tsx +++ b/packages/editor-sdk/src/components/Widgets/PopoverWidget.tsx @@ -1,4 +1,10 @@ -import React, { useState, useEffect, useCallback, useImperativeHandle } from 'react'; +import React, { + useState, + useEffect, + useMemo, + useCallback, + useImperativeHandle, +} from 'react'; import { Popover, PopoverTrigger, @@ -38,10 +44,20 @@ export const PopoverWidget = React.forwardRef< >((props, ref) => { const { spec, path, children } = props; const isObjectChildren = children && typeof children === 'object'; + const [isInit, setIsInit] = useState(false); const [isOpen, setIsOpen] = useState(false); + const mergedSpec = useMemo( + () => ({ + ...spec, + widget: + spec.widget === `${CORE_VERSION}/${CoreWidgetName.Popover}` ? '' : spec.widget, + }), + [spec] + ); const handleOpen = useCallback(() => { setIsOpen(true); + setIsInit(true); emitter.emit('other-popover-close', path); }, [path]); const handleClickTrigger = useCallback(event => { @@ -132,17 +148,13 @@ export const PopoverWidget = React.forwardRef< - {isObjectChildren && 'body' in children ? ( - (children as Children).body - ) : ( - - )} + {isInit ? ( + isObjectChildren && 'body' in children ? ( + (children as Children).body + ) : ( + + ) + ) : null} diff --git a/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx b/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx index a4d02ec8..56834f38 100644 --- a/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx +++ b/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { EventWidget } from '@sunmao-ui/editor-sdk'; import { Box, @@ -44,9 +44,13 @@ export const EventHandlerForm: React.FC = props => { onUp, onDown, } = props; + const [hasOpened, setHasOpened] = React.useState(false); + const onAccordionChange = useCallback(() => { + setHasOpened(true); + }, []); return ( - +

@@ -57,45 +61,47 @@ export const EventHandlerForm: React.FC = props => {

- - - - - - } - size="xs" - variant="ghost" - disabled={index === 0} - onClick={onUp} - /> - } - size="xs" - variant="ghost" - disabled={index === size - 1} - onClick={onDown} - /> - } - onClick={onRemove} - size="xs" - variant="ghost" - /> + {hasOpened ? ( + + + + + + } + size="xs" + variant="ghost" + disabled={index === 0} + onClick={onUp} + /> + } + size="xs" + variant="ghost" + disabled={index === size - 1} + onClick={onDown} + /> + } + onClick={onRemove} + size="xs" + variant="ghost" + /> + - + ) : null}
diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx index 625d33b4..1952ffcd 100644 --- a/packages/editor/src/components/Editor.tsx +++ b/packages/editor/src/components/Editor.tsx @@ -145,6 +145,12 @@ export const Editor: React.FC = observer( } }, [isDisplayApp]); const onPreview = useCallback(() => setPreview(true), []); + const onSelectComponent = useCallback(id => { + editorStore.setSelectedComponentId(id); + }, [editorStore]); + const onRightTabChange = useCallback(activatedTab => { + setToolMenuTab(activatedTab); + }, []); const renderMain = () => { const appBox = ( @@ -216,9 +222,7 @@ export const Editor: React.FC = observer( { - editorStore.setSelectedComponentId(id); - }} + onSelectComponent={onSelectComponent} services={services} /> @@ -257,9 +261,8 @@ export const Editor: React.FC = observer( display="flex" flexDirection="column" index={toolMenuTab} - onChange={activatedTab => { - setToolMenuTab(activatedTab); - }} + onChange={onRightTabChange} + isLazy > Inspect diff --git a/packages/editor/src/components/StructureTree/ComponentTree.tsx b/packages/editor/src/components/StructureTree/ComponentTree.tsx index f4ade1f5..840075cb 100644 --- a/packages/editor/src/components/StructureTree/ComponentTree.tsx +++ b/packages/editor/src/components/StructureTree/ComponentTree.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, useCallback } from 'react'; import { Box, Text, VStack } from '@chakra-ui/react'; import { ComponentSchema } from '@sunmao-ui/core'; import { ComponentItemView } from './ComponentItemView'; @@ -110,14 +110,20 @@ export const ComponentTree: React.FC = props => { isExpanded, ]); - const onClickRemove = () => { + const onClickRemove = useCallback(() => { eventBus.send( 'operation', genOperation(registry, 'removeComponent', { componentId: component.id, }) ); - }; + }, [component.id, eventBus, registry]); + const onClickItem = useCallback(() => { + onSelectComponent(component.id); + }, [component.id, onSelectComponent]); + const onToggleExpanded = useCallback(() => setIsExpanded(prev => !prev), []); + const onDragStart = useCallback(() => setIsDragging(true), []); + const onDragEnd = useCallback(() => setIsDragging(false), []); return ( = props => { id={component.id} title={component.id} isSelected={component.id === selectedComponentId} - onClick={() => { - onSelectComponent(component.id); - }} + onClick={onClickItem} onClickRemove={onClickRemove} noChevron={slots.length === 0} isExpanded={isExpanded} - onToggleExpanded={() => setIsExpanded(prev => !prev)} - onDragStart={() => setIsDragging(true)} - onDragEnd={() => setIsDragging(false)} + onToggleExpanded={onToggleExpanded} + onDragStart={onDragStart} + onDragEnd={onDragEnd} depth={depth} />