React父组件触发事件调用子组件函数的最优实践及代码优化
解决方案
针对你遇到的父组件触发子组件数据请求的场景,无需将fetchData移到Main组件导致代码臃肿,以下是几种通用优化方案:
方案一:通过Ref调用子组件暴露的方法
适合父组件需要主动触发子组件内部逻辑的场景,通过useRef+useImperativeHandle配合,既能调用子组件方法,又能避免直接操作子组件内部细节。
修改Posts组件
用forwardRef包裹组件,通过useImperativeHandle暴露搜索刷新方法:
import { useState, useImperativeHandle, forwardRef } from 'react'; const Posts = forwardRef(({ authToken, authTokenType, username }, ref) => { const [posts, setPosts] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [searchKeyword, setSearchKeyword] = useState(''); const fetchData = async () => { try { const url = new URL(`${BASE_URL}/post/all`); url.searchParams.append('page', page); if (searchKeyword) url.searchParams.append('keyword', searchKeyword); const response = await fetch(url, { headers: { Authorization: `${authTokenType} ${authToken}` } }); const newPosts = await response.json(); if (newPosts.length === 0) setHasMore(false); else { setPosts(prev => [...prev, ...newPosts]); setPage(prev => prev + 1); } } catch (err) { console.error('请求失败:', err); } }; // 暴露给父组件的搜索刷新方法 useImperativeHandle(ref, () => ({ refreshWithSearch: (keyword) => { setPosts([]); setPage(1); setHasMore(true); setSearchKeyword(keyword); fetchData(); } })); return ( <div> <InfiniteScroll dataLength={posts.length} next={fetchData} hasMore={hasMore} loader={<h4>Loading...</h4>} > <Row> {posts.map((post, index) => ( <Col lg={3} key={index}> <Post post={post} authToken={authToken} authTokenType={authTokenType} username={username} /> </Col> ))} </Row> </InfiniteScroll> </div> ); }); export default Posts;
修改Main组件
通过useRef获取Posts实例,点击搜索按钮时调用暴露的方法:
import { useRef, useState } from 'react'; import Posts from './Posts'; function Main({ authToken, authTokenType, username, userId }) { const postsRef = useRef(null); const [searchInput, setSearchInput] = useState(''); const handleSearch = () => { postsRef.current?.refreshWithSearch(searchInput); }; return ( <div> {/* 搜索栏 */} <div> <input type="text" value={searchInput} onChange={(e) => setSearchInput(e.target.value)} placeholder="输入关键词搜索..." /> <button onClick={handleSearch}>搜索</button> </div> <Posts ref={postsRef} authToken={authToken} authTokenType={authTokenType} username={username} /> </div> ); } export default Main;
方案二:通过props传递筛选条件,子组件自动触发请求
更符合React单向数据流设计,父组件管理搜索条件,子组件监听条件变化自动重置并请求数据,无需主动调用子组件方法。
修改Main组件
将搜索状态作为props传递给Posts:
import { useState } from 'react'; import Posts from './Posts'; function Main({ authToken, authTokenType, username, userId }) { const [searchKeyword, setSearchKeyword] = useState(''); return ( <div> <div> <input type="text" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)} placeholder="输入关键词搜索..." /> <button onClick={() => setSearchKeyword(searchKeyword)}>搜索</button> </div> <Posts authToken={authToken} authTokenType={authTokenType} username={username} searchKeyword={searchKeyword} /> </div> ); } export default Main;
修改Posts组件
用useEffect监听搜索条件变化,自动重置并请求数据:
import { useState, useEffect } from 'react'; function Posts({ authToken, authTokenType, username, searchKeyword }) { const [posts, setPosts] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const fetchData = async () => { try { const url = new URL(`${BASE_URL}/post/all`); url.searchParams.append('page', page); if (searchKeyword) url.searchParams.append('keyword', searchKeyword); const response = await fetch(url, { headers: { Authorization: `${authTokenType} ${authToken}` } }); const newPosts = await response.json(); if (newPosts.length === 0) setHasMore(false); else { setPosts(prev => [...prev, ...newPosts]); setPage(prev => prev + 1); } } catch (err) { console.error('请求失败:', err); } }; // 监听搜索关键词变化,重置数据并重新请求 useEffect(() => { setPosts([]); setPage(1); setHasMore(true); fetchData(); }, [searchKeyword]); return ( <div> <InfiniteScroll dataLength={posts.length} next={fetchData} hasMore={hasMore} loader={<h4>Loading...</h4>} > <Row> {posts.map((post, index) => ( <Col lg={3} key={index}> <Post post={post} authToken={authToken} authTokenType={authTokenType} username={username} /> </Col> ))} </Row> </InfiniteScroll> </div> ); } export default Posts;
方案三:用自定义Hook封装数据请求逻辑
如果后续多个组件需要复用帖子请求逻辑,可将fetchData及相关状态封装成自定义Hook,让组件代码更简洁,避免冗余。
自定义Hook:usePosts.js
import { useState, useEffect } from 'react'; const BASE_URL = '你的接口地址'; export const usePosts = (authToken, authTokenType, searchKeyword) => { const [posts, setPosts] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [isLoading, setIsLoading] = useState(false); const fetchData = async () => { if (isLoading) return; setIsLoading(true); try { const url = new URL(`${BASE_URL}/post/all`); url.searchParams.append('page', page); if (searchKeyword) url.searchParams.append('keyword', searchKeyword); const response = await fetch(url, { headers: { Authorization: `${authTokenType} ${authToken}` } }); const newPosts = await response.json(); if (newPosts.length === 0) setHasMore(false); else { setPosts(prev => [...prev, ...newPosts]); setPage(prev => prev + 1); } } catch (err) { console.error('请求失败:', err); } finally { setIsLoading(false); } }; const resetPosts = () => { setPosts([]); setPage(1); setHasMore(true); fetchData(); }; useEffect(() => { resetPosts(); }, [searchKeyword]); return { posts, hasMore, fetchData }; };
修改Posts组件
使用自定义Hook简化代码:
import { usePosts } from './usePosts'; function Posts({ authToken, authTokenType, username, searchKeyword }) { const { posts, hasMore, fetchData } = usePosts(authToken, authTokenType, searchKeyword); return ( <div> <InfiniteScroll dataLength={posts.length} next={fetchData} hasMore={hasMore} loader={<h4>Loading...</h4>} > <Row> {posts.map((post, index) => ( <Col lg={3} key={index}> <Post post={post} authToken={authToken} authTokenType={authTokenType} username={username} /> </Col> ))} </Row> </InfiniteScroll> </div> ); } export default Posts;
内容的提问来源于stack exchange,提问作者Tomas Am
相关产品推荐
相关产品推荐

