在 React 中的两个子组件之间传递函数

Pass function between two child components in React

我有一个名为 Navigation.js 的组件,其中包含三个组件:电影、我的页面和搜索。我这里只需要在搜索和电影组件之间进行通信。

<Router>
 <nav>
   <li>
    <Link to='/'>Movies</Link>
    <Link to='/mypage'>My Page</Link>
   </li>
   <Search/> //Search Component
   
     <Switch>
       <Route path="/mypage">
         <MyPage />
       </Route>
       <Route path="/">
         <Movies/>
      </Route>
     </Switch>
</Router> 

在搜索组件中我有简单的搜索表单。

export const Search = (props) => {
     const searchTxt = useRef('');
     const submitSearchHandler = (e) =>{
         e.preventDefault();
         const val = searchTxt.current.value;
      }
     return (
        <form onSubmit={submitSearchHandler}>
        <input ref={searchTxt} />
        <button type="submit">Search</button>
      </form>
     )
}

在电影组件中,我使用 Fetch 从 API 加载电影。所以我正在尝试的是,每当用户单击搜索组件内的搜索时,我想在电影组件内使用搜索组件的搜索参数执行 fetchMovies 函数。下面给出

    const Movies = () => {
    const [movies, setmovies] = useState({data:[], loading:true})
    const fetchMovies = async (search='') => {
        setmovies({loading:true})
        const moviesResponse = await fetch('https://yts.mx/api/v2/list_movies.json? 
        query_term='+search);
        const data = await moviesResponse.json();
        const moviesdata = data.data;
        setmovies({data:moviesdata.movies,loading:false})
    };
    useEffect(() => {
        fetchMovies()
    },[])
    
    
   let content =  movies.data.map((movie) => {
            return <Movie key={movie.id} movie={movie}/>
             })
   return (
            <div>
               {content}
            </div>
       
       )
     }
    

Lift shared state up 到 React 中最近的公共父组件。

此处 <Search /><Movies /> 都需要访问 searchTxt。将其保存在 <Navigation /> 组件中,并将其作为 props 传递给两个相关的子组件。

此外,将 useRef 替换为 <Navigation /> 中的 useState 挂钩,并根据需要向下传递 searchTxt and setSearchTxt

下面是 <Navigation /> 现在的样子:

const Navigation = () => {

const [searchTxt, setSearchTxt] = useState('');

return (
<Router>
 <nav>
   <li>
    <Link to='/'>Movies</Link>
    <Link to='/mypage'>My Page</Link>
   </li>
   <Search searchTxt={searchTxt} updateSearchText={setSearchTxt} />
   
     <Switch>
       <Route path="/mypage">
         <MyPage />
       </Route>
       <Route path="/">
         <Movies searchTxt={searchTxt} />
      </Route>
     </Switch>
</Router> 
)}

<Search /> 将如下所示:

export const Search = ({searchTxt, updateSearchTxt}) => {
const [input, setInput] = useState(searchTxt);

  const submitHandler = e => {
    e.preventDefault();
    updateSearchTxt(input);
  }

     return (
        <form onSubmit={submitHandler}>
        <input value={input} onChange={e => setInput(e.target.value)} />
        <button type="submit">Search</button>
      </form>
     )
}

<Movies /> 将保持不变。

假设您没有使用任何状态管理库,如 Redux 或 MobX,这样做的合乎逻辑的方法是移动 searchTxt 并将您之前定义的 submitSearchHandler 处理函数修改为委托事件 onSearchText(在您的组件中,这是直接为搜索设置值的工作,现在它将设置搜索文本的工作委托给组件树中一个或多个级别的某个函数) .这在 React 中被称为“提升”,通常用于在树中的多个组件之间共享变量 and/or 处理函数。

这就是您的 Search 组件的样子:

export const Search = (props) => {
     const submitSearchHandler = (e) =>{
         e.preventDefault();
         const val = searchTxt.current.value;
         props.onSearchText(val); // this is the event that will be handled
      }
     return (
        <form onSubmit={submitSearchHandler}>
        <input value={props.searchTxt} />
        <button type="submit">Search</button>
      </form>
     )
}

在同一组件中路由器 jsx 代码上方的某处,您将处理设置 searchTxt 状态变量的值,并将该值作为道具传递给 Movies 组件。

const [searchTxt, setSearchText] = useState('');

<Router>
 <nav>
   <li>
    <Link to='/'>Movies</Link>
    <Link to='/mypage'>My Page</Link>
   </li>
   <Search 
    searchTxt={searchTxt} 
    onSearchText={(text) => setSearchText(text)}
   />   
     <Switch>
       <Route path="/mypage">
         <MyPage />
       </Route>
       <Route path="/">
         <Movies searchTxt={searchTxt}/>
      </Route>
     </Switch>
</Router> 

您将在 Movies 组件中声明一个 useEffect 并将 searchTxt 作为依赖项,从而在每次搜索值更改时触发 fetchMovies() 函数。

    const Movies = ({searchTxt}) => {
    const [movies, setmovies] = useState({data:[], loading:true})
    
    const fetchMovies = async () => {
        setmovies({loading:true})
        const moviesResponse = await fetch('https://yts.mx/api/v2/list_movies.json? 
        query_term='+searchTxt);
        const data = await moviesResponse.json();
        const moviesdata = data.data;
        setmovies({data:moviesdata.movies,loading:false})
    };
    useEffect(() => {
        if(searchText !== ""){
          fetchMovies()
        }
    },[searchTxt])
    
    
   let content =  movies.data.map((movie) => {
            return <Movie key={movie.id} movie={movie}/>
             })
   return (
            <div>
               {content}
            </div>
       
       )
     }

通常,如果您希望两个组件进行通信并且您没有使用状态管理 tool/context,您可以管理一个容器来管理状态和 API 调用(称为 "lifting state up" - 你有一个 - <Navigation /><Movies /> 然后可以是一个哑组件,它 接收 并处理数据,而不必处理它自己的 API呼唤。

这是一个使用模拟数据的工作示例,模拟 API 在两秒后调用 returns JSON。

const {useState} = React;

const json = '["Movie1", "Movie2", "Movie3", "Movie4"]';

function mockApi() {
  return new Promise((res, rej) => {
    setTimeout(() => res(json), 2000);
  });
}

function Navigation() {

  const [movies, setMovies] = useState([]);

  // A reference to handleSearch is passed into
  // the Search component so it can be called when
  // the button is clicked
  function handleSearch(value) {
    mockApi(value)
      .then(json => JSON.parse(json))
      .then(data => setMovies(data));
  }

  // Movies now simply receives the data that's
  // stored in state after the API call
  return (
    <div>
      <Search handleSearch={handleSearch} />
      <Movies movies={movies} />
    </div>
  );
}

function Search({ handleSearch }) {

  // Search has its own state
  const [input, setInput] = useState('');

  // Call the handleSearch function when the
  // button is clicked
  function handleClick() {
    handleSearch(input);
  }

  // Set the state
  function handleInput(e) {
    setInput(e.target.value);
  }

  return (
    <div>
      <input value={input} onChange={handleInput} />
      <button onClick={handleClick}>Search</button>
    </div>
  );
}

// Movies is now just responsible for rendering the movie data
function Movies({ movies }) {
  return movies.map(movie => <div>{movie}</div>);
}

// Render it
ReactDOM.render(
  <Navigation />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>