You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 00:31:03