React客户端组件设置输入状态时单次仅能输入一个字符问题
问题:输入框每次只能输入一个字符,需重新点击才能继续
复现组件说明
这是一个'use client'的Next.js组件,核心逻辑:
- 从dataSource获取数据
- 将数据存入本地状态
currentDataChange - 把
currentDataChange映射为input元素
问题现象
修改输入框值时,映射生成的组件会触发重渲染,导致每次只能输入一个字符,之后必须重新点击输入框才能继续输入下一个字符。这套逻辑在纯React里运行正常,不清楚问题出在哪,也不知道怎么阻止不必要的重渲染。
已尝试的方案
- 单独声明映射函数
- 添加额外状态存储值
- 调换映射数组与值的顺序
组件代码
"use client"; export default function SampleComponent() { type DataSourceType = string[]; const useDataSource = (): DataSourceType => { return ["item1", "item2", "item3"]; }; const dataSource = useDataSource(); const [currentDataChange, setCurrentDataChange] = useState<DataSourceType>([]); useEffect(() => { if (dataSource) setCurrentDataChange(dataSource); }, []); const DataMap = () => { if (!currentDataChange) return null; return currentDataChange.map((value, index) => ( <input key={index} value={value} onChange={(e) => { setCurrentDataChange((curr) => { const newData = [...curr]; newData[index] = e.target.value; return newData; }); }} /> )); }; return ( <div> <DataMap /> </div> ); }
问题原因及修复方案
核心原因
DataMap组件定义在SampleComponent内部,每次父组件重渲染时,DataMap都会被重新创建成一个全新的组件类型。React会直接卸载旧的DataMap实例,再挂载新的,这就导致输入框直接失去焦点。
另外还有两个不符合规范的地方:
useDataSource自定义Hook写在组件内部,违反了React Hook的使用规则useEffect依赖数组为空,虽然当前dataSource是固定值,但如果后续数据源动态变化,状态会无法同步更新
修复步骤
- 把
DataMap组件移到父组件外部,避免每次重渲染都生成新组件 - 将自定义Hook
useDataSource移到组件外部,遵循Hook使用规范 - 给
useEffect加上dataSource依赖,确保数据源变化时状态能同步更新
修复后的代码
"use client"; import { useState, useEffect } from "react"; // 自定义Hook移到组件外部 type DataSourceType = string[]; const useDataSource = (): DataSourceType => { return ["item1", "item2", "item3"]; }; // DataMap组件独立出来,通过props接收数据和回调 const DataMap = ({ data, onDataChange }: { data: DataSourceType; onDataChange: (index: number, value: string) => void }) => { if (!data) return null; return data.map((value, index) => ( <input key={index} value={value} onChange={(e) => { onDataChange(index, e.target.value); }} /> )); }; export default function SampleComponent() { const dataSource = useDataSource(); const [currentDataChange, setCurrentDataChange] = useState<DataSourceType>([]); useEffect(() => { if (dataSource) setCurrentDataChange(dataSource); }, [dataSource]); // 添加dataSource到依赖数组 const handleDataChange = (index: number, value: string) => { setCurrentDataChange((curr) => { const newData = [...curr]; newData[index] = value; return newData; }); }; return ( <div> <DataMap data={currentDataChange} onDataChange={handleDataChange} /> </div> ); }
额外优化建议
- 尽量别用
index作为组件的key,如果数据源里有唯一标识(比如id),用唯一标识做key会让组件渲染更稳定 - 如果
dataSource是静态数据,可以直接用它初始化useState,省去useEffect:
const [currentDataChange, setCurrentDataChange] = useState<DataSourceType>(useDataSource());
内容的提问来源于stack exchange,提问作者Aigarito
相关产品推荐
相关产品推荐

