[譯] 建構函式已死,建構函式萬歲!

unicar發表於2018-01-25

建構函式已死,建構函式萬歲!

向 React 元件里老掉牙的類建構函式(class constructor)說再見

[譯] 建構函式已死,建構函式萬歲!

Photo by Samuel Zeller on Unsplash

儘管無狀態函式元件(SFCs)是一件趁手的神兵利器,但 ES6 類元件仍舊是建立 React 元件及其狀態和生命週期鉤子函式的預設方式。

假設一個 ES6 類元件如下例所示(只展示簡化過的部分程式碼)。

class Foo extends Component {
  constructor(props) {
    super(props); 
    this.state = { loading: true };
  }
複製程式碼
  async componentDidMount() {
    const data = await loadStuff();
    this.setState({ loading: false, data });
  }
複製程式碼
  render() {
    const { loading, data } = this.state;
    return (
      {loading ? <Loading /> : <View {...data} />}
    );
  }
}
複製程式碼

constructor 中初始化 state,並於 componentDidMount 中非同步載入資料,然後根據 loading 的狀態來渲染 View 這個元件。對我而言這是相當標準的模式,如果你熟悉我之前的程式碼風格的話。

類屬性

我們都知道 constructor 正是我們初始化例項屬性的地方,就像本例中這個 state 一樣。如果你正胸有成竹地對自己說,『正是如此!』,那麼你可說對了……但對於即將問世的 ES.next 類屬性提案class properties proposal 而言卻並非如此,目前這份提案正處於第三階段。

按照新的提案來說,我們可以用如下方式直接定義類屬性。

class Foo extends Component {
  state = { loading: true };
  ...
}
複製程式碼

Babel 將會在後臺轉譯你的程式碼並新增上一個 constructor。下圖是 Babel 將你的程式碼片段轉譯過來的結果。

[譯] 建構函式已死,建構函式萬歲!

請注意這裡 Babel 實際上是傳遞了所有引數到 super - 不僅僅是 props。它也會將 super 的返回值傳遞迴呼叫者。兩者雖然感覺有些小題大做,但確實需要這樣。

此處仍存在建構函式,你只是看不見而已。

繫結方法

使用 constructor 的另一重原因是將函式繫結到 this,如下所示。

class Foo extends Component {
  constructor(props) {
    super(props); 
    this.myHandler = this.myHandler.bind(this);
  }
複製程式碼
  myHandler() {
    // some code here that references this
  }
  ...
}
複製程式碼

但有些人用直接將函式表示式指定給一個類屬性的方法完全避免了這個問題,不過這又是另一碼事了。想了解更多可以參考我寫的其他基於 ES6 類的 React 文章。 Demystifying Memory Usage using ES6 React Classes.

Demystifying Memory Usage using ES6 React Classes

那讓我們假設一下你隸屬 bind 陣營(即便不是也煩請耐心看完)。我們還是得需要在 constructor 進行繫結對吧?那倒不一定了。我們可以在這裡使用和上述處理類屬性一樣的方法。

class Foo extends Component {
  myHandler = this.myHandler.bind(this);
  myHandler() {
    // some code here that references this
  }
  ...
}
複製程式碼

用 props 來初始化狀態

那如果你需要從 props 中派生出初始 state,比方說初始化一個預設值?那這樣總該需要使用到 constructor 了吧?

class Foo extends Component {
  constructor(props) {
    super(props); 
    this.state = {
      color: this.props.initialColor
    };
  }
  render() {
    const { color } = this.state;
    return (
      <div>
       {color}
      </div>
    );
  }
}
複製程式碼

並不是哦!類屬性再次救人於水火!我們可以同時取到 thisprops

class Foo extends Component {
  state = {
    color: this.props.initialColor
  };
  ...
}
複製程式碼

獲取資料

那也許我們需要 constructor 獲取資料?基本上不需要。就像我們在第一個程式碼示例看到的那樣,任何資料的載入都應在 componentDidMount 裡完成。但為何獨獨在 componentDidMount呢?因為這樣可以確保在伺服器端執行元件時不會執行獲取資料 - 伺服器端渲染(SSR)同理 — 因為 componentDidMount 不會在伺服器端執行。

結論

綜上可以看出,我們不再需要一個 constructor(或者其他任何例項屬性)來設定初始 state。我們也不需要建構函式來把函式繫結到 this,以及從 props 設定初始的 state。同時我們也完全不需要在 constructor 裡面獲取資料。

那為什麼我們還需要在 React 元件中使用建構函式呢?

怎麼說呢……你還真的不需要

不過,要是你在某些模稜兩可的使用例項裡,遇到需要同時從客戶端和伺服器端在一個元件裡初始化什麼東西的情況,建構函式仍然是個好的出路。你還有 componentWillMount 這個鉤子函式可以用。 從內部機制來看,React 在客戶端和伺服器端都新建好了這個類(即呼叫建構函式)以後,就會立即呼叫這個鉤子函式。

所以對 React 元件來說,我堅信這一點:建構函式已死,建構函式萬歲!


I also write for the American Express Engineering Blog. Check out my other works and the works of my talented co-workers at AmericanExpress.io. You can also follow me on Twitter.


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章