陷阱

我們建議將元件定義為函式而不是類別。 請參閱如何遷移。

Component 是定義為 JavaScript 類別 的 React 元件的基底類別。React 仍然支援類別元件,但我們不建議在新程式碼中使用它們。

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

參考

Component

要將 React 元件定義為類別,請繼承內建的 Component 類別並定義一個 render 方法:

import { Component } from 'react';

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

只有 render 方法是必需的,其他方法是選用的。

請參閱以下更多範例。


context

類別元件的 context 可作為 this.context 使用。僅當您使用 static contextType 指定您想要接收的 context 時,才可以使用它。

一個類別元件一次只能讀取一個 context。

class Button extends Component {
static contextType = ThemeContext;

render() {
const theme = this.context;
const className = 'button-' + theme;
return (
<button className={className}>
{this.props.children}
</button>
);
}
}

注意

在類別元件中讀取 this.context 相當於在函式元件中使用 useContext

請參閱如何遷移。


props

傳遞給類別組件的 props 可以透過 this.props 取得。

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

<Greeting name="Taylor" />

注意

在類別組件中讀取 this.props 等同於在函式組件中宣告 props

請參閱如何遷移。


state

類別組件的狀態可以透過 this.state 取得。 state 欄位必須是一個物件。不要直接修改狀態。如果要更改狀態,請使用新的狀態呼叫 setState

class Counter extends Component {
state = {
age: 42,
};

handleAgeChange = () => {
this.setState({
age: this.state.age + 1
});
};

render() {
return (
<>
<button onClick={this.handleAgeChange}>
Increment age
</button>
<p>You are {this.state.age}.</p>
</>
);
}
}

注意

在類別組件中定義 state 等同於在函式組件中呼叫 useState

請參閱如何遷移。


constructor(props)

建構函式 (constructor) 會在類別組件「掛載」(被加入到螢幕上) 之前執行。通常,在 React 中,建構函式只用於兩個目的。它允許您宣告狀態並將類別方法綁定 (bind)到類別實例。

class Counter extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
// ...
}

如果您使用現代 JavaScript 語法,很少需要建構函式。您可以使用現代瀏覽器和 Babel 等工具都支援的公開類別欄位語法來改寫上面的程式碼:

class Counter extends Component {
state = { counter: 0 };

handleClick = () => {
// ...
}

建構函式不應包含任何副作用或訂閱。

參數

  • props:組件的初始 props。

回傳值

constructor 不應回傳任何值。

注意事項

  • 不要在建構函式中執行任何副作用或訂閱。請改用 componentDidMount 來執行這些操作。

  • 在建構函式中,您需要在任何其他語句之前呼叫 super(props)。否則,建構函式執行時 this.props 將會是 undefined,這可能會造成混淆並導致錯誤。

  • 建構函式是唯一可以直接指定 this.state 的地方。在所有其他方法中,您需要改用 this.setState()。不要在建構函式中呼叫 setState

  • 當您使用伺服器渲染時,建構函式也會在伺服器上執行,然後再執行 render 方法。但是,生命週期方法,例如 componentDidMountcomponentWillUnmount,將不會在伺服器上執行。

  • 當啟用嚴格模式 (Strict Mode)時,React 在開發環境中會呼叫 constructor 兩次,然後捨棄其中一個實例。這有助於您注意到需要從 constructor 中移除的意外副作用。

注意

函式元件中沒有完全等同於 constructor 的方法。要在函式元件中宣告狀態,請呼叫 useState。為了避免重新計算初始狀態,請將函式傳遞給 useState


componentDidCatch(error, info)

如果您定義了 componentDidCatch,當某些子元件(包括遠端子元件)在渲染期間拋出錯誤時,React 將會呼叫它。這讓您可以在正式環境中將錯誤記錄到錯誤回報服務。

通常,它會與 static getDerivedStateFromError 一起使用,讓您可以根據錯誤更新狀態並向使用者顯示錯誤訊息。具有這些方法的元件稱為「*錯誤邊界*」。

請參閱範例。

參數

  • error:拋出的錯誤。實際上,它通常是 Error 的一個實例,但这並非絕對,因為 JavaScript 允許 throw 任何值,包括字串,甚至 null

  • info:一個包含錯誤附加資訊的物件。它的 componentStack 欄位包含一個堆疊追蹤,其中包含拋出錯誤的元件,以及其所有父元件的名稱和來源位置。在正式環境中,元件名稱將會被縮減。如果您設定了正式環境錯誤回報,您可以使用 sourcemaps 解碼元件堆疊,就像處理一般 JavaScript 錯誤堆疊一樣。

回傳值

componentDidCatch 不應該回傳任何值。

注意事項

  • 過去,通常會在 componentDidCatch 內呼叫 setState 來更新 UI 並顯示後備錯誤訊息。現在不建議這樣做,而是建議定義 static getDerivedStateFromError

