Skip to content

Commit

Permalink
perf: reduce re-rendering (#353)
Browse files Browse the repository at this point in the history
* perf: reduce re-rendering

* Create bright-eggs-shop.md
  • Loading branch information
cycleccc authored Nov 18, 2024
1 parent d4b184a commit 9fb698d
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 23 deletions.
6 changes: 6 additions & 0 deletions .changeset/bright-eggs-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@wangeditor-next/core": patch
"@wangeditor-next/editor": patch
---

perf: reduce re-rendering
87 changes: 70 additions & 17 deletions packages/core/src/text-area/update-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@
* @author wangfupeng
*/

import { Descendant, Range } from 'slate'
import { h, VNode } from 'snabbdom'

import { IDomEditor } from '../editor/interface'
import TextArea from './TextArea'
import { genPatchFn, normalizeVnodeData } from '../utils/vdom'
import $, { Dom7Array, getDefaultView, getElementById } from '../utils/dom'
import { node2Vnode } from '../render/node2Vnode'
import $, { Dom7Array, getDefaultView, getElementById } from '../utils/dom'
import { genPatchFn, normalizeVnodeData } from '../utils/vdom'
import {
EDITOR_TO_ELEMENT,
EDITOR_TO_WINDOW,
ELEMENT_TO_NODE,
IS_FIRST_PATCH,
NODE_TO_ELEMENT,
TEXTAREA_TO_PATCH_FN,
TEXTAREA_TO_SELECTION,
TEXTAREA_TO_VNODE,
EDITOR_TO_ELEMENT,
NODE_TO_ELEMENT,
ELEMENT_TO_NODE,
EDITOR_TO_WINDOW,
} from '../utils/weak-maps'
import TextArea from './TextArea'

function genElemId(id: number) {
return `w-e-textarea-${id}`
Expand All @@ -31,7 +34,7 @@ function genElemId(id: number) {
function genRootVnode(elemId: string, readOnly = false): VNode {
return h(`div#${elemId}`, {
props: {
contentEditable: readOnly ? false : true,
contentEditable: !readOnly,
},
})
// 其他属性在 genRootElem 中定,这里不用重复写
Expand All @@ -42,7 +45,7 @@ function genRootVnode(elemId: string, readOnly = false): VNode {
* @param elemId elemId
* @param readOnly readOnly
*/
function genRootElem(elemId: string, readOnly = false): Dom7Array {
function genRootElem(elemId: string, _readOnly = false): Dom7Array {
const $elem = $(`<div
id="${elemId}"
data-slate-editor
Expand All @@ -59,6 +62,32 @@ function genRootElem(elemId: string, readOnly = false): Dom7Array {
return $elem
}

function diffBySelection(
prevVnode: VNode,
content: Descendant[],
editor: IDomEditor,
): VNode[] {
const selection = editor.selection

if (!selection) {
return prevVnode.children as VNode[]
}

const { anchor, focus } = selection

// 确定更新范围
const startIndex = Math.min(anchor.path[0], focus.path[0])

// 克隆数组并更新指定位置
const newChildren = [...(prevVnode.children || [])] as VNode[]
const newNode = node2Vnode(content[startIndex], startIndex, editor, editor)

normalizeVnodeData(newNode)
newChildren[startIndex] = newNode

return newChildren
}

/**
* 获取 editor.children 渲染 DOM
* @param textarea textarea
Expand All @@ -72,24 +101,39 @@ function updateView(textarea: TextArea, editor: IDomEditor) {
// 生成 newVnode
const newVnode = genRootVnode(elemId, readOnly)
const content = editor.children || []
newVnode.children = content.map((node, i) => {
let vnode = node2Vnode(node, i, editor, editor)
normalizeVnodeData(vnode) // 整理 vnode.data 以符合 snabbdom 的要求
return vnode
})
const prevVnode = TEXTAREA_TO_VNODE.get(textarea) // 获取上一次的 vnode
const cacheSelection = TEXTAREA_TO_SELECTION.get(textarea)

if (
prevVnode
&& cacheSelection
&& Range.isCollapsed(cacheSelection)
) {
newVnode.children = diffBySelection(prevVnode, content, editor)
} else {
newVnode.children = content.map((node, i) => {
const vnode = node2Vnode(node, i, editor, editor)

normalizeVnodeData(vnode) // 整理 vnode.data 以符合 snabbdom 的要求
return vnode
})
}

let textareaElem
let isFirstPatch = IS_FIRST_PATCH.get(textarea)
if (isFirstPatch == null) isFirstPatch = true // 尚未赋值,也是第一次

if (isFirstPatch == null) { isFirstPatch = true } // 尚未赋值,也是第一次
if (isFirstPatch) {
// 第一次 patch ,先生成 elem
const $textArea = genRootElem(elemId, readOnly)

$scroll.append($textArea)
textarea.$textArea = $textArea // 存储下编辑区域的 DOM 节点
textareaElem = $textArea[0]

// 再生成 patch 函数,并执行
const patchFn = genPatchFn()

patchFn(textareaElem, newVnode)

// 存储相关信息
Expand All @@ -99,7 +143,8 @@ function updateView(textarea: TextArea, editor: IDomEditor) {
// 不是第一次 patch
const curVnode = TEXTAREA_TO_VNODE.get(textarea)
const patchFn = TEXTAREA_TO_PATCH_FN.get(textarea)
if (curVnode == null || patchFn == null) return

if (curVnode == null || patchFn == null) { return }
textareaElem = curVnode.elm

patchFn(curVnode, newVnode)
Expand All @@ -109,11 +154,12 @@ function updateView(textarea: TextArea, editor: IDomEditor) {
textareaElem = getElementById(elemId)

// 通过 getElementById 获取的有可能是 null (销毁、重建时,可能会发生这种情况)
if (textareaElem == null) return
if (textareaElem == null) { return }
}

// focus
let isFocused

if (isFirstPatch) {
// 初次渲染
isFocused = autoFocus
Expand All @@ -130,9 +176,16 @@ function updateView(textarea: TextArea, editor: IDomEditor) {
// 存储相关信息
if (isFirstPatch) {
const window = getDefaultView(textareaElem)

// eslint-disable-next-line no-unused-expressions
window && EDITOR_TO_WINDOW.set(editor, window)
}

const selection = editor.selection

if (selection) {
TEXTAREA_TO_SELECTION.set(textarea, selection)
}
EDITOR_TO_ELEMENT.set(editor, textareaElem) // 存储 editor -> elem 对应关系
NODE_TO_ELEMENT.set(editor, textareaElem)
ELEMENT_TO_NODE.set(textareaElem, editor)
Expand Down
16 changes: 10 additions & 6 deletions packages/core/src/utils/weak-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
*/

import { Emitter } from 'event-emitter'
import {
Ancestor, Editor, Node, Path, Range,
} from 'slate'
import { VNode } from 'snabbdom'
import { Node, Ancestor, Editor, Path, Range } from 'slate'

import { IEditorConfig } from '../config/interface'
import { IDomEditor } from '../editor/interface'
import TextArea from '../text-area/TextArea'
import Toolbar from '../menus/bar/Toolbar'
import HoverBar from '../menus/bar/HoverBar'
import Toolbar from '../menus/bar/Toolbar'
import { IBarItem } from '../menus/bar-item/index'
import { Key } from './key'
import { PatchFn } from '../utils/vdom'
import { IEditorConfig } from '../config/interface'
import PanelAndModal from '../menus/panel-and-modal/BaseClass'
import TextArea from '../text-area/TextArea'
import { PatchFn } from '../utils/vdom'
import { Key } from './key'

// textarea - editor
export const EDITOR_TO_TEXTAREA = new WeakMap<IDomEditor, TextArea>()
Expand Down Expand Up @@ -74,6 +77,7 @@ export const CHANGING_NODE_PATH: WeakMap<Editor, Path> = new WeakMap()

// 保存 editor -> selection ,用于还原 editor 选区
export const EDITOR_TO_SELECTION: WeakMap<Editor, Range> = new WeakMap()
export const TEXTAREA_TO_SELECTION: WeakMap<TextArea, Range> = new WeakMap()

// editor -> eventEmitter 自定义事件
export const EDITOR_TO_EMITTER: WeakMap<Editor, Emitter> = new WeakMap()
Expand Down

0 comments on commit 9fb698d

Please sign in to comment.