當你在升級 React 18.3 或 React 19 專案並將函式元件(Function Component)的
defaultProps改寫為 JavaScript 預設參數(Default Parameters)時,若只是單純替換語法,常會在 TypeScript Props 型別定義、後端回傳null值導致預設值失效,或是 物件預設值引發useEffect重複觸發 等場景踩坑。本文將針對 React + TypeScript 專案廢除defaultProps後的型別重構、高階元件(HOC)傳遞與三大常見重構陷阱進行完整排查。💡 延伸閱讀(基礎警告排查):若你想先了解瀏覽器 Console 跳出這道英文警告的底層背景與純 JavaScript 基礎改法,可先參考另一篇專文:React Console Warning:Support for defaultProps will be removed from function components 原因與解決方法。
一、 快速回顧:從 defaultProps 轉換為預設參數的基本寫法
在舊版 React 專案中,我們常在函式元件外部掛載 defaultProps 來設定預設屬性:
// ❌ 舊版寫法:在 React 18.3+ 會觸發警告,React 19 函式元件已不再支援
function Text({ fontSize }) {
return <p style={{ fontSize }}>範例文字內容</p>;
}
Text.defaultProps = {
fontSize: '20px',
};
最直接的遷移方式,是改用 ES6 的 函式解構預設參數(Default Parameters),直接在元件接收 Props 的入口處宣告預設值:
// ✅ 現代標準寫法:直接在參數解構時設定預設值
const Text = ({ fontSize = '20px' }) => {
return <p style={{ fontSize }}>範例文字內容</p>;
};
雖然基本語法替換非常直覺,但在實務生產環境(特別是結合 TypeScript、API 資料串接 與 效能最佳化 的專案)中,直接把 defaultProps 刪掉改寫成解構預設參數,往往會遇到以下三個隱藏陷阱。
二、 React + TypeScript 廢除 defaultProps 後的 Props 型別定義規範
過去在 TypeScript 中搭配 defaultProps 是一大痛點:因為編譯器很難同時明白「這個屬性在外部呼叫元件時可以省略(Optional ?:),但在元件內部因為有 defaultProps 所以一定有值(Non-nullable)」。
改用 ES6 預設參數後,TypeScript 的控制流分析(Control Flow Analysis)終於能自動完成型別收斂。以下是舊制與新制的規格對照:
| 對照維度 | 舊制 Component.defaultProps | 新制 ES6 解構預設參數 | 對 TypeScript 開發的影響 |
|---|---|---|---|
| Interface 宣告方式 | 常需額外搭配 Partial 或 typeof defaultProps | 直接將有預設值的屬性加上 ?(Optional) | 大幅減少樣板程式碼(Boilerplate) |
| 元件外部呼叫檢查 | 容易發生必填與選填型別衝突 | 標記 ? 的屬性可自由省略不傳 | IDE 自動補全與 Props 提示更精準 |
| 元件內部型別推導 | 常被推導為 T 或 undefined,需寫 ! 斷言 | 解構賦予預設值後,自動收斂為非空型別 T | 元件內部可直接安全呼叫屬性方法 |
| React 19 相容性 | 函式元件完全忽略 defaultProps | 100% 原生支援,零執行期額外開銷 | 符合未來版本與標準 ES 規範 |
範本一:TypeScript 函式元件與 Props 型別標準定義
在定義 interface 或 type 時,請將具備預設值的屬性加上 ? 設為可選屬性(Optional Property),並在解構時賦予預設值,切勿再使用已棄用的 React.FC 內建 defaultProps 掛載:
import React from 'react';
interface AlertBannerProps {
/** 必填屬性:提示訊息內容 */
message: string;
/** 選填屬性:視覺狀態等級(預設為 'info') */
variant?: 'info' | 'warning' | 'error';
/** 選填屬性:是否顯示關閉按鈕(預設為 true) */
closable?: boolean;
/** 選填回呼:點擊關閉時觸發 */
onClose?: () => void;
}
export const AlertBanner = ({
message,
variant = 'info',
closable = true,
onClose,
}: AlertBannerProps) => {
// 在元件內部,TypeScript 會自動將 variant 推導為 'info' | 'warning' | 'error'(不含 undefined)
// closable 也會自動推導為確定的 boolean 型別
return (
<div className={`alert-box alert-${variant}`} role="alert">
<span>{message}</span>
{closable && (
<button type="button" onClick={onClose} aria-label="關閉提示">
✕
</button>
)}
</div>
);
};
三、 重構常見的三大踩坑場景與排查解法
踩坑 1:API 回傳 null 時,ES6 預設參數不會生效!
這是從後端 API 串接資料時最常發生的執行期 Bug(如 Uncaught TypeError: Cannot read properties of null)。
- 問題原因:在 JavaScript ES6 規範中,解構預設參數(例如
{ items = [] })只有在屬性值嚴格等於undefined(prop === undefined)時才會觸發預設值。如果後端資料庫欄位為空並回傳了null(即<UserList items={data.items} />傳入null),預設的[]將不會生效,items依然會是null! - 解決思路:對於可能收到
null的 API 資料欄位,請結合 空值合併運算子(Nullish Coalescing Operator??) 進行雙重防護:
interface TagListProps {
// 允許外部或 API 傳入 string[]、null 或 undefined
tags?: string[] | null;
}
export const TagList = ({ tags }: TagListProps) => {
// ✅ 同時防禦 undefined 與 null:當 tags 為 null 或 undefined 時皆安全退回空陣列 []
const safeTags = tags ?? [];
return (
<ul>
{safeTags.map((tag) => (
<li key={tag}>{tag}</li>
))}
</ul>
);
};
踩坑 2:在解構參數中直接寫 { config = {} } 或 { list = [] } 導致無限重渲染
- 問題原因:如果你在函式參數中直接寫
({ style = {}, items = [] }) => ...,每次父元件重新渲染且沒有傳入該屬性時,JavaScript 都會在記憶體中重新建立一個全新的{}物件或[]陣列實例(新的記憶體位址)。如果這個屬性剛好被放進useEffect的依賴陣列(Dependency Array)或傳給React.memo子元件,就會因為每次渲染的 Reference 不同而導致React.memo破功,甚至引發useEffect無限迴圈! - 解決思路:將預設的空物件、空陣列或預設設定抽離到元件外部宣告為模組級常數(Module-level Constant),確保每次渲染都指向同一個記憶體參考:
import React, { useEffect } from 'react';
interface ChartConfig {
animate: boolean;
theme: 'light' | 'dark';
}
// ✅ 將預設物件與空陣列抽離至元件外部,固定記憶體參考位址(Reference)
const DEFAULT_CHART_CONFIG: ChartConfig = {
animate: true,
theme: 'light',
};
const EMPTY_DATA_LIST: number[] = [];
interface MetricsChartProps {
data?: number[];
config?: ChartConfig;
}
export const MetricsChart = ({
data = EMPTY_DATA_LIST,
config = DEFAULT_CHART_CONFIG,
}: MetricsChartProps) => {
useEffect(() => {
// 因為 DEFAULT_CHART_CONFIG 參考位址固定,不會在每次重渲染時無故重複觸發 useEffect
console.log('套用圖表設定:', config.theme, '資料筆數:', data.length);
}, [config, data]);
return <div className={`chart-${config.theme}`}>共 {data.length} 筆數據</div>;
};
踩坑 3:搭配 forwardRef 或高階元件(HOC)時的預設參數寫法
當元件需要搭配 React.forwardRef 或包裹在高階元件(Higher-Order Component, HOC)中時,請將預設參數直接寫在內層渲染函式的第一個 props 解構參數上:
import React, { forwardRef } from 'react';
interface TextInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
variant?: 'outlined' | 'filled';
}
// ✅ 在 forwardRef 內層渲染函式的第一個參數直接解構並給定預設值
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
({ label = '請輸入內容', variant = 'outlined', type = 'text', ...restProps }, ref) => {
return (
<label className={`input-field input-${variant}`}>
<span>{label}</span>
<input ref={ref} type={type} {...restProps} />
</label>
);
}
);
TextInput.displayName = 'TextInput';
四、 開發者高頻 Q&A 彙整
Q1:為什麼把 defaultProps 改成預設參數後,傳入 null 會報錯?
- 問題原因:ES6 預設參數僅在參數值為
undefined時才會套用預設值;而null在 JavaScript 中被視為「有明確傳入的空值」,因此不會觸發預設參數。 - 解法:若屬性資料來自後端 API 且可能為
null,請在元件內部搭配空值合併運算子const safeVal = propVal ?? '預設值'處理。
Q2:使用 ...restProps 展開其餘屬性時,預設參數順序有影響嗎?
- 問題原因:在解構賦值
({ variant = 'primary', ...restProps })中,已被明確解構出來的variant不會包含在restProps內。 - 解法:若需要將套用預設值後的屬性一併傳遞給底層原生元素(如
<button type={type} {...restProps}>),請確保在 JSX 中明確綁定該屬性變數。
Q3:為什麼 ESLint 出現 react/require-default-props 規則警告?
- 問題原因:部分舊專案的
eslint-plugin-react預設要求所有可選 Props 必須寫在Component.defaultProps中,與 React 18.3+ 的棄用方向衝突。 - 解法:在
.eslintrc設定檔中,將react/require-default-props的functions選項設為"defaultArguments"(即"react/require-default-props": ["error", { "functions": "defaultArguments" }]),讓 ESLint 改為檢查函式預設參數。
Q4:Class Component(類別元件)也需要把 static defaultProps 移掉嗎?
- 問題原因:React 19 僅移除「函式元件(Function Component)」對
defaultProps的支援。因為類別元件是透過this.props存取屬性,無法使用函式參數解構。 - 解法:現有的 Class Component 仍可保留
static defaultProps,不影響 React 19 升級。
Q5:如果我想看基礎的 Console Warning 觸發原因與純 JS 範例該看哪篇?
- 問題原因:本文聚焦於 TypeScript 型別推導、
null解構陷阱與物件參考最佳化。 - 解法:若需查閱原始警告訊息與純 JavaScript 對照範例,請直接參考姊妹篇:React Console Warning:Support for defaultProps will be removed from function components 原因與解決方法。
參考資料與延伸閱讀
- 延伸閱讀:React Console Warning:Support for defaultProps will be removed from function components 原因與解決方法
- 延伸閱讀:React Console Warning:images jsx-a11y/alt-text 警告原因、解決方法與 img alt 設定規範
- React 官方部落格:React 19 實作要點與移除 Function Component defaultProps 說明
- MDN Web Docs:JavaScript 函式預設參數 (Default parameters) 規範
- TypeScript 官方手冊:函式可選參數與預設值型別推導 (Optional and Default Parameters)
發表迴響