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

如何将App.js中的搜索相关逻辑抽离为独立的Search.js组件

React搜索逻辑抽离到独立组件的解决方案

问题背景

我正在学习Robin Wieruch所著的《The Road To React》,书中案例全部将代码写在App.js文件中,不符合最佳开发实践。我已经将其他模块都抽离为独立组件文件,但无法将搜索相关逻辑抽离到单独的Search.js文件中,当前文件夹结构如下:
文件夹结构

现有可运行代码

App.js代码

import React, { useState } from 'react';
import './App.css';
import Greeting from '../Greeting/Greeting';
import Search from '../Search/Search';
import List from '../List/List';

const useSemiPersistentState = (key, initialState) => {
  const [value, setValue] = React.useState(
    localStorage.getItem(key) || initialState
  );

  React.useEffect(() => {
    localStorage.setItem(key, value);
  }, [value, key]);

  return [value, setValue]
};

// App组件
const App = () => {
  const stories = [
    {
      title: 'React',
      url: 'https://reactjs.org/',
      author: 'Jordan Walke',
      num_comments: 3,
      points: 4,
      objectID: 0,
    },
    {
      title: 'Redux',
      url: 'https://redux.js.org/',
      author: 'Dan Abramov, Andrew Clark',
      num_comments: 2,
      points: 5,
      objectID: 1,
    },
  ];

  // 用自定义Hook设置searchTerm、setSearchTerm状态
  const [searchTerm, setSearchTerm] = useSemiPersistentState(
    'search',
    'React'
  );

  // 获取搜索框输入值
  const handleSearch = (event) => {
    setSearchTerm(event.target.value);
  };

  // 匹配用户输入与stories数组内容,统一转小写判断
  const searchedStories = stories.filter((story) => story.title.toLowerCase().includes(searchTerm.toLowerCase())
  );

  // 渲染
  return (
    <div className="App">
      <Greeting name="Colin" age="28" occupation="Front-end developer" />
      <InputWithLabel
        id="search"
        value={searchTerm}
        isFocused
        onInputChange={handleSearch}
      >
        <strong>Search:</strong>
      </InputWithLabel>
      <hr />
      <List list={searchedStories} />
    </div>
  );
}

// 搜索框组件,解构props
const InputWithLabel = ({ id, value, type = "text", onInputChange, isFocused, children, }) => {
  const inputRef = React.useRef();

  React.useEffect(() => {
    if (isFocused && inputRef.current) {
      inputRef.current.focus();
    }
  }, [isFocused]);

  return (
    <>
      <label htmlFor={id}>{children}</label>
      &nbsp;
      <input
        ref={inputRef}
        id={id}
        type={type}
        value={value}
        autoFocus={isFocused}
        onChange={onInputChange}
      />
    </>
  )
}

export default App;

List组件代码

import React from 'react';
import Item from '../Item/Item';

function List({ list }) {
    return (
        <ul>
            {list.map(({ objectID, ...item }) => (
                <Item key={objectID} {...item} />
            ))}
        </ul>
    );
}

export default List;

Item组件代码

import React from 'react';

function Item({ title, url, author, num_comments, points }) {
    return (
        <div>
            <li>
                <span>
                    <a href={url}>{title}</a>
                </span>
                <span> {author}</span>
                <span> {num_comments} comments,</span>
                <span> {points} points.</span>
            </li>
        </div>
    );
}

export default Item;

Greeting组件和Helpers文件夹与本次问题无关,暂不提供代码。

抽离时遇到的错误

将搜索相关代码移动到Search.js并在App.js中导入后触发如下编译错误:

编译失败
src/Components/App/App.js
第60行第8列: 'InputWithLabel' 未定义 react/jsx-no-undef
第62行第16列: 'searchTerm' 未定义 no-undef
第64行第24列: 'handleSearch' 未定义 no-undef
第69行第19列: 'searchedStories' 未定义 no-undef

解决步骤

报错是因为你把InputWithLabel组件抽离后,App.js中没有对应定义,也没有将搜索需要的状态和方法通过props传递给抽离后的Search组件,按以下步骤修改即可:

1. 编写Search/Search.js文件

import React from 'react';

const InputWithLabel = ({ id, value, type = "text", onInputChange, isFocused, children, }) => {
  const inputRef = React.useRef();

  React.useEffect(() => {
    if (isFocused && inputRef.current) {
      inputRef.current.focus();
    }
  }, [isFocused]);

  return (
    <>
      <label htmlFor={id}>{children}</label>
      &nbsp;
      <input
        ref={inputRef}
        id={id}
        type={type}
        value={value}
        autoFocus={isFocused}
        onChange={onInputChange}
      />
    </>
  )
}

const Search = ({ searchTerm, handleSearch }) => {
  return (
    <InputWithLabel
      id="search"
      value={searchTerm}
      isFocused
      onInputChange={handleSearch}
    >
      <strong>Search:</strong>
    </InputWithLabel>
  )
}

export default Search;

2. 修改App.js文件

删除App.js中原来的InputWithLabel组件定义,将模板中的InputWithLabel替换为导入的Search组件,传入需要的props:

import React, { useState } from 'react';
import './App.css';
import Greeting from '../Greeting/Greeting';
import Search from '../Search/Search';
import List from '../List/List';

const useSemiPersistentState = (key, initialState) => {
  const [value, setValue] = React.useState(
    localStorage.getItem(key) || initialState
  );

  React.useEffect(() => {
    localStorage.setItem(key, value);
  }, [value, key]);

  return [value, setValue]
};

const App = () => {
  const stories = [
    {
      title: 'React',
      url: 'https://reactjs.org/',
      author: 'Jordan Walke',
      num_comments: 3,
      points: 4,
      objectID: 0,
    },
    {
      title: 'Redux',
      url: 'https://redux.js.org/',
      author: 'Dan Abramov, Andrew Clark',
      num_comments: 2,
      points: 5,
      objectID: 1,
    },
  ];

  const [searchTerm, setSearchTerm] = useSemiPersistentState(
    'search',
    'React'
  );

  const handleSearch = (event) => {
    setSearchTerm(event.target.value);
  };

  const searchedStories = stories.filter((story) => story.title.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div className="App">
      <Greeting name="Colin" age="28" occupation="Front-end developer" />
      <Search searchTerm={searchTerm} handleSearch={handleSearch} />
      <hr />
      <List list={searchedStories} />
    </div>
  );
}

export default App;

修改后所有报错会消失,功能和原代码完全一致。

内容的提问来源于stack exchange,提问作者Halp_am_stuck

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 22:36:05