  • React 的正式和開發版本在 componentDidCatch 處理錯誤的方式略有不同。在開發環境中,錯誤會向上冒泡到 window,這表示任何 window.onerrorwindow.addEventListener('error', callback) 都會攔截被 componentDidCatch 捕捉到的錯誤。相反,在正式環境中,錯誤不會向上冒泡,這表示任何上層錯誤處理程式只會收到未被 componentDidCatch明確捕捉到的錯誤。

注意

目前函式元件中沒有直接等同於 componentDidCatch 的方法。如果您想避免建立類別元件,請像上面那樣編寫單個 ErrorBoundary 元件,並在您的應用程式中使用它。或者,您可以使用 react-error-boundary套件,它可以幫您完成這項工作。


componentDidMount()

如果您定義了 componentDidMount 方法,React 會在您的組件被新增(掛載)到螢幕上時呼叫它。這是一個常見的開始資料提取、設定訂閱或操作 DOM 節點的地方。

如果您實作了 componentDidMount,您通常需要實作其他生命週期方法來避免錯誤。例如,如果 componentDidMount 讀取了一些狀態或屬性,您還必須實作 componentDidUpdate 來處理它們的變更,以及 componentWillUnmount 來清除 componentDidMount 所做的任何操作。

class ChatRoom extends Component {
state = {
serverUrl: 'https://127.0.0.1:1234'
};

componentDidMount() {
this.setupConnection();
}

componentDidUpdate(prevProps, prevState) {
if (
this.props.roomId !== prevProps.roomId ||
this.state.serverUrl !== prevState.serverUrl
) {
this.destroyConnection();
this.setupConnection();
}
}

componentWillUnmount() {
this.destroyConnection();
}

// ...
}

查看更多範例。

參數

componentDidMount 不接受任何參數。

回傳值

componentDidMount 不應該回傳任何值。

注意事項

  • 當啟用嚴格模式時,在開發環境中,React 會呼叫 componentDidMount,然後立即呼叫 componentWillUnmount,然後再次呼叫 componentDidMount。這可以幫助您注意到是否忘記實作 componentWillUnmount 或其邏輯是否沒有完全「鏡像」 componentDidMount 的操作。

  • 雖然您可以在 componentDidMount 中立即呼叫 setState,但盡可能避免這樣做是比較好的。它會觸發額外的渲染,但它會在瀏覽器更新螢幕之前發生。這保證了即使在這種情況下 render 會被呼叫兩次,使用者也不會看到中間狀態。請謹慎使用此模式,因為它通常會導致效能問題。在大多數情況下,您應該能夠在 constructor 中指定初始狀態。然而,對於像是模態框和工具提示之類的情況,當您需要在渲染依賴於其大小或位置的內容之前測量 DOM 節點時,這可能是必要的。

注意

在許多情況下,在類別組件中同時定義 componentDidMountcomponentDidUpdatecomponentWillUnmount 等同於在函式組件中呼叫 useEffect。在極少數情況下,程式碼需要在瀏覽器繪製之前執行,useLayoutEffect 是一個更接近的匹配。

請參閱如何遷移。


componentDidUpdate(prevProps, prevState, snapshot?)

如果您定義了 componentDidUpdate 方法,React 會在您的組件使用更新的屬性或狀態重新渲染後立即呼叫它。第一次渲染時不會呼叫此方法。

您可以使用它在更新後操作 DOM。這也是進行網路請求的常見地方,只要您將目前的屬性與先前的屬性進行比較(例如,如果屬性沒有更改,則可能不需要網路請求)。通常,您會將它與 componentDidMountcomponentWillUnmount 一起使用:

class ChatRoom extends Component {
state = {
serverUrl: 'https://127.0.0.1:1234'
};

componentDidMount() {
this.setupConnection();
}

componentDidUpdate(prevProps, prevState) {
if (
this.props.roomId !== prevProps.roomId ||
this.state.serverUrl !== prevState.serverUrl
) {
this.destroyConnection();
this.setupConnection();
}
}

componentWillUnmount() {
this.destroyConnection();
}

// ...
}

查看更多範例。

參數

  • prevProps:更新前的 Props。比較 prevPropsthis.props 來判斷哪些內容已變更。

  • prevState:更新前的 State。比較 prevStatethis.state 來判斷哪些內容已變更。

  • snapshot:如果您實作了 getSnapshotBeforeUpdatesnapshot 將會包含您從該方法返回的值。否則,它會是 undefined

回傳值

componentDidUpdate 不應該回傳任何值。

注意事項

  • 如果定義了 shouldComponentUpdate 並回傳 false,則 componentDidUpdate 將不會被呼叫。

  • componentDidUpdate 內的邏輯通常應該包裝在比較 this.propsprevProps,以及 this.stateprevState 的條件式中。否則,可能會造成無限迴圈。

  • 雖然您可以立即在 componentDidUpdate 中呼叫 setState,但盡可能避免這樣做。它會觸發額外的渲染,但這會在瀏覽器更新畫面之前發生。這可以確保即使在這種情況下 render 會被呼叫兩次,使用者也不會看到中間狀態。這種模式通常會導致效能問題,但在某些少數情況下,例如需要在渲染依賴其大小或位置的內容之前測量 DOM 節點的模態框和工具提示,可能還是必要的。

注意

在許多情況下,在類別組件中同時定義 componentDidMountcomponentDidUpdatecomponentWillUnmount 等同於在函式組件中呼叫 useEffect。在極少數情況下,程式碼需要在瀏覽器繪製之前執行,useLayoutEffect 是一個更接近的匹配。

請參閱如何遷移。


componentWillMount()

已棄用

此 API 已從 componentWillMount 更名為 UNSAFE_componentWillMount。舊名稱已棄用。在 React 未來主要版本中,將只支援新名稱。

執行 rename-unsafe-lifecycles codemod 自動更新您的元件。


componentWillReceiveProps(nextProps) ...

已棄用

此 API 已從 componentWillReceiveProps 更名為 UNSAFE_componentWillReceiveProps。舊名稱已棄用。在 React 未來主要版本中,將只支援新名稱。

執行 rename-unsafe-lifecycles codemod 自動更新您的元件。


componentWillUpdate(nextProps, nextState) ...

已棄用

此 API 已從 componentWillUpdate 更名為 UNSAFE_componentWillUpdate。舊名稱已棄用。在 React 未來主要版本中,將只支援新名稱。

執行 rename-unsafe-lifecycles codemod 自動更新您的元件。


componentWillUnmount() ...

如果您定義了 componentWillUnmount 方法,React 將在您的元件從螢幕移除(卸載)之前呼叫它。這通常是用於取消資料提取或移除訂閱的地方。

`componentWillUnmount` 內部的邏輯應該要「鏡射」`componentDidMount` componentDidMount 內部的邏輯。例如,如果 `componentDidMount` 設定了一個訂閱,`componentWillUnmount` 就應該清除該訂閱。如果你的 `componentWillUnmount` 中的清除邏輯讀取了一些 props 或 state,你通常也需要實作 `componentDidUpdate` componentDidUpdate 來清除對應於舊 props 和 state 的資源(例如訂閱)。

class ChatRoom extends Component {
state = {
serverUrl: 'https://127.0.0.1:1234'
};

componentDidMount() {
this.setupConnection();
}

componentDidUpdate(prevProps, prevState) {
if (
this.props.roomId !== prevProps.roomId ||
this.state.serverUrl !== prevState.serverUrl
) {
this.destroyConnection();
this.setupConnection();
}
}

componentWillUnmount() {
this.destroyConnection();
}

// ...
}

查看更多範例。

參數

`componentWillUnmount` 不接受任何參數。

回傳值

`componentWillUnmount` 不應該回傳任何值。

注意事項

