Skip to content

Commit

Permalink
perf: reduce re-rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
cycleccc committed Nov 17, 2024
1 parent 3827ccb commit 1d11ac0
Showing 1 changed file with 77 additions and 17 deletions.
94 changes: 77 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,23 @@
* @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_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 +33,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 +44,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 +61,52 @@ function genRootElem(elemId: string, readOnly = false): Dom7Array {
return $elem
}

let cacheSelection:Range | null = null

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

if (!selection) { return prevVnode.children as VNode[] } // 如果没有 selection,直接返回原 vnode

const { anchor, focus } = selection

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

// 新的 children 数组
const newChildren: VNode[] = []
const prevChildren = prevVnode.children || [] // 原来的 children

if (cacheSelection && Range.isCollapsed(cacheSelection) && content.length !== prevChildren.length) {
content.forEach((node, index) => {
if (index === startIndex) {
const newNode = node2Vnode(node, index, editor, editor)

normalizeVnodeData(newNode)
newChildren.push(newNode)
} else {
newChildren.push(prevChildren[index] as VNode)
}
})
return newChildren
}
content.forEach((node, index) => {
const newNode = node2Vnode(node, index, editor, editor)

normalizeVnodeData(newNode)
newChildren.push(newNode)
})

cacheSelection = selection // 缓存 selection

// 返回新的 vnode
return newChildren
}

/**
* 获取 editor.children 渲染 DOM
* @param textarea textarea
Expand All @@ -72,24 +120,32 @@ 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

if (prevVnode) { 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 +155,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 +166,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,6 +188,8 @@ 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)
}

Expand Down

0 comments on commit 1d11ac0

Please sign in to comment.