React Deriving and Redundant State

建立:2026-05-17 · 最後編輯:2026-05-17

Deriving State
 

如果可以透過派生出的資料就不應該多出 state 進行管理,例如:

function TripSummary() {
 const [tripItems] = useState([
   { name: 'Flight', cost: 500 },
   { name: 'Hotel', cost: 300 },
 ]);
 const [totalCost, setTotalCost] = useState(0); // ❌ Unnecessary state

 useEffect(() => {
   setTotalCost(tripItems.reduce((sum, item) => sum + item.cost, 0)); // ❌ Sync effect
 }, [tripItems]);

 return <div>Total: ${totalCost}</div>;
}

 

多出了不必要的 setStateuseEffect 額外進行管理可以被生出來的資料:

// ✅ Derive the value directly
const totalCost = tripItems.reduce((sum, item) => sum + item.cost, 0);

 

Redundant State

另一個例子與第一個 Deriving State 類似,但可能會較難發現:

function HotelSelection() {
 const [hotels] = useState([
   { id: 'h1', name: 'Grand Hotel', price: 200 },
   { id: 'h2', name: 'Budget Inn', price: 80 },
 ]);
 const [selectedHotel, setSelectedHotel] = useState<Hotel | null>(null); // ❌ Stores entire object

 const handleSelect = (hotel: Hotel) => {
   setSelectedHotel(hotel); // ❌ Duplicates data from hotels array
 };

 return (
   <div>
     {selectedHotel && (
       <div>
         {selectedHotel.name} - ${selectedHotel.price}
       </div>
     )}
   </div>
 );
}

 

selectedHotel 完整的儲存,看似沒問題,但是違反了單一資料來源的問題,假設在背後有個 interval 會去更新飯店的價格,但是 selectedHotel 並沒有被更新到,因完它完整的儲存了新的 Hotel,這極有可能會發生 Bug,例如資料的不一致,違反了 Single Source of Truth 的規則。

最好的方式是使用第一個派生的方式去處理:

const handleSelect = (hotelId: string) => {
   setSelectedHotelId(hotelId); // ✅ Store minimal data
};

// ✅ Derive the full object when needed
const selectedHotel = hotels.find((h) => h.id === selectedHotelId);