  • 當啟用嚴格模式時,在開發環境中,React 會呼叫 `componentDidMount` componentDidMount,,然後立即呼叫 `componentWillUnmount`,接著再次呼叫 `componentDidMount`。這有助於你注意到是否忘記實作 `componentWillUnmount` 或它的邏輯沒有完全「鏡射」`componentDidMount` 的動作。

注意

在許多情況下,在類別組件中同時定義 componentDidMountcomponentDidUpdatecomponentWillUnmount 等同於在函式組件中呼叫 useEffect。在極少數情況下,程式碼需要在瀏覽器繪製之前執行,useLayoutEffect 是一個更接近的匹配。

請參閱如何遷移。


`forceUpdate(callback?)`

強制元件重新渲染。

通常,這是不必要的。如果你的元件的 `render` render 方法只從 `this.props` this.props、`this.state` this.state 或 `this.context` this.context, 讀取資料,那麼當你在你的元件或其父元件內呼叫 `setState` setState 時,它會自動重新渲染。然而,如果你的元件的 `render` 方法直接從外部資料來源讀取資料,你必須告訴 React 在該資料來源更改時更新使用者介面。這就是 `forceUpdate` 讓你做的事情。

盡量避免使用 `forceUpdate`,並且只在 `render` 中從 `this.props` 和 `this.state` 讀取資料。

參數

  • **選用** `callback` 如果有指定,React 會在更新提交後呼叫你提供的 `callback`。

回傳值

`forceUpdate` 不回傳任何值。

注意事項

注意

在函式元件中,透過讀取外部資料來源並使用 forceUpdate 強制類別元件重新渲染以響應其變化,已被 useSyncExternalStore 取代。


getSnapshotBeforeUpdate(prevProps, prevState)

如果您實作 getSnapshotBeforeUpdate,React 將在更新 DOM 之前立即呼叫它。它允許您的元件在 DOM(例如捲動位置)可能更改之前從中擷取一些資訊。此生命週期方法返回的任何值都將作為參數傳遞給 componentDidUpdate

例如,您可以在需要在更新期間保留其捲動位置的 UI(如聊天主題)中使用它

class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}

getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}

render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}

在上面的例子中,重要的是直接在 getSnapshotBeforeUpdate 中讀取 scrollHeight 屬性。在 renderUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate 中讀取它是不安全的,因為在呼叫這些方法和 React 更新 DOM 之間存在潛在的時間間隔。

參數

  • prevProps:更新前的 Props。比較 prevPropsthis.props 來判斷哪些內容已變更。

  • prevState:更新前的 State。比較 prevStatethis.state 來判斷哪些內容已變更。

回傳值

您應該返回任何您想要的快照值類型,或者 null。您返回的值將作為第三個參數傳遞給 componentDidUpdate

注意事項

注意

目前,函式元件沒有與 getSnapshotBeforeUpdate 等效的函式。這種用例非常少見,但如果您需要它,目前您必須編寫一個類別元件。


render()

render 方法是類別元件中唯一必需的方法。

render 方法應該指定您想要在螢幕上顯示的內容,例如

import { Component } from 'react';

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

React 可能隨時呼叫 render,因此您不應該假設它在特定時間運行。通常,render 方法應該返回一段 JSX,但也支援一些 其他返回類型(如字串)。為了計算返回的 JSX,render 方法可以讀取 this.propsthis.statethis.context

您應該將 render 方法編寫為純函式,這表示如果 props、state 和 context 相同,它應該返回相同的結果。它也不應該包含副作用(例如設定訂閱)或與瀏覽器 API 互動。副作用應該發生在事件處理程式或方法中,例如 componentDidMount

參數

render 不接受任何參數。

回傳值

render 可以回傳任何有效的 React 節點。這包含 React 元素,例如 <div />、字串、數字、入口、空節點(nullundefinedtruefalse),以及 React 節點的陣列。

注意事項

  • render 應該被撰寫成 props、狀態和 context 的純函式。它不應該有副作用。

  • 如果定義了 shouldComponentUpdate 並且回傳 falserender 將不會被呼叫。

  • 當啟用嚴格模式時,React 會在開發過程中呼叫 render 兩次,然後捨棄其中一個結果。這可以幫助您注意到需要從 render 方法中移除的意外副作用。

  • render 呼叫與後續的 componentDidMountcomponentDidUpdate 呼叫之間沒有一對一的對應關係。當有利時,React 可能會捨棄一些 render 呼叫的結果。


