React中useEffect渲染异常:移除tempFlights状态的优化方案
问题原因分析
- 数组引用类型导致原数组被修改:JavaScript中数组是引用类型,
flights.sort()是原地排序方法——它会直接修改调用它的原数组(也就是useFetchFlightResults返回的flights),而非返回新数组。所以不管你是否将排序结果赋值给temp,原flights的顺序已经被改变。 - setTempFlights触发重渲染同步显示变化:执行
setTempFlights([...temp])时会触发组件重渲染,此时flights已经被之前的sort修改,页面基于flights渲染时就能立刻看到排序后的结果。 - 移除setTempFlights后的延迟问题:去掉
setTempFlights后,useEffect里的flights.sort()仍会修改原数组,但useEffect是在组件渲染完成后执行的——这次修改不会触发组件重渲染,页面还是显示排序前的flights,直到下次用户修改sortBy触发组件重新渲染,才会用上已经被修改过的flights,因此出现“需等待用户下次输入才更新”的情况。
移除tempFlights的实现方案
核心思路是不修改原flights数组,通过衍生值或状态管理排序结果,以下是两种可行方案:
方案一:使用useMemo计算排序后的数组(推荐)
利用useMemo缓存排序结果,仅在flights或sortBy变化时重新计算,避免不必要的性能消耗:
import React, { useState, useMemo } from "react"; import "./index.style.css"; import FlightSearchItem from "./FlightSearchItem"; import FlightSearchFooter from "./FlightSearchFooter"; import useFetchFlightResults from "./useFetchFlightResults"; import SortBy from "./filters/SortBy"; import { SortByDefaultOption } from "./filters/SortBy/enums"; export default function FlightSearch() { const [sortBy, setSortBy] = useState(SortByDefaultOption); const { flights } = useFetchFlightResults(); // 缓存排序后的数组,先拷贝原数组避免修改原数据 const sortedFlights = useMemo(() => { const flightsCopy = [...flights]; if (sortBy.value === "PRICE_LOW") { return flightsCopy.sort((a, b) => a.price - b.price); } else if (sortBy.value === "BEST") { return flightsCopy.sort((a, b) => a.score - b.score); } else { return flightsCopy.sort((a, b) => { return new Date(a.segmentsArray[0].arrivesAt) - new Date(b.segmentsArray[0].arrivesAt); }); } }, [flights, sortBy]); // 基于排序后的数组做分页 const paginatedFlights = sortedFlights.slice(0, 8); return ( <div className="row"> <div className="pane m-t-1"> <div className="pane-header search-results__header"> <div className="search-results__title"> <b>Select an outbound flight</b> <p className="m-v-0 fade small">DEN → CHI</p> </div> <SortBy value={sortBy} onChange={setSortBy} /> </div> <div className="pane-content"> {Array.isArray(paginatedFlights) && paginatedFlights.map((flight) => ( <FlightSearchItem key={flight.id} flight={flight} /> ))} </div> </div> <FlightSearchFooter /> </div> ); }
方案二:使用状态存储排序后的数组
如果需要后续手动修改排序结果,可使用useState存储排序后的数组,在useEffect中更新:
import React, { useEffect, useState } from "react"; import "./index.style.css"; import FlightSearchItem from "./FlightSearchItem"; import FlightSearchFooter from "./FlightSearchFooter"; import useFetchFlightResults from "./useFetchFlightResults"; import SortBy from "./filters/SortBy"; import { SortByDefaultOption } from "./filters/SortBy/enums"; export default function FlightSearch() { const [sortBy, setSortBy] = useState(SortByDefaultOption); const { flights } = useFetchFlightResults(); const [sortedFlights, setSortedFlights] = useState([...flights]); useEffect(() => { const flightsCopy = [...flights]; let sorted = []; if (sortBy.value === "PRICE_LOW") { sorted = flightsCopy.sort((a, b) => a.price - b.price); } else if (sortBy.value === "BEST") { sorted = flightsCopy.sort((a, b) => a.score - b.score); } else { sorted = flightsCopy.sort((a, b) => { return new Date(a.segmentsArray[0].arrivesAt) - new Date(b.segmentsArray[0].arrivesAt); }); } setSortedFlights(sorted); }, [flights, sortBy]); const paginatedFlights = sortedFlights.slice(0, 8); return ( <div className="row"> <div className="pane m-t-1"> <div className="pane-header search-results__header"> <div className="search-results__title"> <b>Select an outbound flight</b> <p className="m-v-0 fade small">DEN → CHI</p> </div> <SortBy value={sortBy} onChange={setSortBy} /> </div> <div className="pane-content"> {Array.isArray(paginatedFlights) && paginatedFlights.map((flight) => ( <FlightSearchItem key={flight.id} flight={flight} /> ))} </div> </div> <FlightSearchFooter /> </div> ); }
关键注意点
- 禁止直接修改
useFetchFlightResults返回的flights数组,这属于外部状态,直接修改会导致状态不一致,引发难以排查的bug。 - 排序前必须先拷贝原数组(用
[...flights]或flights.slice()),确保原数组不受影响。
内容的提问来源于stack exchange,提问作者Aaron
相关产品推荐
相关产品推荐

