【譯】函式式的React

joking_zhang發表於2019-05-07
原文: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 的作者。

本文已經聯絡原文作者,並授權翻譯,轉載請保留原文連結

相關文章