setState(nextState, callback?)

呼叫 setState 來更新 React 元件的狀態。

class Form extends Component {
state = {
name: 'Taylor',
};

handleNameChange = (e) => {
const newName = e.target.value;
this.setState({
name: newName
});
}

render() {
return (
<>
<input value={this.state.name} onChange={this.handleNameChange} />
<p>Hello, {this.state.name}.</p>
</>
);
}
}

setState 將變更排入元件狀態的佇列中。它告訴 React 這個元件及其子元件需要使用新的狀態重新渲染。這是您響應互動更新使用者介面的主要方式。

陷阱

呼叫 setState 並不會 改變已執行程式碼中的目前狀態

function handleClick() {
console.log(this.state.name); // "Taylor"
this.setState({
name: 'Robin'
});
console.log(this.state.name); // Still "Taylor"!
}

它只會影響從*下一次*渲染開始 this.state 將回傳的內容。

您也可以將函式傳遞給 setState。它允許您根據先前的狀態更新狀態

handleIncreaseAge = () => {
this.setState(prevState => {
return {
age: prevState.age + 1
};
});
}

您不必這樣做,但如果您想在同一個事件中多次更新狀態,這會很方便。

參數

  • nextState:物件或函式。

    • 如果您傳遞物件作為 nextState,它將會被淺層合併到 this.state 中。
    • 如果您傳遞函式作為 nextState,它將被視為*更新器函式*。它必須是純函式,應將待處理狀態和 props 作為參數,並應回傳要淺層合併到 this.state 中的物件。React 會將您的更新器函式放入佇列中,並重新渲染您的元件。在下一次渲染期間,React 將會透過將所有佇列中的更新器套用到先前的狀態來計算下一個狀態。
  • 可選 callback:如果指定,React 將會在更新提交後呼叫您提供的 callback

回傳值

setState 不會回傳任何東西。

注意事項

  • setState 視為更新元件的*請求*,而不是立即執行的命令。當多個元件響應事件更新其狀態時,React 將會批次處理其更新,並在事件結束時以單次傳遞重新渲染它們。在極少數情況下,您需要強制同步套用特定狀態更新,您可以將其包裝在 flushSync 中,但这可能會損害效能。

  • setState 並不會立即更新 this.state。這使得在呼叫 setState 之後立即讀取 this.state 成為一個潛在的陷阱。相反地,請使用 componentDidUpdatesetStatecallback 參數,這兩個方法都保證在更新套用後才會觸發。如果您需要根據先前的狀態設定狀態,您可以將一個函式傳遞給 nextState,如上所述。

注意

在類別元件中呼叫 setState 類似於在函式元件中呼叫 set 函式

請參閱如何遷移。


shouldComponentUpdate(nextProps, nextState, nextContext)

如果您定義了 shouldComponentUpdate,React 會呼叫它來決定是否可以跳過重新渲染。

如果您有信心要手動撰寫,您可以比較 this.propsnextProps,以及 this.statenextState,並回傳 false 來告訴 React 可以跳過更新。

class Rectangle extends Component {
state = {
isHovered: false
};

shouldComponentUpdate(nextProps, nextState) {
if (
nextProps.position.x === this.props.position.x &&
nextProps.position.y === this.props.position.y &&
nextProps.size.width === this.props.size.width &&
nextProps.size.height === this.props.size.height &&
nextState.isHovered === this.state.isHovered
) {
// Nothing has changed, so a re-render is unnecessary
return false;
}
return true;
}

// ...
}

當收到新的 props 或 state 時,React 會在渲染之前呼叫 shouldComponentUpdate。預設值為 true。這個方法在初始渲染或使用 forceUpdate 時不會被呼叫。

參數

  • nextProps:元件即將渲染的下一個 props。將 nextPropsthis.props 比較,以確定哪些內容已更改。
  • nextState:元件即將渲染的下一個 state。將 nextStatethis.state 比較,以確定哪些內容已更改。
  • nextContext:元件即將渲染的下一個 context。將 nextContextthis.context 比較,以確定哪些內容已更改。僅在您指定 static contextType 時才可用。

回傳值

如果您希望元件重新渲染,請回傳 true。這是預設行為。

回傳 false 來告訴 React 可以跳過重新渲染。

注意事項

  • 這個方法 *僅* 作為效能優化而存在。如果您的元件在沒有它的情況下損壞,請先修復它。

  • 考慮使用 PureComponent 來代替手動撰寫 shouldComponentUpdatePureComponent 會淺層比較 props 和 state,並降低您跳過必要更新的可能性。

  • 我們不建議在 shouldComponentUpdate 中進行深度相等性檢查或使用 JSON.stringify。這會使效能變得不可預測,並取決於每個 prop 和 state 的資料結構。在最好的情況下,您可能會在應用程式中引入數秒的停頓,在最壞的情況下,您可能會導致應用程式崩潰。

  • 回傳 false 並不會阻止子元件在 *它們的* state 更改時重新渲染。

  • 回傳 false 並不能 *保證* 元件不會重新渲染。React 會將回傳值作為提示,但如果基於其他原因需要重新渲染您的元件,它仍然可能會選擇這樣做。

注意

使用 shouldComponentUpdate 優化類別元件類似於使用 memo 優化函式元件。函式元件還可以使用 useMemo 進行更精細的優化。


UNSAFE_componentWillMount()

如果您定義了 UNSAFE_componentWillMount,React 會在 constructor 之後立即呼叫它。它僅基於歷史原因而存在,不應在任何新程式碼中使用。請改用下列替代方案之一。

  • 要初始化狀態,請將 state 宣告為類別欄位,或在 constructor 內設定 this.state
  • 如果您需要執行副作用或設定訂閱,請將該邏輯移至 componentDidMount

