
在react应用中,开发者有时会遇到一个令人困扰的问题:当用户在输入框(<input>)中键入一个字符后,输入框会立即失去焦点,用户需要再次点击才能继续输入或编辑。这极大地影响了用户体验。
根据提供的代码片段,我们可以观察到以下关键点:
// State to store DataSource
const [dataSource, setDataSource] = useState<any>(data);
const handleOnchange = (event: any, props: any) => {
const newData = [...dataSource];
const itemIndex = newData.findIndex(
(item) => item.OrderID === props.OrderID
);
newData[itemIndex].Freight = event.target.value;
setDataSource(newData); // 每次输入都更新了全局状态
};
// Custom Grid Component (render prop or function)
const gridTemplate = (props: any) => {
const val = props.Freight;
return (
<div>
<input value={val} onChange={(event) => handleOnchange(event, props)} />
</div>
);
};此问题的核心在于React的受控组件(Controlled Components)机制与组件重渲染(Re-rendering)行为的结合。
当一个组件重渲染时,React会重新执行其渲染逻辑(即函数组件的函数体),并根据新的props和state生成新的JSX元素树。如果gridTemplate是一个函数而不是一个独立的组件,或者父组件没有正确使用key属性,或者即使使用了key,但因为dataSource的变化导致整个列表结构被认为发生了显著变化,React可能会决定销毁旧的<input>元素并重新挂载一个新的<input>元素。
关键点: 每次<input>元素被重新挂载(即从DOM中移除再添加),它都会失去焦点。这就是为什么在键入每个字符后都需要重新点击输入框的原因。
解决这个问题的关键在于:将输入框的即时值与全局数据状态分离。 输入框内部维护自己的值,只在特定时机(例如,输入框失去焦点时或用户按下Enter键时)才更新全局状态。
这种方法允许输入框在用户键入时平滑地更新其内部值,而不会频繁触发全局状态的重渲染,从而避免了焦点丢失。
以下是实现这一策略的示例代码:
import React, { useState, useEffect } from 'react';
// 模拟初始数据
const initialData = [
{ OrderID: 1, Freight: 100 },
{ OrderID: 2, Freight: 200 },
{ OrderID: 3, Freight: 300 },
];
/**
* 独立的 GridInput 组件
* 负责管理单个输入框的局部状态,并在特定事件时通知父组件更新全局状态
*/
const GridInput = ({ initialValue, onValueChange, orderId }) => {
// 使用局部状态 inputValue 来控制输入框的当前显示值
const [inputValue, setInputValue] = useState(initialValue);
// 当外部 initialValue 发生变化时(例如,数据从后端更新),同步到局部状态
// 注意:此 useEffect 确保了当 dataSource 在外部被更新时,GridInput 能够反映最新的值。
// 但在用户输入过程中,由于 onValueChange 只在 onBlur/onEnter 触发,
// initialValue 在用户键入时不会改变,因此不会干扰输入体验。
useEffect(() => {
setInputValue(initialValue);
}, [initialValue]);
// 处理输入框值变化的函数,只更新局部状态
const handleChange = (e) => {
setInputValue(e.target.value);
};
// 处理输入框失去焦点事件,此时通知父组件更新全局状态
const handleBlur = () => {
// 只有当输入框失去焦点时,才通过回调函数将最终值传递给父组件
onValueChange(orderId, inputValue);
};
// 处理键盘按下事件,特别是 Enter 键,也可以触发更新
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
// 按下 Enter 键也触发更新,并使输入框失去焦点
onValueChange(orderId, inputValue);
e.target.blur(); // 强制输入框失去焦点
}
};
return (
<input
type="text" // 明确指定输入类型
value={inputValue}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
style={{ border: '1px solid #ccc', padding: '5px' }} // 简单的样式
/>
);
};
/**
* 父组件,负责管理 dataSource 状态并渲染 GridInput 列表
*/
const MyGridComponent = () => {
const [dataSource, setDataSource] = useState(initialData);
// 更新 dataSource 中特定 Freight 值的回调函数
const handleUpdateFreight = (orderId, newFreight) => {
setDataSource((prevDataSource) => {
const newData = [...prevDataSource];
const itemIndex = newData.findIndex(
(item) => item.OrderID === orderId
);
if (itemIndex > -1) {
// 创建新对象以避免直接修改原对象,确保 immutability
newData[itemIndex] = { ...newData[itemIndex], Freight: newFreight };
}
return newData;
});
};
return (
<div style={{ fontFamily: 'Arial, sans-serif', padding: '20px' }}>
<h2>订单运费列表</h2>
{dataSource.map((item) => (
<div key={item.OrderID} style={{ marginBottom: '10px', border: '1px solid #eee', padding: '10px' }}>
<span style={{ marginRight: '10px', fontWeight: 'bold' }}>订单ID: {item.OrderID}</span>
<span style={{ marginRight: '10px' }}>运费:</span>
<GridInput
initialValue={item.Freight}
onValueChange={handleUpdateFreight}
orderId={item.OrderID}
/>
</div>
))}
<h3 style={{ marginTop: '30px' }}>当前数据源:</h3>
<pre style={{ background: '#f4f4f4', padding: '10px', borderRadius: '5px' }}>
{JSON.stringify(dataSource, null, 2)}
</pre>
</div>
);
};
export default MyGridComponent;代码解析:
以上就是React输入框焦点丢失问题:深入解析与解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号