Procházet zdrojové kódy

Disable UI rendering when history is skipped (#574)

When we are scrolling, resizing, or moving elements, we already disable the history. Since those actions do not change the state of the UI, we can also avoid re-drawing it and save ~10ms per frame.

I had to change all the forceUpdate() to setState({}), otherwise it would bypass shouldComponentUpdate.
Christopher Chedeau před 5 roky
rodič
revize
8ab176b9a5
1 změnil soubory, kde provedl 33 přidání a 18 odebrání
  1. 33 18
      src/index.tsx

+ 33 - 18
src/index.tsx

@@ -180,7 +180,7 @@ export class App extends React.Component<any, AppState> {
   private syncActionResult = (res: ActionResult) => {
     if (res.elements !== undefined) {
       elements = res.elements;
-      this.forceUpdate();
+      this.setState({});
     }
 
     if (res.appState !== undefined) {
@@ -199,7 +199,7 @@ export class App extends React.Component<any, AppState> {
       ),
     );
     elements = deleteSelectedElements(elements);
-    this.forceUpdate();
+    this.setState({});
     e.preventDefault();
   };
   private onCopy = (e: ClipboardEvent) => {
@@ -226,6 +226,14 @@ export class App extends React.Component<any, AppState> {
     this.saveDebounced.flush();
   };
 
+  public shouldComponentUpdate() {
+    if (!history.isRecording()) {
+      this.componentDidUpdate();
+      return false;
+    }
+    return true;
+  }
+
   public async componentDidMount() {
     document.addEventListener("copy", this.onCopy);
     document.addEventListener("paste", this.onPaste);
@@ -253,7 +261,7 @@ export class App extends React.Component<any, AppState> {
     if (data.appState) {
       this.setState(data.appState);
     } else {
-      this.forceUpdate();
+      this.setState({});
     }
   }
 
@@ -275,7 +283,7 @@ export class App extends React.Component<any, AppState> {
   public state: AppState = getDefaultAppState();
 
   private onResize = () => {
-    this.forceUpdate();
+    this.setState({});
   };
 
   private updateCurrentCursorPosition = (e: MouseEvent) => {
@@ -286,7 +294,7 @@ export class App extends React.Component<any, AppState> {
   private onKeyDown = (event: KeyboardEvent) => {
     if (event.key === KEYS.ESCAPE && !this.state.draggingElement) {
       elements = clearSelection(elements);
-      this.forceUpdate();
+      this.setState({});
       this.setState({ elementType: "selection" });
       if (window.document.activeElement instanceof HTMLElement) {
         window.document.activeElement.blur();
@@ -320,7 +328,7 @@ export class App extends React.Component<any, AppState> {
         }
         return el;
       });
-      this.forceUpdate();
+      this.setState({});
       event.preventDefault();
     } else if (
       shapesShortcutKeys.includes(event.key.toLowerCase()) &&
@@ -505,7 +513,7 @@ export class App extends React.Component<any, AppState> {
                 elements = clearSelection(elements);
                 document.documentElement.style.cursor =
                   value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
-                this.forceUpdate();
+                this.setState({});
               }}
             ></ToolButton>
           );
@@ -695,7 +703,7 @@ export class App extends React.Component<any, AppState> {
             if (!element.isSelected) {
               elements = clearSelection(elements);
               element.isSelected = true;
-              this.forceUpdate();
+              this.setState({});
             }
 
             ContextMenu.push({
@@ -737,6 +745,8 @@ export class App extends React.Component<any, AppState> {
                 let deltaY = lastY - e.clientY;
                 lastX = e.clientX;
                 lastY = e.clientY;
+                // We don't want to save history when panning around
+                history.skipRecording();
                 this.setState(state => ({
                   scrollX: state.scrollX - deltaX,
                   scrollY: state.scrollY - deltaY,
@@ -941,6 +951,8 @@ export class App extends React.Component<any, AppState> {
               if (isOverHorizontalScrollBar) {
                 const x = e.clientX - CANVAS_WINDOW_OFFSET_LEFT;
                 const dx = x - lastX;
+                // We don't want to save history when scrolling
+                history.skipRecording();
                 this.setState(state => ({ scrollX: state.scrollX - dx }));
                 lastX = x;
                 return;
@@ -949,6 +961,8 @@ export class App extends React.Component<any, AppState> {
               if (isOverVerticalScrollBar) {
                 const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP;
                 const dy = y - lastY;
+                // We don't want to save history when scrolling
+                history.skipRecording();
                 this.setState(state => ({ scrollY: state.scrollY - dy }));
                 lastY = y;
                 return;
@@ -1051,7 +1065,7 @@ export class App extends React.Component<any, AppState> {
                   lastY = y;
                   // We don't want to save history when resizing an element
                   history.skipRecording();
-                  this.forceUpdate();
+                  this.setState({});
                   return;
                 }
               }
@@ -1072,7 +1086,7 @@ export class App extends React.Component<any, AppState> {
                   lastY = y;
                   // We don't want to save history when dragging an element to initially size it
                   history.skipRecording();
-                  this.forceUpdate();
+                  this.setState({});
                   return;
                 }
               }
@@ -1127,7 +1141,7 @@ export class App extends React.Component<any, AppState> {
               }
               // We don't want to save history when moving an element
               history.skipRecording();
-              this.forceUpdate();
+              this.setState({});
             };
 
             const onMouseUp = (e: MouseEvent) => {
@@ -1152,12 +1166,11 @@ export class App extends React.Component<any, AppState> {
                 this.setState({
                   draggingElement: null,
                 });
-                this.forceUpdate();
                 return;
               }
 
               if (normalizeDimensions(draggingElement)) {
-                this.forceUpdate();
+                this.setState({});
               }
 
               if (resizingElement && isInvisiblySmallElement(resizingElement)) {
@@ -1188,7 +1201,7 @@ export class App extends React.Component<any, AppState> {
               if (draggingElement === null) {
                 // if no element is clicked, clear the selection and redraw
                 elements = clearSelection(elements);
-                this.forceUpdate();
+                this.setState({});
                 return;
               }
 
@@ -1208,7 +1221,7 @@ export class App extends React.Component<any, AppState> {
               }
 
               history.resumeRecording();
-              this.forceUpdate();
+              this.setState({});
             };
 
             lastMouseUp = onMouseUp;
@@ -1218,7 +1231,7 @@ export class App extends React.Component<any, AppState> {
 
             // We don't want to save history on mouseDown, only on mouseUp when it's fully configured
             history.skipRecording();
-            this.forceUpdate();
+            this.setState({});
           }}
           onDoubleClick={e => {
             const { x, y } = viewportCoordsToSceneCoords(e, this.state);
@@ -1253,7 +1266,7 @@ export class App extends React.Component<any, AppState> {
               elements = elements.filter(
                 element => element.id !== elementAtPosition.id,
               );
-              this.forceUpdate();
+              this.setState({});
 
               textX =
                 this.state.scrollX +
@@ -1355,6 +1368,8 @@ export class App extends React.Component<any, AppState> {
   private handleWheel = (e: WheelEvent) => {
     e.preventDefault();
     const { deltaX, deltaY } = e;
+    // We don't want to save history when panning around
+    history.skipRecording();
     this.setState(state => ({
       scrollX: state.scrollX - deltaX,
       scrollY: state.scrollY - deltaY,
@@ -1412,7 +1427,7 @@ export class App extends React.Component<any, AppState> {
           return duplicate;
         }),
       ];
-      this.forceUpdate();
+      this.setState({});
     }
   };