Component
Component
是定義為 JavaScript 類別 的 React 元件的基底類別。React 仍然支援類別元件,但我們不建議在新程式碼中使用它們。
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
- 參考
Component
context
props
state
constructor(props)
componentDidCatch(error, info)
componentDidMount()
componentDidUpdate(prevProps, prevState, snapshot?)
componentWillMount()
componentWillReceiveProps(nextProps)
componentWillUpdate(nextProps, nextState)
componentWillUnmount()
forceUpdate(callback?)
getSnapshotBeforeUpdate(prevProps, prevState)
render()
setState(nextState, callback?)
shouldComponentUpdate(nextProps, nextState, nextContext)
UNSAFE_componentWillMount()
UNSAFE_componentWillReceiveProps(nextProps, nextContext)
UNSAFE_componentWillUpdate(nextProps, nextState)
static contextType
static defaultProps
static getDerivedStateFromError(error)
static getDerivedStateFromProps(props, state)
- 用法
- 替代方案
參考
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>
);
}
}
props
傳遞給類別組件的 props 可以透過 this.props
取得。
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
<Greeting name="Taylor" />
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>
</>
);
}
}
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
方法。但是,生命週期方法,例如componentDidMount
或componentWillUnmount
,將不會在伺服器上執行。 -
當啟用嚴格模式 (Strict Mode)時,React 在開發環境中會呼叫
constructor
兩次,然後捨棄其中一個實例。這有助於您注意到需要從constructor
中移除的意外副作用。
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.onerror
或window.addEventListener('error', callback)
都會攔截被componentDidCatch
捕捉到的錯誤。相反,在正式環境中,錯誤不會向上冒泡,這表示任何上層錯誤處理程式只會收到未被componentDidCatch
明確捕捉到的錯誤。
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 節點時,這可能是必要的。
componentDidUpdate(prevProps, prevState, snapshot?)
如果您定義了 componentDidUpdate
方法,React 會在您的組件使用更新的屬性或狀態重新渲染後立即呼叫它。第一次渲染時不會呼叫此方法。
您可以使用它在更新後操作 DOM。這也是進行網路請求的常見地方,只要您將目前的屬性與先前的屬性進行比較(例如,如果屬性沒有更改,則可能不需要網路請求)。通常,您會將它與 componentDidMount
和 componentWillUnmount
一起使用:
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。比較prevProps
與this.props
來判斷哪些內容已變更。 -
prevState
:更新前的 State。比較prevState
與this.state
來判斷哪些內容已變更。 -
snapshot
:如果您實作了getSnapshotBeforeUpdate
,snapshot
將會包含您從該方法返回的值。否則,它會是undefined
。
回傳值
componentDidUpdate
不應該回傳任何值。
注意事項
-
如果定義了
shouldComponentUpdate
並回傳false
,則componentDidUpdate
將不會被呼叫。 -
componentDidUpdate
內的邏輯通常應該包裝在比較this.props
與prevProps
,以及this.state
與prevState
的條件式中。否則,可能會造成無限迴圈。 -
雖然您可以立即在
componentDidUpdate
中呼叫setState
,但盡可能避免這樣做。它會觸發額外的渲染,但這會在瀏覽器更新畫面之前發生。這可以確保即使在這種情況下render
會被呼叫兩次,使用者也不會看到中間狀態。這種模式通常會導致效能問題,但在某些少數情況下,例如需要在渲染依賴其大小或位置的內容之前測量 DOM 節點的模態框和工具提示,可能還是必要的。
componentWillMount()
componentWillReceiveProps(nextProps)
componentWillUpdate(nextProps, nextState)
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` 的動作。
`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
,React 將會重新渲染,而不會呼叫shouldComponentUpdate
。
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
屬性。在 render
、UNSAFE_componentWillReceiveProps
或 UNSAFE_componentWillUpdate
中讀取它是不安全的,因為在呼叫這些方法和 React 更新 DOM 之間存在潛在的時間間隔。
參數
-
prevProps
:更新前的 Props。比較prevProps
與this.props
來判斷哪些內容已變更。 -
prevState
:更新前的 State。比較prevState
與this.state
來判斷哪些內容已變更。
回傳值
您應該返回任何您想要的快照值類型,或者 null
。您返回的值將作為第三個參數傳遞給 componentDidUpdate
。
注意事項
- 如果定義了
shouldComponentUpdate
並返回false
,則不會呼叫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.props
、this.state
和 this.context
。
您應該將 render
方法編寫為純函式,這表示如果 props、state 和 context 相同,它應該返回相同的結果。它也不應該包含副作用(例如設定訂閱)或與瀏覽器 API 互動。副作用應該發生在事件處理程式或方法中,例如 componentDidMount
。
參數
render
不接受任何參數。
回傳值
render
可以回傳任何有效的 React 節點。這包含 React 元素,例如 <div />
、字串、數字、入口、空節點(null
、undefined
、true
和 false
),以及 React 節點的陣列。
注意事項
-
render
應該被撰寫成 props、狀態和 context 的純函式。它不應該有副作用。 -
如果定義了
shouldComponentUpdate
並且回傳false
,render
將不會被呼叫。 -
當啟用嚴格模式時,React 會在開發過程中呼叫
render
兩次,然後捨棄其中一個結果。這可以幫助您注意到需要從render
方法中移除的意外副作用。 -
render
呼叫與後續的componentDidMount
或componentDidUpdate
呼叫之間沒有一對一的對應關係。當有利時,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
。它允許您根據先前的狀態更新狀態
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
成為一個潛在的陷阱。相反地,請使用componentDidUpdate
或setState
的callback
參數,這兩個方法都保證在更新套用後才會觸發。如果您需要根據先前的狀態設定狀態,您可以將一個函式傳遞給nextState
,如上所述。
shouldComponentUpdate(nextProps, nextState, nextContext)
如果您定義了 shouldComponentUpdate
,React 會呼叫它來決定是否可以跳過重新渲染。
如果您有信心要手動撰寫,您可以比較 this.props
與 nextProps
,以及 this.state
與 nextState
,並回傳 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。將nextProps
與this.props
比較,以確定哪些內容已更改。nextState
:元件即將渲染的下一個 state。將nextState
與this.state
比較,以確定哪些內容已更改。nextContext
:元件即將渲染的下一個 context。將nextContext
與this.context
比較,以確定哪些內容已更改。僅在您指定static contextType
時才可用。
回傳值
如果您希望元件重新渲染,請回傳 true
。這是預設行為。
回傳 false
來告訴 React 可以跳過重新渲染。
注意事項
-
這個方法 *僅* 作為效能優化而存在。如果您的元件在沒有它的情況下損壞,請先修復它。
-
考慮使用
PureComponent
來代替手動撰寫shouldComponentUpdate
。PureComponent
會淺層比較 props 和 state,並降低您跳過必要更新的可能性。 -
我們不建議在
shouldComponentUpdate
中進行深度相等性檢查或使用JSON.stringify
。這會使效能變得不可預測,並取決於每個 prop 和 state 的資料結構。在最好的情況下,您可能會在應用程式中引入數秒的停頓,在最壞的情況下,您可能會導致應用程式崩潰。 -
回傳
false
並不會阻止子元件在 *它們的* state 更改時重新渲染。 -
回傳
false
並不能 *保證* 元件不會重新渲染。React 會將回傳值作為提示,但如果基於其他原因需要重新渲染您的元件,它仍然可能會選擇這樣做。
UNSAFE_componentWillMount()
如果您定義了 UNSAFE_componentWillMount
,React 會在 constructor
之後立即呼叫它。它僅基於歷史原因而存在,不應在任何新程式碼中使用。請改用下列替代方案之一。
- 要初始化狀態,請將
state
宣告為類別欄位,或在constructor
內設定this.state
。 - 如果您需要執行副作用或設定訂閱,請將該邏輯移至
componentDidMount
。
參數 ...
UNSAFE_componentWillMount
不接受任何參數。
回傳值 ...
UNSAFE_componentWillMount
不應回傳任何值。
注意事項 ...
-
如果元件實作了
static getDerivedStateFromProps
或getSnapshotBeforeUpdate
,則不會呼叫UNSAFE_componentWillMount
。 -
儘管其命名如此,但如果您的應用程式使用現代 React 功能(例如
Suspense
),UNSAFE_componentWillMount
並不能保證元件*一定*會被掛載。如果渲染嘗試被暫停(例如,因為某些子元件的程式碼尚未載入),React 將會捨棄正在進行中的樹狀結構,並在下次嘗試時從頭開始建構元件。這就是為什麼此方法被稱為「不安全」的原因。依賴於掛載的程式碼(例如新增訂閱)應放入componentDidMount
中。 -
UNSAFE_componentWillMount
是在 伺服器渲染 期間執行的唯一生命週期方法。實際上,它與constructor
完全相同,因此您應該使用constructor
來處理這種類型的邏輯。
UNSAFE_componentWillReceiveProps(nextProps, nextContext)
...
如果您定義了 UNSAFE_componentWillReceiveProps
,當元件收到新的屬性時,React 將會呼叫它。它僅基於歷史原因而存在,不應在任何新程式碼中使用。請改用下列替代方案之一。
- 如果您需要因應屬性變更來**執行副作用**(例如,擷取資料、執行動畫或重新初始化訂閱),請將該邏輯移至
componentDidUpdate
。 - 如果您需要**僅在屬性變更時避免重新計算某些資料**,請改用記憶體輔助函式。
- 如果您需要在**屬性變更時「重置」某些狀態**,請考慮將元件設為完全受控或帶有 key 的完全不受控。
- 如果您需要在**屬性變更時「調整」某些狀態**,請檢查您是否可以在渲染期間僅根據屬性計算所有必要的資訊。如果無法,請改用
static getDerivedStateFromProps
。
參數 ...
nextProps
:元件即將從其父元件接收的下一組屬性。比較nextProps
與this.props
來判斷哪些屬性發生了變化。nextContext
:元件即將從最接近的提供者接收的下一組 context。比較nextContext
與this.context
來判斷哪些 context 發生了變化。僅在指定static contextType
時可用。
回傳值
UNSAFE_componentWillReceiveProps
不應回傳任何值。
注意事項
-
如果元件實作了
static getDerivedStateFromProps
或getSnapshotBeforeUpdate
,則UNSAFE_componentWillReceiveProps
不會被呼叫。 -
儘管名稱如此,但如果您的應用程式使用像是
Suspense
之類的現代 React 功能,UNSAFE_componentWillReceiveProps
並不能保證元件*一定會*收到這些屬性。如果渲染嘗試被暫停(例如,因為某些子元件的程式碼尚未載入),React 將會捨棄正在進行中的樹狀結構,並在下次嘗試時從頭開始建構元件。在下一次渲染嘗試時,屬性可能已經不同。這就是為什麼這個方法被稱為「不安全」的原因。僅針對已提交的更新(例如重設訂閱)才應該執行的程式碼,應該放入componentDidUpdate
中。 -
UNSAFE_componentWillReceiveProps
並不表示元件收到的屬性與上次*不同*。您需要自行比較nextProps
和this.props
來檢查是否有任何更改。 -
React 在掛載期間不會使用初始屬性呼叫
UNSAFE_componentWillReceiveProps
。它只會在元件的某些屬性即將更新時呼叫此方法。例如,呼叫setState
通常不會在同一個元件內觸發UNSAFE_componentWillReceiveProps
。
UNSAFE_componentWillUpdate(nextProps, nextState)
如果您定義了 UNSAFE_componentWillUpdate
,React 將在使用新的屬性或狀態進行渲染之前呼叫它。它僅出於歷史原因而存在,不應在任何新程式碼中使用。請改用以下替代方案之一
- 如果您需要因應屬性或狀態更改而執行副作用(例如,擷取資料、執行動畫或重新初始化訂閱),請將該邏輯移至
componentDidUpdate
。 - 如果您需要從 DOM 中讀取某些資訊(例如,儲存目前的捲動位置),以便稍後在
componentDidUpdate
中使用它,請改在getSnapshotBeforeUpdate
中讀取它。
參數
nextProps
:元件即將渲染的下一個 props。將nextProps
與this.props
比較,以確定哪些內容已更改。nextState
:元件即將用於渲染的下一組狀態。比較nextState
與this.state
來判斷哪些狀態發生了變化。
回傳值
UNSAFE_componentWillUpdate
不應該回傳任何值。
注意事項
-
如果定義了
shouldComponentUpdate
並回傳false
,則UNSAFE_componentWillUpdate
不會被呼叫。 -
如果元件實作了
static getDerivedStateFromProps
或getSnapshotBeforeUpdate
,則UNSAFE_componentWillUpdate
不會被呼叫。 -
在
componentWillUpdate
期間呼叫setState
(或任何導致setState
被呼叫的方法,例如發送 Redux action)是不支援的。 -
儘管其命名如此,但如果您的應用程式使用現代 React 功能(例如
Suspense
),UNSAFE_componentWillUpdate
並不能保證元件*一定會*更新。如果渲染嘗試被暫停(例如,因為某些子元件的程式碼尚未載入),React 將會捨棄正在進行中的樹狀結構,並在下次嘗試時從頭開始建構元件。在下一次渲染嘗試時,props 和 state 可能會有所不同。這就是為什麼此方法被稱為「不安全」的原因。僅針對已提交的更新(例如重設訂閱)執行的程式碼應該放在componentDidUpdate
中。 -
UNSAFE_componentWillUpdate
並不表示元件收到的 props 或 state 與上次*不同*。您需要自行比較nextProps
與this.props
,以及nextState
與this.state
,才能檢查是否有任何更改。 -
在掛載期間,React 不會使用初始 props 和 state 呼叫
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>
);
}
}
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" />
</>
static getDerivedStateFromError(error)
如果您定義了 static getDerivedStateFromError
,當子組件(包含遠端子組件)在渲染期間拋出錯誤時,React 會呼叫它。這讓您可以顯示錯誤訊息,而不是清除 UI。
通常,它會與 componentDidCatch
一起使用,讓您可以將錯誤報告發送到某些分析服務。具有這些方法的組件稱為「*錯誤邊界*」。
參數
回傳值
static getDerivedStateFromError
應該回傳一個狀態,告知組件顯示錯誤訊息。
注意事項
static getDerivedStateFromError
應該是一個純函式。如果您想執行副作用(例如,呼叫分析服務),您還需要實作componentDidCatch
。
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
和其他類別方法之間重複使用一些程式碼。
用法
定義類別組件
要將 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,並將生產錯誤報告發送到您的錯誤報告服務。
您不需要將每個組件都包裝到一個單獨的錯誤邊界中。當您考慮錯誤邊界的粒度時,請考慮在哪裡顯示錯誤訊息才有意義。例如,在訊息應用程式中,將錯誤邊界放在對話列表周圍是有意義的。將錯誤邊界放在每條訊息周圍也是有意義的。但是,將邊界放在每個頭像周圍就沒有意義了。
替代方案
將簡單組件從類別遷移到函式
通常,您會將組件定義為函式。
例如,假設您正在將這個 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" /> </> ); }
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> </> ) }
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.serverUrl
和 this.props.roomId
。這就是為什麼 componentDidUpdate
會檢查 this.state.serverUrl
和 this.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> </> ); }
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> ) }