請參閱從不安全的生命週期遷移的範例。

參數 ...

UNSAFE_componentWillMount 不接受任何參數。

回傳值 ...

UNSAFE_componentWillMount 不應回傳任何值。

注意事項 ...

  • 如果元件實作了 static getDerivedStateFromPropsgetSnapshotBeforeUpdate,則不會呼叫 UNSAFE_componentWillMount

  • 儘管其命名如此,但如果您的應用程式使用現代 React 功能(例如 Suspense),UNSAFE_componentWillMount 並不能保證元件*一定*會被掛載。如果渲染嘗試被暫停(例如,因為某些子元件的程式碼尚未載入),React 將會捨棄正在進行中的樹狀結構,並在下次嘗試時從頭開始建構元件。這就是為什麼此方法被稱為「不安全」的原因。依賴於掛載的程式碼(例如新增訂閱)應放入 componentDidMount 中。

  • UNSAFE_componentWillMount 是在 伺服器渲染 期間執行的唯一生命週期方法。實際上,它與 constructor 完全相同,因此您應該使用 constructor 來處理這種類型的邏輯。

注意

在類別元件的 UNSAFE_componentWillMount 內呼叫 setState 來初始化狀態,相當於將該狀態作為初始狀態傳遞給函式元件中的 useState


UNSAFE_componentWillReceiveProps(nextProps, nextContext) ...

如果您定義了 UNSAFE_componentWillReceiveProps,當元件收到新的屬性時,React 將會呼叫它。它僅基於歷史原因而存在,不應在任何新程式碼中使用。請改用下列替代方案之一。

  • 如果您需要因應屬性變更來**執行副作用**(例如,擷取資料、執行動畫或重新初始化訂閱),請將該邏輯移至 componentDidUpdate
  • 如果您需要**僅在屬性變更時避免重新計算某些資料**,請改用記憶體輔助函式
  • 如果您需要在**屬性變更時「重置」某些狀態**,請考慮將元件設為完全受控帶有 key 的完全不受控
  • 如果您需要在**屬性變更時「調整」某些狀態**,請檢查您是否可以在渲染期間僅根據屬性計算所有必要的資訊。如果無法,請改用 static getDerivedStateFromProps

請參閱從不安全的生命週期遷移的範例。

參數 ...

  • nextProps:元件即將從其父元件接收的下一組屬性。比較 nextPropsthis.props 來判斷哪些屬性發生了變化。
  • nextContext:元件即將從最接近的提供者接收的下一組 context。比較 nextContextthis.context 來判斷哪些 context 發生了變化。僅在指定 static contextType 時可用。

回傳值

UNSAFE_componentWillReceiveProps 不應回傳任何值。

注意事項

  • 如果元件實作了 static getDerivedStateFromPropsgetSnapshotBeforeUpdate,則 UNSAFE_componentWillReceiveProps 不會被呼叫。

  • 儘管名稱如此,但如果您的應用程式使用像是 Suspense 之類的現代 React 功能,UNSAFE_componentWillReceiveProps 並不能保證元件*一定會*收到這些屬性。如果渲染嘗試被暫停(例如,因為某些子元件的程式碼尚未載入),React 將會捨棄正在進行中的樹狀結構,並在下次嘗試時從頭開始建構元件。在下一次渲染嘗試時,屬性可能已經不同。這就是為什麼這個方法被稱為「不安全」的原因。僅針對已提交的更新(例如重設訂閱)才應該執行的程式碼,應該放入 componentDidUpdate 中。

  • UNSAFE_componentWillReceiveProps 並不表示元件收到的屬性與上次*不同*。您需要自行比較 nextPropsthis.props 來檢查是否有任何更改。

  • React 在掛載期間不會使用初始屬性呼叫 UNSAFE_componentWillReceiveProps。它只會在元件的某些屬性即將更新時呼叫此方法。例如,呼叫 setState 通常不會在同一個元件內觸發 UNSAFE_componentWillReceiveProps

注意

在類別元件的 UNSAFE_componentWillReceiveProps 中呼叫 setState 來「調整」狀態,相當於在函式元件渲染期間useState 呼叫 set 函式


UNSAFE_componentWillUpdate(nextProps, nextState)

如果您定義了 UNSAFE_componentWillUpdate,React 將在使用新的屬性或狀態進行渲染之前呼叫它。它僅出於歷史原因而存在,不應在任何新程式碼中使用。請改用以下替代方案之一

  • 如果您需要因應屬性或狀態更改而執行副作用(例如,擷取資料、執行動畫或重新初始化訂閱),請將該邏輯移至 componentDidUpdate
  • 如果您需要從 DOM 中讀取某些資訊(例如,儲存目前的捲動位置),以便稍後在 componentDidUpdate 中使用它,請改在 getSnapshotBeforeUpdate 中讀取它。

請參閱從不安全的生命週期遷移的範例。

參數

  • nextProps:元件即將渲染的下一個 props。將 nextPropsthis.props 比較,以確定哪些內容已更改。
  • nextState:元件即將用於渲染的下一組狀態。比較 nextStatethis.state 來判斷哪些狀態發生了變化。

回傳值

UNSAFE_componentWillUpdate 不應該回傳任何值。

