两个不同useState setter函数联合类型报错问题求助
我有两个React组件Apartment和House,它们的状态都包含共同的pictures属性(字符串数组类型)。我编写了FileUploader组件,希望接收父组件的useState setter函数来更新pictures数组。但在为FileUploader的setFunction属性定义联合类型React.Dispatch<React.SetStateAction<HouseType> | React.SetStateAction<ApartmentType>>时,TypeScript抛出类型不兼容错误:
Type 'Dispatch<SetStateAction<HouseType>>' is not assignable to type 'Dispatch<SetStateAction<ApartmentType> | SetStateAction<HouseType>>'. Type 'SetStateAction<ApartmentType> | SetStateAction<HouseType>' is not assignable to type 'SetStateAction<HouseType>'. Type 'ApartmentType' is not assignable to type 'SetStateAction<HouseType>'. Property 'owner' is missing in type 'ApartmentType' but required in type 'HouseType'
原以为联合类型能覆盖两种情况,但实际不行,请问哪里出错了?相关代码如下:
Apartment.ts
type ApartmentType = { landlord: string; pictures: string[]; } function Apartment () { const [ apartmentData, setApartmentData ] = useState<ApartmentType>({ landlord: 'juan', pictures: [ 'url1', 'url2' ] }) return ( <h1>Apartment</h1> <FileUploader setFunction={setApartmentData} /> ) }
House.ts
type HouseType = { owner: string; pictures: string[]; } function House () { const [ houseData, setHouseData ] = useState<HouseType>({ owner: 'Jhon', pictures: [ 'url1', 'url2' ] }) return ( <h1>House</h1> <FileUploader setFunction={setHouseData} /> ) }
FileUploader.ts
type FileUploaderProps = { setFunction: React.Dispatch<React.SetStateAction<HouseType> | React.SetStateAction<ApartmentType>> } function FileUploader ({ setFunction }: FileUploaderProps) { function updatePictures () { setFunction((prevValue: any) => ({ ...prevValue, pictures: [ ...prevValue.pictures, 'newUrl1', 'newUrl2'] })) } return ( <div> <h1>File Uploader</h1> <button type='button' onClick={updatePictures}></button> </div> ) }
问题出在TypeScript中函数参数的逆变特性:如果函数A能接受类型B的参数,函数B能接受类型A的参数(且A是B的子类型),那么函数A是函数B的子类型。
你定义的setFunction类型是Dispatch<SetStateAction<HouseType> | SetStateAction<ApartmentType>>,意味着这个函数必须能接受HouseType或ApartmentType任意一种类型的更新操作。但父组件的setHouseData是Dispatch<SetStateAction<HouseType>>,它只能处理HouseType相关的更新,无法接受ApartmentType的输入——如果FileUploader用ApartmentType调用setHouseData,会直接导致类型错误,所以TypeScript拒绝了这个赋值。
方案1:泛型约束(推荐)
让FileUploader成为泛型组件,约束泛型类型必须包含pictures属性,TypeScript会自动匹配父组件的状态类型:
// 定义共同属性的约束类型 type HasPictures = { pictures: string[]; }; // 泛型Props类型 type FileUploaderProps<T extends HasPictures> = { setFunction: React.Dispatch<React.SetStateAction<T>>; }; // 泛型组件 function FileUploader<T extends HasPictures>({ setFunction }: FileUploaderProps<T>) { function updatePictures() { setFunction((prevValue) => ({ ...prevValue, pictures: [...prevValue.pictures, 'newUrl1', 'newUrl2'] })); } return ( <div> <h1>File Uploader</h1> <button type='button' onClick={updatePictures}>添加图片</button> </div> ); }
父组件使用时无需额外指定类型,TypeScript会自动推断:
// Apartment组件中 <FileUploader setFunction={setApartmentData} /> // House组件中 <FileUploader setFunction={setHouseData} />
方案2:调整联合类型的结构
如果不想用泛型,可以将setFunction定义为两个Dispatch类型的联合(而非Dispatch包裹联合类型):
type FileUploaderProps = { setFunction: React.Dispatch<React.SetStateAction<HouseType>> | React.Dispatch<React.SetStateAction<ApartmentType>>; };
此时updatePictures中的prevValue会被推断为HouseType | ApartmentType,由于我们只操作共同的pictures属性,TypeScript会允许这个操作。
方案3:解耦组件职责
把图片更新逻辑交给父组件处理,FileUploader只负责通知父组件添加新图片,彻底解耦状态结构:
type FileUploaderProps = { onAddPictures: (newPictures: string[]) => void; }; function FileUploader({ onAddPictures }: FileUploaderProps) { function updatePictures() { onAddPictures(['newUrl1', 'newUrl2']); } return ( <div> <h1>File Uploader</h1> <button type='button' onClick={updatePictures}>添加图片</button> </div> ); }
父组件中使用:
// Apartment组件 <FileUploader onAddPictures={(newPics) => setApartmentData(prev => ({...prev, pictures: [...prev.pictures, ...newPics]}))} /> // House组件 <FileUploader onAddPictures={(newPics) => setHouseData(prev => ({...prev, pictures: [...prev.pictures, ...newPics]}))} />
内容的提问来源于stack exchange,提问作者GhostOrder

