React中如何获取两个ImageList子组件的状态供父组件保存时调用接口
方案1:useImperativeHandle 最小侵入实现(推荐,改动量最小)
该方案无需改动现有ImageList的内部状态维护逻辑,也不需要提升状态或新增Context,最适合当前不想调整现有功能、也不希望MyItem逻辑进一步臃肿的场景。
实现步骤:
- 改造ImageList组件,转发ref并暴露获取内部状态的方法,注意
key是React保留属性,不能作为自定义props传递,改名成listKey
const ImageList = React.forwardRef(({listKey}, ref) => { const [images, setImages] = useState([{imageKey: listKey, url:`/images/${listKey}`, fileData: null}]) function handleImageSelected(image){ setImages(prev => [...prev, image]) } // 给父组件暴露取值方法 useImperativeHandle(ref, () => ({ getCurrentImages: () => images })) return ( <> <ImageUploader onSelect={handleImageSelected}/> <SortedImageList images={images} /> </> ) })
- 父组件MyItem中创建两个ref对应两个ImageList实例,保存时直接取值即可
function MyItem() { const bannerImageRef = useRef() const iconImageRef = useRef() function handleSave() { // 直接获取两个组件的最新状态 const bannerImages = bannerImageRef.current.getCurrentImages() const iconImages = iconImageRef.current.getCurrentImages() // 组装参数调用后端接口即可 } return ( <> <TabPanel index={0} title="Detail"> <HeaderWithSaveButton onSaveClick={handleSave} /> <SomeTextContent/> </TabPanel> <TabPanel index={1} title="Images"> <ImageList listKey="banner" ref={bannerImageRef} /> <ImageList listKey="icon" ref={iconImageRef} /> </TabPanel> </> ) }
方案2:状态提升+自定义Hook封装
如果后续有其他组件需要共享两个图片列表的状态,可以做状态提升,同时把图片相关逻辑抽成独立的自定义Hook,不会增加MyItem的代码复杂度:
// 抽成独立的自定义Hook,和MyItem代码完全分离 function useImageListState() { const [bannerImages, setBannerImages] = useState([{imageKey: 'banner', url:`/images/banner`, fileData: null}]) const [iconImages, setIconImages] = useState([{imageKey: 'icon', url:`/images/icon`, fileData: null}]) const updateImages = useCallback((listKey, newImages) => { listKey === 'banner' ? setBannerImages(newImages) : setIconImages(newImages) }, []) const getAllImages = useCallback(() => ({ banner: bannerImages, icon: iconImages }), [bannerImages, iconImages]) return { updateImages, getAllImages } }
父组件仅需调用Hook,无需编写状态管理逻辑:
function MyItem() { const { updateImages, getAllImages } = useImageListState() function handleSave() { const allImages = getAllImages() // 调用接口即可 } return ( <> <TabPanel index={0} title="Detail"> <HeaderWithSaveButton onSaveClick={handleSave} /> <SomeTextContent/> </TabPanel> <TabPanel index={1} title="Images"> <ImageList listKey="banner" images={bannerImages} onUpdate={updateImages} /> <ImageList listKey="icon" images={iconImages} onUpdate={updateImages} /> </TabPanel> </> ) }
ImageList调整为受控组件,内部不维护状态:
function ImageList({listKey, images, onUpdate}) { function handleImageSelected(image){ onUpdate(listKey, [...images, image]) } return ( <> <ImageUploader onSelect={handleImageSelected}/> <SortedImageList images={images} /> </> ) }
方案3:Context实现
可以用Context实现需求,不需要创建两个Context,单个Context内即可同时存储banner、icon两个图片数组的状态和操作方法,适合模态框内多层级组件都需要使用图片状态的场景:
// 创建Context const ImageListContext = createContext() // 封装Provider组件,逻辑全部内聚不污染MyItem function ImageListProvider({children}) { const [bannerImages, setBannerImages] = useState([{imageKey: 'banner', url:`/images/banner`, fileData: null}]) const [iconImages, setIconImages] = useState([{imageKey: 'icon', url:`/images/icon`, fileData: null}]) const updateImages = useCallback((listKey, newImages) => { listKey === 'banner' ? setBannerImages(newImages) : setIconImages(newImages) }, []) const getAllImages = useCallback(() => ({ banner: bannerImages, icon: iconImages }), [bannerImages, iconImages]) return ( <ImageListContext.Provider value={{updateImages, getAllImages}}> {children} </ImageListContext.Provider> ) }
父组件仅需套一层Provider:
function MyItem() { const { getAllImages } = useContext(ImageListContext) function handleSave() { const allImages = getAllImages() // 调用接口 } return ( <ImageListProvider> <TabPanel index={0} title="Detail"> <HeaderWithSaveButton onSaveClick={handleSave} /> <SomeTextContent/> </TabPanel> <TabPanel index={1} title="Images"> <ImageList listKey="banner" /> <ImageList listKey="icon" /> </TabPanel> </ImageListProvider> ) }
ImageList内部通过Context调用更新方法:
function ImageList({listKey}) { const { updateImages } = useContext(ImageListContext) const [images, setImages] = useState([{imageKey: listKey, url:`/images/${listKey}`, fileData: null}]) function handleImageSelected(image){ const newImages = [...images, image] setImages(newImages) updateImages(listKey, newImages) } return ( <> <ImageUploader onSelect={handleImageSelected}/> <SortedImageList images={images} /> </> ) }
内容的提问来源于stack exchange,提问作者MattoMK
相关产品推荐
相关产品推荐