注意事項

  • 如果定義了 shouldComponentUpdate 並回傳 false,則 UNSAFE_componentWillUpdate 不會被呼叫。

  • 如果元件實作了 static getDerivedStateFromPropsgetSnapshotBeforeUpdate,則 UNSAFE_componentWillUpdate 不會被呼叫。

  • componentWillUpdate 期間呼叫 setState(或任何導致 setState 被呼叫的方法,例如發送 Redux action)是不支援的。

  • 儘管其命名如此,但如果您的應用程式使用現代 React 功能(例如 Suspense),UNSAFE_componentWillUpdate 並不能保證元件*一定會*更新。如果渲染嘗試被暫停(例如,因為某些子元件的程式碼尚未載入),React 將會捨棄正在進行中的樹狀結構,並在下次嘗試時從頭開始建構元件。在下一次渲染嘗試時,props 和 state 可能會有所不同。這就是為什麼此方法被稱為「不安全」的原因。僅針對已提交的更新(例如重設訂閱)執行的程式碼應該放在 componentDidUpdate 中。

  • UNSAFE_componentWillUpdate 並不表示元件收到的 props 或 state 與上次*不同*。您需要自行比較 nextPropsthis.props,以及 nextStatethis.state,才能檢查是否有任何更改。

  • 在掛載期間,React 不會使用初始 props 和 state 呼叫 UNSAFE_componentWillUpdate

注意

函式元件中沒有直接對應 UNSAFE_componentWillUpdate 的方法。


static contextType

如果您想從類別元件中讀取 this.context,您必須指定它需要讀取哪個 context。您指定為 static contextType 的 context 必須是先前由 createContext 建立的值。

class Button extends Component {
static contextType = ThemeContext;

render() {
const theme = this.context;
const className = 'button-' + theme;
return (
<button className={className}>
{this.props.children}
</button>
);
}
}

注意

在類別元件中讀取 this.context 相當於在函式元件中使用 useContext

請參閱如何遷移。


static defaultProps

您可以定義 static defaultProps 來設定類別的預設 props。它們將用於 undefined 和遺漏的 props,但不用於 null props。

例如,以下是定義 color prop 應預設為 'blue' 的方法

class Button extends Component {
static defaultProps = {
color: 'blue'
};

render() {
return <button className={this.props.color}>click me</button>;
}
}

如果未提供 color prop 或其為 undefined,則預設會將其設定為 'blue'

<>
{/* this.props.color is "blue" */}
<Button />

{/* this.props.color is "blue" */}
<Button color={undefined} />

{/* this.props.color is null */}
<Button color={null} />

{/* this.props.color is "red" */}
<Button color="red" />
</>

注意

在類別元件中定義 defaultProps 類似於在函式元件中使用 預設值


static getDerivedStateFromError(error)

如果您定義了 static getDerivedStateFromError,當子組件(包含遠端子組件)在渲染期間拋出錯誤時,React 會呼叫它。這讓您可以顯示錯誤訊息,而不是清除 UI。

通常,它會與 componentDidCatch 一起使用,讓您可以將錯誤報告發送到某些分析服務。具有這些方法的組件稱為「*錯誤邊界*」。

請參閱範例。

參數

  • error:拋出的錯誤。實際上,它通常是 Error 的一個實例,但这並非絕對,因為 JavaScript 允許 throw 任何值,包括字串,甚至 null

回傳值

static getDerivedStateFromError 應該回傳一個狀態,告知組件顯示錯誤訊息。

注意事項

  • static getDerivedStateFromError 應該是一個純函式。如果您想執行副作用(例如,呼叫分析服務),您還需要實作 componentDidCatch

注意

目前在函式組件中沒有 static getDerivedStateFromError 的直接對應方法。如果您想避免建立類別組件,請撰寫一個單獨的 ErrorBoundary 組件,如上所示,並在您的應用程式中使用它。或者,使用 react-error-boundary 套件,它可以做到這一點。


static getDerivedStateFromProps(props, state)

如果您定義了 static getDerivedStateFromProps,React 會在呼叫 render 之前立即呼叫它,無論是在初始掛載時還是在後續更新時。它應該回傳一個物件來更新狀態,或者回傳 null 表示不更新任何內容。

這個方法適用於少數情況,其中狀態會隨著時間推移而依賴屬性的變化。例如,這個 Form 組件會在 userID 屬性變更時重置 email 狀態

class Form extends Component {
state = {
email: this.props.defaultEmail,
prevUserID: this.props.userID
};

static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevUserID) {
return {
prevUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}

// ...
}

請注意,這種模式需要您在狀態中保留屬性的先前值(例如 userID)(例如 prevUserID)。

陷阱

衍生狀態會導致程式碼冗長,並使您的組件難以理解。請確保您熟悉更簡單的替代方案:

參數

  • props:組件即將渲染的下一個屬性。
  • state:組件即將渲染的下一個狀態。

回傳值

static getDerivedStateFromProps 會回傳一個物件來更新狀態,或者回傳 null 表示不更新任何內容。

注意事項

  • 這個方法在*每次*渲染時都會被觸發,無論觸發原因為何。這與 UNSAFE_componentWillReceiveProps 不同,後者僅在父組件導致重新渲染時觸發,而不是因為本地的 setState 觸發。

  • 這個方法無法存取組件實例。如果需要,您可以透過在類別定義之外提取組件屬性和狀態的純函數,在 static getDerivedStateFromProps 和其他類別方法之間重複使用一些程式碼。

注意

在類別組件中實作 static getDerivedStateFromProps 等同於在函式組件渲染期間呼叫 useStateset 函數


用法

定義類別組件

要將 React 元件定義為類別,請繼承內建的 Component 類別並定義一個 render 方法:

import { Component } from 'react';

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

每當 React 需要決定在螢幕上顯示什麼內容時,它都會呼叫您的 render 方法。通常,您會從它回傳一些 JSX。您的 render 方法應該是一個 純函數: 它應該只計算 JSX。

函式組件 類似,類別組件可以透過 屬性 (props) 從其父組件接收資訊。但是,讀取屬性的語法不同。例如,如果父組件渲染 <Greeting name="Taylor" />,那麼您可以從 this.props 讀取 name 屬性,例如 this.props.name

