首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在React中处理具有受控内容可编辑性的游标

在React中处理具有受控内容可编辑性的游标
EN

Stack Overflow用户
提问于 2021-09-19 12:59:34
回答 2查看 67关注 0票数 0

我正在尝试在React中设置一个受控的contentEditable。每次我在div中编写一些内容时,组件都会重新呈现,并且光标/插入符号会跳回到开头。我试图通过将光标保存在onInput回调中来处理此问题:

代码语言:javascript
运行
复制
import { useState, useEffect, useRef, useLayoutEffect } from 'react'

function App() {
    const [HTML, setHTML] = useState()
    const [selectionRange, setSelectionRange] = useState()
    console.log('on rerender:', selectionRange)

    useLayoutEffect(() => {
        console.log('in layout effect', selectionRange)
        const selection = document.getSelection()
        if (selectionRange !== undefined) {
            selection.removeAllRanges()
            selection.addRange(selectionRange)
        }
    })

    function inputHandler(ev) {
        console.log('on input', document.getSelection().getRangeAt(0))
        setSelectionRange(document.getSelection().getRangeAt(0).cloneRange())
        setHTML(ev.target.innerHTML)
    }

    return (
        <>
            <div
                contentEditable
                suppressContentEditableWarning
                onInput={inputHandler}
                dangerouslySetInnerHTML={{ __html: HTML }}
            >
            </div>
            <div>html:{HTML}</div>
        </>
    )
}

export default App

这不起作用,光标仍然停留在开头。如果我在contentEditable目录中输入一个字符,就会得到输出:

代码语言:javascript
运行
复制
on input 
Range { commonAncestorContainer: #text, startContainer: #text, startOffset: 1, endContainer: #text
, endOffset: 1, collapsed: true }
on rerender: 
Range { commonAncestorContainer: #text, startContainer: #text, startOffset: 1, endContainer: #text
, endOffset: 1, collapsed: true }
in layout effect 
Range { commonAncestorContainer: div, startContainer: div, startOffset: 0, endContainer: div, endOffset: 0, collapsed: true }

为什么selectionRange的值在重新渲染开始时是正确的,但在useLayoutEffect回调中却发生了变化?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-09-19 16:36:54

contentEditable div被重新渲染时,它将消失。Range对象包含对该div的子级(startNodeendNode属性)的引用,当该div消失时,Range对象会跟踪该div,并将其自身重置为其父目录,且偏移量为零。

下面的代码演示了如何处理这个问题,如果您现在知道contentEditable div只有一个子级。它修复了光标在开头卡住的问题。我们要做的是将偏移量保存在文本中,在恢复时,我们创建一个新的Range对象,新呈现的文本节点为startNode,保存的偏移量为startOffset

代码语言:javascript
运行
复制
import { useState, useEffect, useRef, useLayoutEffect } from 'react'

function App() {
    const [HTML, setHTML] = useState()
    const [offset, setOffset] = useState()
    const textRef = useRef()

    useLayoutEffect(() => {
        if (offset !== undefined) {
            const newRange = document.createRange()
            newRange.setStart(textRef.current.childNodes[0], offset)
            const selection = document.getSelection()
            selection.removeAllRanges()
            selection.addRange(newRange)
        }
    })

    function inputHandler(ev) {
        const range = document.getSelection().getRangeAt(0)
        setOffset(range.startOffset)
        setHTML(ev.target.innerHTML)
    }

    return (
        <>
            <div
                contentEditable
                suppressContentEditableWarning
                onInput={inputHandler}
                dangerouslySetInnerHTML={{ __html: HTML }}
                ref={textRef}
            >
            </div>
            <div>html:{HTML}</div>
        </>
    )
}

export default App
票数 0
EN

Stack Overflow用户

发布于 2021-09-19 13:12:45

好吧,我不熟悉范围操作,但在我看来,问题在于状态的改变。

你可以使用useRefuseState来解决这个问题,现在让我在useState中使用一个对象。

代码语言:javascript
运行
复制
  function App() {
    const [HTML, setHTML] = useState()
    const [selectionRange, setSelectionRange] = useState({ range: null })

    useLayoutEffect(() => {
        const selection = document.getSelection()
        if (selectionRange !== undefined) {
            selection.removeAllRanges()
            if (selectionRange.range) 
              selection.addRange(selectionRange.range)
        }
    })

    function inputHandler(ev) {
        selectionRange.range = document.getSelection().getRangeAt(0).cloneRange())
        setSelectionRange({ ...selectionRange })
        setHTML(ev.target.innerHTML)
    }

你可以很容易地用useRef替换这个版本,关键是要确保在通过setState之前立即赋值,这需要时间来将你的状态更新到最新的值。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69243509

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档