From a2184d86571cfd0d89beb67863c444a988174937 Mon Sep 17 00:00:00 2001 From: mainhanu Date: Wed, 19 Oct 2022 20:38:34 +0800 Subject: [PATCH] transform.delete and transform.insertFragment performance optimize (#5137) * feat: transform.delete and transform.insertFragment performance optimize * feat: add changeset * feat: optimize code Co-authored-by: mainhanu --- .changeset/new-needles-itch.md | 5 + packages/slate/src/interfaces/path.ts | 217 +++++++++++++------------- packages/slate/src/transforms/text.ts | 9 +- 3 files changed, 118 insertions(+), 113 deletions(-) create mode 100644 .changeset/new-needles-itch.md diff --git a/.changeset/new-needles-itch.md b/.changeset/new-needles-itch.md new file mode 100644 index 000000000..2f17c5c4a --- /dev/null +++ b/.changeset/new-needles-itch.md @@ -0,0 +1,5 @@ +--- +'slate': minor +--- + +transform.delete and transform.insertFragment performance optimize diff --git a/packages/slate/src/interfaces/path.ts b/packages/slate/src/interfaces/path.ts index 6453fe922..fa4929162 100644 --- a/packages/slate/src/interfaces/path.ts +++ b/packages/slate/src/interfaces/path.ts @@ -1,4 +1,3 @@ -import { produce } from 'immer' import { Operation } from '..' import { TextDirection } from './types' @@ -373,125 +372,125 @@ export const Path: PathInterface = { operation: Operation, options: PathTransformOptions = {} ): Path | null { - return produce(path, p => { - const { affinity = 'forward' } = options + if (!path) return null - // PERF: Exit early if the operation is guaranteed not to have an effect. - if (!path || path?.length === 0) { - return - } + // PERF: use destructing instead of immer + const p = [...path] + const { affinity = 'forward' } = options - if (p === null) { - return null - } + // PERF: Exit early if the operation is guaranteed not to have an effect. + if (path.length === 0) { + return p + } - switch (operation.type) { - case 'insert_node': { - const { path: op } = operation + switch (operation.type) { + case 'insert_node': { + const { path: op } = operation - if ( - Path.equals(op, p) || - Path.endsBefore(op, p) || - Path.isAncestor(op, p) - ) { - p[op.length - 1] += 1 - } - - break + if ( + Path.equals(op, p) || + Path.endsBefore(op, p) || + Path.isAncestor(op, p) + ) { + p[op.length - 1] += 1 } - case 'remove_node': { - const { path: op } = operation + break + } - if (Path.equals(op, p) || Path.isAncestor(op, p)) { + case 'remove_node': { + const { path: op } = operation + + if (Path.equals(op, p) || Path.isAncestor(op, p)) { + return null + } else if (Path.endsBefore(op, p)) { + p[op.length - 1] -= 1 + } + + break + } + + case 'merge_node': { + const { path: op, position } = operation + + if (Path.equals(op, p) || Path.endsBefore(op, p)) { + p[op.length - 1] -= 1 + } else if (Path.isAncestor(op, p)) { + p[op.length - 1] -= 1 + p[op.length] += position + } + + break + } + + case 'split_node': { + const { path: op, position } = operation + + if (Path.equals(op, p)) { + if (affinity === 'forward') { + p[p.length - 1] += 1 + } else if (affinity === 'backward') { + // Nothing, because it still refers to the right path. + } else { return null - } else if (Path.endsBefore(op, p)) { - p[op.length - 1] -= 1 } - - break + } else if (Path.endsBefore(op, p)) { + p[op.length - 1] += 1 + } else if (Path.isAncestor(op, p) && path[op.length] >= position) { + p[op.length - 1] += 1 + p[op.length] -= position } - case 'merge_node': { - const { path: op, position } = operation - - if (Path.equals(op, p) || Path.endsBefore(op, p)) { - p[op.length - 1] -= 1 - } else if (Path.isAncestor(op, p)) { - p[op.length - 1] -= 1 - p[op.length] += position - } - - break - } - - case 'split_node': { - const { path: op, position } = operation - - if (Path.equals(op, p)) { - if (affinity === 'forward') { - p[p.length - 1] += 1 - } else if (affinity === 'backward') { - // Nothing, because it still refers to the right path. - } else { - return null - } - } else if (Path.endsBefore(op, p)) { - p[op.length - 1] += 1 - } else if (Path.isAncestor(op, p) && path[op.length] >= position) { - p[op.length - 1] += 1 - p[op.length] -= position - } - - break - } - - case 'move_node': { - const { path: op, newPath: onp } = operation - - // If the old and new path are the same, it's a no-op. - if (Path.equals(op, onp)) { - return - } - - if (Path.isAncestor(op, p) || Path.equals(op, p)) { - const copy = onp.slice() - - if (Path.endsBefore(op, onp) && op.length < onp.length) { - copy[op.length - 1] -= 1 - } - - return copy.concat(p.slice(op.length)) - } else if ( - Path.isSibling(op, onp) && - (Path.isAncestor(onp, p) || Path.equals(onp, p)) - ) { - if (Path.endsBefore(op, p)) { - p[op.length - 1] -= 1 - } else { - p[op.length - 1] += 1 - } - } else if ( - Path.endsBefore(onp, p) || - Path.equals(onp, p) || - Path.isAncestor(onp, p) - ) { - if (Path.endsBefore(op, p)) { - p[op.length - 1] -= 1 - } - - p[onp.length - 1] += 1 - } else if (Path.endsBefore(op, p)) { - if (Path.equals(onp, p)) { - p[onp.length - 1] += 1 - } - - p[op.length - 1] -= 1 - } - - break - } + break } - }) + + case 'move_node': { + const { path: op, newPath: onp } = operation + + // If the old and new path are the same, it's a no-op. + if (Path.equals(op, onp)) { + return p + } + + if (Path.isAncestor(op, p) || Path.equals(op, p)) { + const copy = onp.slice() + + if (Path.endsBefore(op, onp) && op.length < onp.length) { + copy[op.length - 1] -= 1 + } + + return copy.concat(p.slice(op.length)) + } else if ( + Path.isSibling(op, onp) && + (Path.isAncestor(onp, p) || Path.equals(onp, p)) + ) { + if (Path.endsBefore(op, p)) { + p[op.length - 1] -= 1 + } else { + p[op.length - 1] += 1 + } + } else if ( + Path.endsBefore(onp, p) || + Path.equals(onp, p) || + Path.isAncestor(onp, p) + ) { + if (Path.endsBefore(op, p)) { + p[op.length - 1] -= 1 + } + + p[onp.length - 1] += 1 + } else if (Path.endsBefore(op, p)) { + if (Path.equals(onp, p)) { + p[onp.length - 1] += 1 + } + + p[op.length - 1] -= 1 + } + + break + } + } + + return p }, } diff --git a/packages/slate/src/transforms/text.ts b/packages/slate/src/transforms/text.ts index c64e61fd1..8fcc271a1 100644 --- a/packages/slate/src/transforms/text.ts +++ b/packages/slate/src/transforms/text.ts @@ -187,10 +187,11 @@ export const TextTransforms: TextTransforms = { } } - for (const pathRef of pathRefs) { - const path = pathRef.unref()! - Transforms.removeNodes(editor, { at: path, voids }) - } + pathRefs + .reverse() + .map(r => r.unref()) + .filter((r): r is Path => r !== null) + .forEach(p => Transforms.removeNodes(editor, { at: p, voids })) if (!endVoid) { const point = endRef.current!