import { Component } from 'react';

class Greeting extends Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

export default function App() {
  return (
    <>
      <Greeting name="Sara" />
      <Greeting name="Cahal" />
      <Greeting name="Edite" />
    </>
  );
}

請注意,類別組件內部不支援 Hooks(以 use 開頭的函數,例如 useState)。

陷阱

我們建議將組件定義為函數而不是類別。瞭解如何遷移


將狀態添加到類別組件

要將 狀態 新增到類別,請將一個物件指派給名為 state 的屬性。要更新狀態,請呼叫 this.setState

import { Component } from 'react';

export default class Counter extends Component {
  state = {
    name: 'Taylor',
    age: 42,
  };

  handleNameChange = (e) => {
    this.setState({
      name: e.target.value
    });
  }

  handleAgeChange = () => {
    this.setState({
      age: this.state.age + 1 
    });
  };

  render() {
    return (
      <>
        <input
          value={this.state.name}
          onChange={this.handleNameChange}
        />
        <button onClick={this.handleAgeChange}>
          Increment age
        </button>
        <p>Hello, {this.state.name}. You are {this.state.age}.</p>
      </>
    );
  }
}

陷阱

我們建議將組件定義為函數而不是類別。瞭解如何遷移


將生命週期方法新增到類別組件

您可以在類別上定義一些特殊方法。

如果您定義了 componentDidMount 方法,React 會在您的組件被加入(掛載)到螢幕上時呼叫它。React 會在您的組件因屬性或狀態改變而重新渲染後呼叫 componentDidUpdate。React 會在您的組件從螢幕上被移除(卸載)後呼叫 componentWillUnmount

如果您實作了 componentDidMount,您通常需要實作所有三個生命週期方法以避免錯誤。例如,如果 componentDidMount 讀取了一些狀態或屬性,您也必須實作 componentDidUpdate 來處理它們的變化,以及實作 componentWillUnmount 來清理 componentDidMount 所做的任何事情。

例如,這個 ChatRoom 組件保持聊天連線與屬性和狀態同步。

import { Component } from 'react';
import { createConnection } from './chat.js';

export default class ChatRoom extends Component {
  state = {
    serverUrl: 'https://127.0.0.1:1234'
  };

  componentDidMount() {
    this.setupConnection();
  }

  componentDidUpdate(prevProps, prevState) {
    if (
      this.props.roomId !== prevProps.roomId ||
      this.state.serverUrl !== prevState.serverUrl
    ) {
      this.destroyConnection();
      this.setupConnection();
    }
  }

  componentWillUnmount() {
    this.destroyConnection();
  }

  setupConnection() {
    this.connection = createConnection(
      this.state.serverUrl,
      this.props.roomId
    );
    this.connection.connect();    
  }

  destroyConnection() {
    this.connection.disconnect();
    this.connection = null;
  }

  render() {
    return (
      <>
        <label>
          Server URL:{' '}
          <input
            value={this.state.serverUrl}
            onChange={e => {
              this.setState({
                serverUrl: e.target.value
              });
            }}
          />
        </label>
        <h1>Welcome to the {this.props.roomId} room!</h1>
      </>
    );
  }
}

請注意,在開發過程中,當 嚴格模式 開啟時,React 會呼叫 componentDidMount,立即呼叫 componentWillUnmount,然後再次呼叫 componentDidMount。這有助於您注意到是否忘記實作 componentWillUnmount 或其邏輯沒有完全「鏡像」 componentDidMount 的行為。

陷阱

我們建議將組件定義為函式而不是類別。請參閱如何遷移。


使用錯誤邊界捕捉渲染錯誤

預設情況下,如果您的應用程式在渲染過程中拋出錯誤,React 將會從螢幕上移除其 UI。為了防止這種情況,您可以將 UI 的一部分包裝到一個*錯誤邊界*中。錯誤邊界是一個特殊的組件,它允許您顯示一些後備 UI 來代替崩潰的部分,例如錯誤訊息。

要實作一個錯誤邊界組件,您需要提供 static getDerivedStateFromError,它允許您根據錯誤更新狀態並向使用者顯示錯誤訊息。您也可以選擇性地實作 componentDidCatch 來新增一些額外的邏輯,例如將錯誤記錄到分析服務。

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

componentDidCatch(error, info) {
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logErrorToMyService(error, info.componentStack);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return this.props.fallback;
}

return this.props.children;
}
}

然後您可以用它來包裝組件樹的一部分。

<ErrorBoundary fallback={<p>Something went wrong</p>}>
<Profile />
</ErrorBoundary>

如果 Profile 或其子組件拋出錯誤,ErrorBoundary 將會「捕捉」到該錯誤,顯示一個包含您提供的錯誤訊息的後備 UI,並將生產錯誤報告發送到您的錯誤報告服務。

您不需要將每個組件都包裝到一個單獨的錯誤邊界中。當您考慮錯誤邊界的粒度時,請考慮在哪裡顯示錯誤訊息才有意義。例如,在訊息應用程式中,將錯誤邊界放在對話列表周圍是有意義的。將錯誤邊界放在每條訊息周圍也是有意義的。但是,將邊界放在每個頭像周圍就沒有意義了。

注意

目前沒有辦法將錯誤邊界寫成函式組件。但是,您不必自己編寫錯誤邊界類別。例如,您可以使用 react-error-boundary 來代替。


替代方案

將簡單組件從類別遷移到函式

通常,您會將組件定義為函式

例如,假設您正在將這個 Greeting 類別組件轉換為函式。

import { Component } from 'react';

class Greeting extends Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

