fix(components-react): 修复 Input 组件受控模式下无法输入中文#18731
Conversation
Walkthrough修改 Input 组件以引入局部状态 Changes
Sequence Diagram(s)sequenceDiagram
participant User as 用户
participant IME as 输入法编辑器
participant Input as Input 组件
participant DOM as DOM
User->>IME: 开始输入(非直接字符)
IME->>Input: compositionstart
Input->>Input: isOnComposition = true
User->>IME: 继续输入/修改
IME->>Input: compositionupdate
Input->>Input: 更新 compositionValue
Input->>DOM: 渲染 compositionValue(displayValue)
User->>IME: 确认/结束合成
IME->>Input: compositionend
Input->>Input: isOnComposition = false
Input->>Input: 调用 triggerValueChange(finalValue, e)
Input->>DOM: 更新真实 value 并触发 onInput 事件
代码审阅工作量🎯 4 (复杂) | ⏱️ ~45 分钟 建议审阅者
贺诗
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @packages/taro-components-react/src/components/input/index.tsx:
- Around line 144-173: The compositionend branch in handleComposition sets
this.onInputExecuted = true but relies on a subsequent handleInput to clear it,
which can fail in some WebViews; update handleComposition (compositionend
branch) to schedule a microtask fallback (e.g., Promise.resolve().then(...))
that clears this.onInputExecuted if it remains true after the event loop tick so
the flag cannot get stuck, keeping the existing immediate-true behavior for
normal browsers while ensuring eventual reset in environments that don't fire
the input event.
🧹 Nitpick comments (2)
packages/taro-components-react/src/components/input/index.tsx (2)
100-142:triggerValueChange修改 DOM 值时建议补一个更稳的边界与类型约束
目前仅对number做 maxlength 截断并回写e.target.value,整体 OK;但建议对maxlength <= 0、value非字符串(上游传入)做兜底,避免出现意外空串/异常。建议性改动(更稳的边界/类型兜底)
triggerValueChange (value: string, e: any) { @@ - let finalValue = value + let finalValue = value ?? '' + if (typeof finalValue !== 'string') finalValue = String(finalValue) const inputType = getTrueType(type, confirmType, password) @@ - if (inputType === 'number' && finalValue && maxlength <= finalValue.length) { + if (inputType === 'number' && maxlength > 0 && finalValue && maxlength <= finalValue.length) { finalValue = finalValue.substring(0, maxlength) // 如果被截断了,需要同步回 DOM if (e.target && e.target.value !== finalValue) { e.target.value = finalValue } }
238-265:compositionupdate里复用handleInput有副作用耦合,建议直接 setState(可选)
现在compositionupdate调handleInput会额外执行stopPropagation、以及受onInputExecuted早退逻辑影响;更直接的做法是在 compositionupdate 里只做setState({ compositionValue: e.target.value })。可选简化
} else if (e.type === 'compositionupdate') { // 拼音输入过程中,保持标记并更新显示 this.isOnComposition = true - // 必须在这里触发 setState 才能让输入框里的拼音实时更新 - this.handleInput(e) + // 直接更新显示值,避免复用 handleInput 带来的副作用耦合 + this.setState({ compositionValue: e.target.value }) } else if (e.type === 'compositionend') {
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/taro-components-react/src/components/input/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build Rust Binding / stable - x86_64-pc-windows-msvc
🔇 Additional comments (2)
packages/taro-components-react/src/components/input/index.tsx (2)
40-69: 组合输入引入本地compositionValue状态的方向正确
这套“组合输入期间用内部 state 渲染、结束后再对外触发”的策略能对症解决受控模式下中文输入显示/上屏问题。
293-342:displayValue(compositionValue 优先)+ 绑定 onCompositionUpdate 的渲染路径合理
受控 value 在组合输入期间不再覆盖输入框显示,且补齐onCompositionUpdate,整体符合预期;另外 omit 掉onInput再由内部统一转发也能避免重复触发。
a592745 to
faf8d0c
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/taro-components-react/src/components/input/index.tsx (1)
100-142: 注释与实现不一致,容易造成维护误解。第124行注释说明"只有当值确实改变,或者需要强制触发时才调用",但第125-141行的实现中,只要
typeof onInput === 'function'为真就直接调用onInput(e),没有任何值变更检查。该注释没有准确反映代码的实际行为,容易误导后续维护。
🧹 Nitpick comments (2)
packages/taro-components-react/src/components/input/index.tsx (2)
45-50: 注释顺序与代码实现不一致。第49行注释描述的操作顺序为
compositionend -> triggerValueChange(外部回调) -> onInputExecuted = true,但实际代码中(line 260-263)是先设置this.onInputExecuted = true再调用this.triggerValueChange()。建议修正注释以避免混淆:♻️ 建议的注释修正
-空格选词 (中文输入法): compositionend -> triggerValueChange(外部回调) -> onInputExecuted = true -> 紧随其后的 handleInput 被拦截退出。 +空格选词 (中文输入法): compositionend -> onInputExecuted = true -> triggerValueChange(外部回调) -> 紧随其后的 handleInput 被拦截退出。
144-173: handleInput 核心逻辑正确,但标志位使用可以优化。整体逻辑清晰地处理了 composition 期间和普通输入两种场景。不过在第163-171行有个小问题:
this.onInputExecuted = true // ... this.triggerValueChange(newValue, e) this.onInputExecuted = false这里将
onInputExecuted设为 true 后立即在同步调用triggerValueChange后重置为 false。由于triggerValueChange只是调用props.onInput回调,不太可能同步触发新的 input 事件,这个立即重置看起来是多余的。相比之下,
compositionend中设置该标志后不立即重置(line 260),而是依赖后续的handleInput调用来重置(line 149)。这种不一致可能引起维护困扰。建议考虑:
- 如果确认
triggerValueChange不会同步触发事件,可以移除 Case 2 中的标志位设置- 或者在注释中说明为什么需要这样处理
♻️ 简化建议(如确认不需要防护)
} else { // Case 2: 普通输入 (英文、数字、或中文选词后) - // 标记执行,防止重复 - this.onInputExecuted = true // 清理中间状态 if (this.state.compositionValue !== undefined) { this.setState({ compositionValue: undefined }) } this.triggerValueChange(newValue, e) - this.onInputExecuted = false }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/taro-components-react/src/components/input/index.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-06T06:55:44.077Z
Learnt from: Single-Dancer
Repo: NervJS/taro PR: 17653
File: packages/taro-components-advanced/src/components/water-flow/node.ts:83-95
Timestamp: 2025-05-06T06:55:44.077Z
Learning: 在 Taro 的 water-flow 组件中,getRectSizeSync 函数如果找不到指定的节点并且重试次数用尽,会一直处于 pending 状态而不是抛出错误,因为它在 retryTimes <= 0 时直接返回而不 resolve 或 reject Promise。应该使用 Promise.race 添加超时机制来解决这个问题。
Applied to files:
packages/taro-components-react/src/components/input/index.tsx
📚 Learning: 2025-05-06T06:55:44.077Z
Learnt from: Single-Dancer
Repo: NervJS/taro PR: 17653
File: packages/taro-components-advanced/src/components/water-flow/node.ts:83-95
Timestamp: 2025-05-06T06:55:44.077Z
Learning: 在 Taro 的 water-flow 组件中,getRectSizeSync 函数如果找不到指定的节点,会一直处于 pending 状态而不是抛出错误。在这种情况下,应该使用 Promise.race 添加超时机制,而不是仅依赖 try/catch 来处理错误。
Applied to files:
packages/taro-components-react/src/components/input/index.tsx
📚 Learning: 2025-05-06T06:55:44.077Z
Learnt from: Single-Dancer
Repo: NervJS/taro PR: 17653
File: packages/taro-components-advanced/src/components/water-flow/node.ts:83-95
Timestamp: 2025-05-06T06:55:44.077Z
Learning: 在 Taro 的 getRectSizeSync 函数中,如果找不到指定的节点,函数会一直处于 pending 状态而不是抛出错误。在这种情况下,使用 try/catch 处理不够充分,需要添加超时处理如 Promise.race 来避免无限等待。
Applied to files:
packages/taro-components-react/src/components/input/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: Build Rust Binding / stable - x86_64-pc-windows-msvc
- GitHub Check: Build Rust Binding / stable - x86_64-unknown-linux-gnu
- GitHub Check: Build Rust Binding / stable - x86_64-apple-darwin
- GitHub Check: Build Rust Binding / stable - x86_64-unknown-linux-musl
- GitHub Check: Build Rust WASM / stable - wasm32-wasi
- GitHub Check: Build Rust Binding / stable - x86_64-pc-windows-msvc
- GitHub Check: Build Rust Binding / stable - x86_64-unknown-linux-musl
- GitHub Check: Build Rust Binding / stable - x86_64-unknown-linux-gnu
- GitHub Check: Build Rust WASM / stable - wasm32-wasi
🔇 Additional comments (5)
packages/taro-components-react/src/components/input/index.tsx (5)
40-42: 接口定义清晰。IState 接口设计合理,compositionValue 作为可选字段能够准确表示 IME 输入的临时状态。
53-69: 构造函数初始化正确。状态初始化和标志位设置合理,注释清晰说明了 onInputExecuted 的用途。
175-236: 事件处理器中的标志位重置合理。在
handlePaste、handleFocus和handleKeyDown中重置onInputExecuted标志,能够防止标志位异常保持导致后续输入被阻塞,这是防御性编程的良好实践。
280-344: 渲染逻辑正确实现了 IME 输入显示。关键改动:
- 从 state 中获取
compositionValue(line 293)- 计算
displayValue:优先显示compositionValue,否则显示受控的value(lines 313-315)- 将 input 的 value 绑定到
displayValue(line 332)- 添加
onCompositionUpdate事件处理(line 339)这个实现是整个方案的核心,正确地在 composition 期间显示 IME 中间输入(如拼音),在 composition 结束后显示最终值,完美解决了受控组件下中文输入的问题。
238-265: Composition 事件处理逻辑完全正确。该方法精妙地处理了 IME 输入的完整生命周期,通过
isOnComposition和onInputExecuted两个标记,完美适配 Chrome 和其他浏览器的事件顺序差异:
- compositionstart → compositionupdate → compositionend 的标准流程得到正确实现,compositionupdate 时通过 handleInput 只更新内部拼音显示状态,不向外触发 onChange
- 防重复触发机制在 compositionend 设置
onInputExecuted = true,后续 handleInput 中消费此标记,确保浏览器的 onChange 事件不会导致二次回调- 最终值通知通过 compositionend 中的 triggerValueChange 确保父组件收到最终汉字,而非拼音临时状态
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #18731 +/- ##
=======================================
Coverage 56.32% 56.32%
=======================================
Files 447 447
Lines 23345 23345
Branches 5795 5795
=======================================
Hits 13149 13149
- Misses 8366 8376 +10
+ Partials 1830 1820 -10
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
这个 PR 做了什么? (简要描述所做更改)
修复 Input 组件受控模式下无法输入中文
这个 PR 是什么类型? (至少选择一个)
这个 PR 涉及以下平台:
Summary by CodeRabbit
发布说明
Bug Fixes
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.