React函数组件:父组件访问子组件状态及JSX数组创建问题
我是React新手,用函数组件开发时遇到了问题:父组件通过useState存储子组件的初始化值,渲染正常,但现在要访问子组件输入框的值,尝试了几种方法都报错,想问问思路是不是错了,也猜测用useRef但不确定怎么用。
现有代码
父组件代码
function ParentComponent() { const [optionInputArr, setOptionInputArr] = useState < IOptionInput[] > (initialOptions); return ( <div> { optionInputArr.map((item) => { return <OptionInput optionInput={item} key={item.key} /> }) } </div> ) }
子组件代码
function OptionInput(props: {optionInput: IOptionInput}){ return ( <> <input></input> </> ); } export default OptionInput;
尝试的错误方案及报错
方案1:把组件实例存在状态数组
定义状态:
const[optionInputArr2, setOptionInputArr2] = useState<typeof OptionInput[]>([]);
初始化组件的函数:
const initializeElems = ()=>{ let props = {optionInput: { name: "", key: "", className: "", type: "", placeholder: "" }} let option1 = OptionInput(props); option1.key = props.optionInput.key; setOptionInputArr2(option1); // <-- 报错 }
报错信息:
Argument of type 'Element' is not assignable to parameter of type
'SetStateAction<((props: { optionInput: IOptionInput; }) =>
Element)[]>'.ts(2345)
方案2:用React.createElement创建组件
let elem = React.createElement("OptionInput",props); setOptionInputArr2(elem); // <---- 报错
报错信息:
Argument of type 'ReactElement<{ optionInput: { name: string; key:
string; className: string; type: string; placeholder: string; }; },
string | JSXElementConstructor>' is not assignable to parameter
of type 'SetStateAction<((props: { optionInput: IOptionInput; }) =>
Element)[]>'
需求
- 父组件可访问每个子组件的状态
- 子组件拥有自身状态
我的疑问
我的思路是不是有误?另外猜测用useRef实现,但不确定怎么通过它访问子组件。
解决方案
你的思路确实走偏了,React里不要把组件实例存在状态里,状态应该存数据,而不是组件元素。针对你的需求,有两种常用方案:
方案1:受控组件(推荐)
把子组件的状态提升到父组件,子组件通过props接收值和onChange回调,这样父组件天然拥有所有子组件的状态。
修改子组件:
function OptionInput(props: { optionInput: IOptionInput; value: string; onChange: (value: string) => void; }){ return ( <> <input value={props.value} onChange={(e) => props.onChange(e.target.value)} placeholder={props.optionInput.placeholder} className={props.optionInput.className} /> </> ); }
修改父组件,维护每个子组件的输入值:
function ParentComponent() { // 假设initialOptions是带key的数组,这里扩展为每个项带value const [optionInputArr, setOptionInputArr] = useState<(IOptionInput & {value: string})[]>( initialOptions.map(opt => ({...opt, value: ""})) ); // 处理子组件输入变化 const handleInputChange = (key: string, newValue: string) => { setOptionInputArr(prev => prev.map(item => item.key === key ? {...item, value: newValue} : item ) ); }; return ( <div> { optionInputArr.map((item) => { return ( <OptionInput key={item.key} optionInput={item} value={item.value} onChange={(val) => handleInputChange(item.key, val)} /> ) }) } {/* 这里可以直接访问所有子组件的值 */} <button onClick={() => console.log(optionInputArr.map(item => item.value))}> 打印所有输入值 </button> </div> ) }
方案2:用useRef保存子组件的引用(非受控场景)
如果确实需要子组件维护自身状态,同时父组件能访问,可以通过forwardRef和useImperativeHandle暴露子组件的方法或状态。
修改子组件:
import { forwardRef, useImperativeHandle, useState } from 'react'; // 定义父组件能调用的方法类型 type OptionInputHandle = { getInputValue: () => string; }; const OptionInput = forwardRef<OptionInputHandle, {optionInput: IOptionInput}>((props, ref) => { const [inputValue, setInputValue] = useState(""); // 暴露给父组件的方法 useImperativeHandle(ref, () => ({ getInputValue: () => inputValue })); return ( <> <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder={props.optionInput.placeholder} className={props.optionInput.className} /> </> ); }); export default OptionInput;
修改父组件,用useRef数组保存每个子组件的引用:
import { useState, useRef } from 'react'; import OptionInput from './OptionInput'; function ParentComponent() { const [optionInputArr, setOptionInputArr] = useState < IOptionInput[] > (initialOptions); // 创建ref数组,保存每个子组件的引用 const inputRefs = useRef<(OptionInputHandle | null)[]>([]); // 获取所有子组件的值 const getAllInputValues = () => { const values = inputRefs.current.map(ref => ref?.getInputValue() || ""); console.log(values); }; return ( <div> { optionInputArr.map((item, index) => { return ( <OptionInput key={item.key} optionInput={item} ref={(el) => inputRefs.current[index] = el} /> ) }) } <button onClick={getAllInputValues}>打印所有输入值</button> </div> ) }
总结
- 优先用受控组件,把状态放在父组件,这是React的标准数据流模式,更易维护和调试。
- 非受控场景再考虑
useRef+forwardRef的方案,但要注意这种方式会打破单向数据流,尽量少用。 - 绝对不要把组件元素存在
useState里,状态只存数据,组件渲染靠数据驱动。
内容的提问来源于stack exchange,提问作者sander

