起初,我的英语不好。请理解。
我也遇到了这个问题,所以我试图找到一个解决方法。
我只是猜测原因可能是 dom 事件和 react.js 重新渲染之间的时间间隔,但我无法确定。
关于解决方法的一个关键点是在 componentDidUpdate 生命周期中手动设置/获取光标位置。
下面是示例代码。
我希望它会有所帮助。
module NotWorks =
type MsgX = | InputX of string
| InputX2 of string
let subjectX , observableX = FSharp.Control.AsyncRx.subject<MsgX>()
type ModelX = {
input : string
input2 : string
}
let updateX (modelX : ModelX) (msgX : MsgX) : ModelX =
match msgX with
| MsgX.InputX(s) -> { modelX with input = (s) }
| MsgX.InputX2(s) -> { modelX with input2 = (s) }
type ComponentX(modelX : ModelX) as this =
inherit Fable.React.Component<ModelX, unit>(modelX) with
override _.render() : ReactElement =
div []
[
textarea
[
OnChange this.OnChange
Value this.props.input
Rows 10
Cols 40
]
[]
textarea
[
OnChange this.OnChange2
Value this.props.input2
Rows 10
Cols 40
]
[]
]
override _.componentDidUpdate(_ : ModelX, _ : unit) : unit =
()
member _.OnChange(e : Browser.Types.Event) : unit =
let target = e.target :?> Browser.Types.HTMLTextAreaElement
async {
// in here, no interval. but the problem can appeared sometimes.
do! subjectX.OnNextAsync(MsgX.InputX(target.value))
} |> Async.Start
member _.OnChange2(e : Browser.Types.Event) : unit =
let target = e.target :?> Browser.Types.HTMLTextAreaElement
let x = target.value
async {
do! Async.Sleep(1) // this makes interval. the problem appears always.
do! subjectX.OnNextAsync(MsgX.InputX2(x))
} |> Async.Start
let viewX (modelX : ModelX) (_ : Dispatch<MsgX>) : ReactElement =
Fable.React.Helpers.ofType<ComponentX, ModelX, unit>(modelX) []
let componentX =
Fable.Reaction.Reaction.StreamView
{input = ""; input2 = ""}
viewX
updateX
(fun _ o ->
o
|> FSharp.Control.AsyncRx.merge observableX
|> Fable.Reaction.AsyncRx.tag "x"
)
module Works =
type MsgX = | InputX of string * int * int
let subjectX , observableX = FSharp.Control.AsyncRx.subject<MsgX>()
type ModelX = {
input : string * int * int
}
let updateX (modelX : ModelX) (msgX : MsgX) : ModelX =
match msgX with
| MsgX.InputX(s, start, ``end``) -> { modelX with input = (s, start, ``end``) }
type ComponentX(modelX : ModelX) as this =
inherit Fable.React.Component<ModelX, unit>(modelX) with
// we need a ref to get/set cursor position.
let mutable refTextArea : Option<Browser.Types.HTMLTextAreaElement> = None
override _.render() : ReactElement =
let s, _, _ = this.props.input
textarea
[
Props.Ref(fun e -> refTextArea <- Some(e :?> Browser.Types.HTMLTextAreaElement))
OnChange this.OnChange
Value s
Rows 10
Cols 40
]
[]
override _.componentDidUpdate(_ : ModelX, _ : unit) : unit =
// set cursor position manually using saved model data.
refTextArea
|> Option.iter (fun elem ->
let _, start, ``end`` = this.props.input // must use current model data but not previous model data.
elem.selectionStart <- start;
elem.selectionEnd <- ``end``
)
member _.OnChange(e : Browser.Types.Event) : unit =
let target = e.target :?> Browser.Types.HTMLTextAreaElement
// save cursor position.
let x = target.value
let start = target.selectionStart
let ``end``= target.selectionEnd
async {
do! subjectX.OnNextAsync(MsgX.InputX(x, start, ``end``))
} |> Async.Start
let viewX (modelX : ModelX) (_ : Dispatch<MsgX>) : ReactElement =
Fable.React.Helpers.ofType<ComponentX, ModelX, unit>(modelX) []
let componentX =
Fable.Reaction.Reaction.StreamView
{input = "", 0, 0}
viewX
updateX
(fun _ o ->
o
|> FSharp.Control.AsyncRx.merge observableX
|> Fable.Reaction.AsyncRx.tag "x"
)