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

React Memo包裹组件在search状态更新时仍意外重渲染问题

React输入框更新触发子组件意外重渲染问题修复

核心问题原因

你做的memo和useCallback优化完全没生效,输入内容就触发子组件打印,是几个基础写法错误导致的:

  • 组件入参写法不符合React规则:React函数组件只会接收1个参数,即所有传入属性组成的props对象。你写的const StationComponent = (fetchSchedule, s) => {}是完全错误的,这种写法下第一个形参拿到的是整个props对象{ fetchSchedule: 函数, s: 站点数据 },第二个形参永远是undefined:一方面会导致点击站点时抛出"fetchSchedule is not a function"的错误,另一方面memo做浅比较时无法正确识别props是否变化,缓存直接失效,只要父组件重渲染子组件就会跟着重跑。
  • useCallback缺失依赖项:你包裹filterStations的useCallback没有传入依赖数组,也没有把内部用到的stationData、search加入依赖,会触发闭包陷阱,点击搜索时拿不到最新输入值。
  • 附带两个逻辑bug:一是南向车次列表的判断条件错写为routes.N.length > 0,会导致南向列表永远不渲染;二是列表渲染时key属性错误写在了子组件内部的div上,会触发React渲染警告。

修复方案

  1. 修正子组件的入参写法,直接在形参位置解构需要的props
  2. 给所有useCallback补全正确的依赖数组
  3. 修正南向车次的判断条件,把key属性移到map循环的组件上
  4. 给search状态设置空字符串初始值,避免调用includes时触发类型错误

修复后的完整代码如下:

import { memo, useCallback, useState } from 'react'

import * as stations from './stations.json'
import './App.css';

const StyledContainer = {
  "cursor": "pointer",
  "display": "flex",
  "flex-direction": "row"
}

const StyledSegment = {
  "justify-content": "space-between",
  "text-align": "left",
  "width": "33.3%"
}

const baseUrl = 'https://api.wheresthefuckingtrain.com/by-id'

// 正确解构props,直接用memo包裹组件
const StationComponent = memo(({ fetchSchedule, s }) => {
  const { id, location, name, routes = false } = s

  console.log({ routes }) // 仅在对应props变化时才会打印

  return (
    <div onClick={() => { fetchSchedule(id) }}>
      <h3>{name}</h3>
      <h4>{location}</h4>
      {routes && routes.N.length > 0 && <div><label>North</label><ul>{routes.N.map(({ route, time }, idx) => <li key={idx}>Route: {route}<br />{new Date(time).toLocaleTimeString()}</li>)}</ul></div>}
      {/* 修正南向列表判断条件 */}
      {routes && routes.S.length > 0 && <div><label>South</label><ul>{routes.S.map(({ route, time }, idx) => <li key={idx}>Route: {route}<br />{new Date(time).toLocaleTimeString()}</li>)}</ul></div>}
    </div>
  )
})

function App() {
  const [filteredStationData, setFilteredStationData] = useState(stations)
  const [search, setSearch] = useState('') 
  const [stationData, setStationData] = useState(stations)

  const fetchSchedule = useCallback(async (id) => {
    const res = await fetch(`${baseUrl}/${id}`, { method: 'get' })
    const json = await res.json()

    const copyStationData = JSON.parse(JSON.stringify(stations))
    copyStationData[id].routes = json.data[0]

    setStationData(copyStationData)
  }, []) // 无外部依赖,依赖数组为空即可

  const filterStations = useCallback(() => {
    const filteredStations = Object.values(stationData).filter((s) => s.name.includes(search))
    setFilteredStationData(filteredStations)
  }, [stationData, search]) // 补全依赖项

  return (
    <div className="App" style={StyledContainer}>
      <header className="App-header">
        <input placeholder="Name" onChange={(e) => { setSearch(e.target.value) }} />
        <button onClick={filterStations}>Search</button>
        <div style={StyledSegment}>
          {/* 把key属性移到循环的组件上 */}
          {Object.values(filteredStationData).map(s => <StationComponent key={s.id} fetchSchedule={fetchSchedule} s={s} />)}
        </div>
      </header>
    </div>
  );
}

export default App;

修复后,只有当你点击搜索按钮更新filteredStationData、或者拉取班次更新对应站点的routes数据时,对应StationComponent才会重渲染,输入框修改search值不会再触发子组件的console输出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 01:54:22