原文:The functional side of React
作者:Andrea Chiarelli
譯者:博軒
React 是現在最流行的 JavaScript 庫之一。使用 React 可以非常輕鬆地建立 Web 使用者互動介面。 它的成功有很多因素,但也許其中一個因素是清晰有效的程式設計方法。
在 React 的世界中,UI 是由一個一個元件所組成的。元件可以組合在一起以建立其他元件, 應用本身就是一個包含了所有元件的一個大元件。開發者使用 React 會很容易聯想到:物件導向程式設計 。因為定義元件的語法本身,就會給人這種感覺:
class HelloReact extends Component {
render() {
return (<div>Hello React!</div>);
}
}
然鵝,在物件導向的的表象之下,React 隱藏了一種函式式的特質。讓我們看看這些特質都是什麼?
使用 render()
渲染輸出
React 元件的一大特徵是是包含了 render()
方法。沒有包含 render()
方法的元件不是 React 元件。render()
方法總會返回一個 React 元素,這種行為就像是元件的一種特徵一樣。換句話說,React 會要求任何元件必須有輸出。元件是根據輸入來返回輸出的,這樣來考慮元件的話,就會讓你感覺元件更像一個函式,而不是一個物件。
元件就是一個函式
實際上,您不僅可以將 React 元件視為函式。 您還可以用函式來實現元件。 以下程式碼展示瞭如何使用函式實現上面定義的元件:
const HelloReact = () => <div>Hello React!</div>;
如您所見,它是一種實現元件的簡單而緊湊的方法。
此外,您可以將引數傳遞給函式:
const Hello = (props) => <div>Hello {props.name}!</div>;
在上面的示例中,您傳遞了 props
引數,這裡的 props
物件用於將資料從一個元件傳遞到另一個元件。
props
的不變性
如你所知,props
是不可改變的,你可以閱讀他們,但你無法改變它們。這也正是 React
元件的函式特性之一。props
是元件的輸入引數,因此給予其不可變性可以避免副作用。實際上,這也是函數語言程式設計的基本原則之一:函式不能更改輸入引數。
譯註:純函式的介紹,推薦看下這個 - 函數語言程式設計 - wiki,以及我之前翻譯過的 【譯】JavaScript 中的函數語言程式設計原理
單項資料流
關於 React
,它的另一個特性就是單項資料流。這意味著在元件的層次結構中,資料必須從較高的元件流向較低的元件,反之亦然。如果我們將元件視為物件,這個觀點就會顯得有些牽強。相反,如果我們從函式的角度去考慮元件,就會顯得很自然。
考慮一下下面的程式碼:
class App extends Component {
render() {
return (<Hello name="React" />)
};
}
class Hello extends Component {
render() {
return (<div>Hello {props.name}!</div>);
}
}
例子中有兩個元件:App
元件使用 Hello
元件來展示 “Hello React!” 。同時,例子中也隱式的定義了元件之間的層次結構。但是乍一看,無法通過 name
屬性來來看清楚資料的流向。
現在,讓我們來看看使用函式修改之後的程式碼:
const App = () => <Hello name="React" />;
const Hello = (props) => <div>Hello {props.name}!</div>;
通過上面元件的層級結構可以清楚的看出,不過是 App()
和 Hello()
,兩個函式的組合。你也可以將其視為下面的內容:
const App = () => Hello("React");
從上面的例子中就可以很明顯的看出,“React” 文字是怎樣在 App 元件中傳遞的了。
譯註:這裡原文例子中使用的是 const App = () => Hello("John");
,和文章中的 "React" 不相符,所以我改了例子中傳遞的文案,嘿嘿嘿...
組合 vs 繼承
在物件導向的程式設計範例中,對於類,很容易將繼承視為一種標準。 但是,如果從函式的角度考慮 React
元件,繼承就會顯得不那麼自然。
例如,假設您要升級 Hello
元件,以便它還可以顯示 “歡迎訊息” 。 您可以將其與 Hello
元件組合來建立新元件,比如下面的例子:
const HelloAndWelcome = (props) => <div><Hello {…props} /><p>Welcome to React!</p></div>;
正如Facebook團隊所宣稱的那樣,真的很少需要繼承。
高階元件和高階函式
高階元件是 React
程式設計中的常見模式。 它允許重用元件邏輯來建立新元件。 簡單來說,高階元件是一個函式,它將一個元件作為輸入並返回一個新元件作為其輸出。 以下是高階元件的示例:
const AddWelcome = (GreetingComponent) => {
class TheNewComponent extends Component {
render() {
return (
<div>
<GreetingComponent {…this.props}/>
<p>Welcome to React!</p>
</div>);
}
}
return TheNewComponent;
};
函式 AddWelcome()
接受 GreetingComponent
引數,並在新元件 TheNewComponent
定義的 render()
方法中使用它。 這個新元件只是在 GreetingComponent
的輸出後新增一條歡迎訊息。 最後,函式 AddWelcome()
會返回新元件。
您可以使用此功能,如以下示例所示:
const HelloAndWelcome = AddWelcome(Hello);
通過使用 AddWelcome()
包裝 Hello
元件,您將獲得一個新元件。
您可以將上面例子中的高階函式 AddWelcome()
用函式的方式來重新整理:
const AddWelcome = (GreetingComponent) => {
const TheNewComponent = (props) => <div><GreetingComponent {…props}/><p>Welcome to React!</p></div>;
return TheNewComponent;
};
如你所見,這就像是一個高階函式,這個函式接受一個函式,返回一個新的 React
元素。
元件和狀態
應用程式的狀態是隨時間變化的資料集。 函數語言程式設計範例旨在避免在應用程式中使用狀態。 實際上,應用程式狀態管理是軟體開發中複雜性的主要來源之一。 但是,由於你不能沒有它,至少你應該嘗試限制它的使用並使其更易於管理。
React
的開發指南促進了無狀態元件的建立,即無 state
元件。 這種元件的輸出僅僅取決於傳入的 props
。 無狀態元件看起來很像純函式,實際上也是如此。
但是,如您所知,在不使用 state
的情況下無法編寫複雜的應用程式。 訣竅是在應用程式的幾個點上隔離 state
,如果在一個點上更好。 此策略會要求開發人員在根元件中使用狀態提升。 換句話說,應該在上層節點中保留狀態,而其後代應該是無狀態元件。 這樣,我們可以更好地控制狀態,因為它只由單個根元件管理。
結論
對於剛開始使用 React
的開發者來說,React
並不是像看起來那樣,它更加適合函式式編碼的原則,而不是物件導向的原則。通常,這允許開發者編寫更加正式可驗證的程式碼,例如使用自動化測試來測試應用程式。 我建議充分利用 React
的函式特性來編寫更易於維護的程式碼。
Andrea Chiarelli 是 Beginning React 的作者。本文已經聯絡原文作者,並授權翻譯,轉載請保留原文連結