export default function App() {
  return (
    <>
      <Greeting name="Sara" />
      <Greeting name="Cahal" />
      <Greeting name="Edite" />
    </>
  );
}

定義一個名為 Greeting 的函式。這是您將移動 render 函式主體的地方。

function Greeting() {
// ... move the code from the render method here ...
}

使用 解構語法定義 name 屬性並直接讀取它,而不是使用 this.props.name

function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}

這是一個完整的範例。

function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

export default function App() {
  return (
    <>
      <Greeting name="Sara" />
      <Greeting name="Cahal" />
      <Greeting name="Edite" />
    </>
  );
}


將具有狀態的組件從類別遷移到函式

假設您正在將這個 Counter 類別元件轉換為函式

import { Component } from 'react';

export default class Counter extends Component {
  state = {
    name: 'Taylor',
    age: 42,
  };

  handleNameChange = (e) => {
    this.setState({
      name: e.target.value
    });
  }

  handleAgeChange = (e) => {
    this.setState({
      age: this.state.age + 1 
    });
  };

  render() {
    return (
      <>
        <input
          value={this.state.name}
          onChange={this.handleNameChange}
        />
        <button onClick={this.handleAgeChange}>
          Increment age
        </button>
        <p>Hello, {this.state.name}. You are {this.state.age}.</p>
      </>
    );
  }
}

首先,宣告一個具有必要 狀態變數 的函式:

import { useState } from 'react';

function Counter() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
// ...

接下來,轉換事件處理器

function Counter() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);

function handleNameChange(e) {
setName(e.target.value);
}

function handleAgeChange() {
setAge(age + 1);
}
// ...

最後,將所有以 this 開頭的參照替換為您在元件中定義的變數和函式。例如,將 this.state.age 替換為 age,并将 this.handleNameChange 替換為 handleNameChange

以下是完整轉換後的元件

import { useState } from 'react';

export default function Counter() {
  const [name, setName] = useState('Taylor');
  const [age, setAge] = useState(42);

  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleAgeChange() {
    setAge(age + 1);
  }

  return (
    <>
      <input
        value={name}
        onChange={handleNameChange}
      />
      <button onClick={handleAgeChange}>
        Increment age
      </button>
      <p>Hello, {name}. You are {age}.</p>
    </>
  )
}


將具有生命週期方法的元件從類別遷移到函式

假設您正在將這個具有生命週期方法的 ChatRoom 類別元件轉換為函式

import { Component } from 'react';
import { createConnection } from './chat.js';

export default class ChatRoom extends Component {
  state = {
    serverUrl: 'https://127.0.0.1:1234'
  };

  componentDidMount() {
    this.setupConnection();
  }

  componentDidUpdate(prevProps, prevState) {
    if (
      this.props.roomId !== prevProps.roomId ||
      this.state.serverUrl !== prevState.serverUrl
    ) {
      this.destroyConnection();
      this.setupConnection();
    }
  }

  componentWillUnmount() {
    this.destroyConnection();
  }

  setupConnection() {
    this.connection = createConnection(
      this.state.serverUrl,
      this.props.roomId
    );
    this.connection.connect();    
  }

  destroyConnection() {
    this.connection.disconnect();
    this.connection = null;
  }

  render() {
    return (
      <>
        <label>
          Server URL:{' '}
          <input
            value={this.state.serverUrl}
            onChange={e => {
              this.setState({
                serverUrl: e.target.value
              });
            }}
          />
        </label>
        <h1>Welcome to the {this.props.roomId} room!</h1>
      </>
    );
  }
}

首先,驗證您的 componentWillUnmount 是否執行了與 componentDidMount 相反的操作。在上面的例子中,的確如此:它斷開了 componentDidMount 建立的連線。如果缺少此類邏輯,請先新增它。

接下來,驗證您的 componentDidUpdate 方法是否處理了您在 componentDidMount 中使用的任何 props 和 state 的更改。在上面的例子中,componentDidMount 呼叫了 setupConnection,它讀取了 this.state.serverUrlthis.props.roomId。這就是為什麼 componentDidUpdate 會檢查 this.state.serverUrlthis.props.roomId 是否已更改,如果已更改,則重置連線。如果您的 componentDidUpdate 邏輯遺失或未處理所有相關 props 和 state 的更改,請先修復它。

在上面的例子中,生命週期方法中的邏輯將元件連接到 React 之外的系統(聊天伺服器)。要將元件連接到外部系統,請將此邏輯描述為單個 Effect:

import { useState, useEffect } from 'react';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://127.0.0.1:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);

// ...
}

這個 useEffect 呼叫等同於上面生命週期方法中的邏輯。如果您的生命週期方法執行了多個不相關的操作,請將它們拆分為多個獨立的 Effect。 這裡有一個您可以操作的完整範例

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://127.0.0.1:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

注意

如果您的元件未與任何外部系統同步,您可能不需要 Effect。


將具有 context 的元件從類別遷移到函式

在此範例中,PanelButton 類別元件從 context 中讀取 this.context

import { createContext, Component } from 'react';

const ThemeContext = createContext(null);

class Panel extends Component {
  static contextType = ThemeContext;

  render() {
    const theme = this.context;
    const className = 'panel-' + theme;
    return (
      <section className={className}>
        <h1>{this.props.title}</h1>
        {this.props.children}
      </section>
    );    
  }
}

class Button extends Component {
  static contextType = ThemeContext;

  render() {
    const theme = this.context;
    const className = 'button-' + theme;
    return (
      <button className={className}>
        {this.props.children}
      </button>
    );
  }
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

當您將它們轉換為函式元件時,請將 this.context 替換為 useContext 呼叫

import { createContext, useContext } from 'react';

const ThemeContext = createContext(null);

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}