[譯]我們為什麼要寫 super(props)?

WashingtonHua發表於2018-12-03
  • 原文地址:Why Do We Write super(props) ?
  • 原文作者:Dan Abramov
  • 譯者:Washington Hua
  • 我聽說 Hooks 最近很火。諷刺的是,我想以一些關於 class 元件的有趣故事來開始這個部落格。怎樣!(皮一下很開心)

    這些小坑並不會影響你高效的使用 React,但如果你願意深入瞭解下背後的工作原理,你會發現它們非常有意思。

    這是第一個。

    我這輩子寫過的 super(props) 比我想象的要多得多

    class Checkbox extends React.Component {
      constructor(props) {
        super(props);
        this.state = { isOn: true };
      }
      // ...
    }複製程式碼

    當然,class fields proposal 允許我們跳過這個儀式。

    class Checkbox extends React.Component {
      state = { isOn: true };
      // ...
    }複製程式碼

    這樣的語法是在 2015 年 React 0.13 增加對純 Class 的支援的時候加入 計劃 的. 定義 constructor 和呼叫 super(props) 一直都只是 class fiels 出現之前的臨時解決方案。

    然而,讓我們只用 ES2015 的特性來回顧一下這個例子。

    class Checkbox extends React.Component {
      constructor(props) {
        super(props);
        this.state = { isOn: true };
      }
      // ...
    }複製程式碼

    我們為什麼要呼叫super?能不能不呼叫它?如果非要呼叫,如果不傳 props 會怎樣?還有其它引數嗎?讓我們來看一下。

    在 JavaScript 中,super 指代父類的建構函式。(在我們的案例中,它指向 React.Component 這個實現)

    重點在於,在你呼叫父類建構函式之前,你無法在建構函式中使用 this。JavaScript 不會允許你這麼做。

    class Checkbox extends React.Component {
      constructor(props) {
        // ? 這時候還不能使用 `this`
        super(props);
        // ✅ 現在開始可以了
        this.state = { isOn: true };
      }
      // ...
    }複製程式碼

    JavaScript 強制你在使用 this 前執行父類建構函式有一個很好的理由。考慮這樣一個類結構:

    class Person {
      constructor(name) {
        this.name = name;
      }
    }
    
    class PolitePerson extends Person {
      constructor(name) {
        this.greetColleagues(); // ? 這是不允許的,下面會解釋原因
        super(name);
      }
    
      greetColleagues() {
        alert('Good morning folks!');
      }
    }複製程式碼

    想象一下如果在呼叫 super 前使用 this 是被允許的。一個月之後。我們或許會改變 greetColleagues 把 person 的 name 加到訊息中。

    greetColleagues() {
      alert('Good morning folks!');
      alert('My name is ' + this.name + ', nice to meet you!');
    }複製程式碼

    但我們忘了 this.greetColleagues() 是在 super() 有機會設定 this.name 之前被呼叫的。this.name 甚至還沒被定義!如你所見,像這樣的程式碼理解起來會很困難。

    為了避免這樣的陷阱,JavaScript 強制規定,如果你想在建構函式中只用this,就必須先呼叫 super。讓父類做它該做的事!這一限制也適用於定義成類的 React 元件。

    constructor(props) {
      super(props);
      // ✅ 現在可以使用 `this` 了
      this.state = { isOn: true };
    }複製程式碼

    這給我們留下了另一個問題:為什麼要傳 props

    你或許覺得把 props 傳進 super 是必要的,這使得基類 React.Component 可以初始化 this.props

    // React 內部
    class Component {
      constructor(props) {
        this.props = props;
        // ...
      }
    }複製程式碼

    很接近了——事實上,它就是這麼做的

    然而,即便在呼叫 super() 時沒有傳入 props 引數,你依然能夠在 render 和其它方法中訪問 this.props。(你要是不相信我,可以自己試一試)

    這是什麼原理?其實 React 在呼叫你的建構函式之後,馬上又給例項設定了一遍 props

    // React 內部
    const instance = new YourComponent(props);
    instance.props = props;複製程式碼

    因此,即便你忘了把 props 傳入 super(),React 依然會在事後設定它們。這是有理由的。

    當 React 新增對 Class 的支援時,它並不是只新增了對 ES6 的支援,而是希望能夠支援儘可能廣泛的 class 抽象。由於不是很確定 ClojureScript、CoffeeScript、ES6、Fable、Scala.js、TypeScript 或其他解決方案誰更適合用來定義元件,React 對於是否有必要呼叫 super() 刻意不表態。

    那麼這是否意味著你可以只寫 super() 而不用 super(props)

    或許並非如此,因為這依然讓人困擾。誠然,React 會在你的建構函式執行之後設定 this.props。但在 super 呼叫一直到建構函式結束之前,this.props 依然是未定義的。

    // React 內部
    class Component {
      constructor(props) {
        this.props = props;
        // ...
      }
    }
    
    // 你的程式碼
    class Button extends React.Component {
      constructor(props) {
        super(); // ? 我們忘了傳入 props
        console.log(props);      // ✅ {}
        console.log(this.props); // ? undefined
      }
      // ...
    }複製程式碼

    如果這發生在某些從建構函式中呼叫的函式,除錯起來會更加麻煩。這也是為什麼我推薦總是使用 super(props) 的寫法,即便這是非必要的

    class Button extends React.Component {
      constructor(props) {
        super(props); // ✅ 我們傳了 props
        console.log(props);      // ✅ {}
        console.log(this.props); // ✅ {}
      }
      // ...
    }複製程式碼

    這樣的寫法確保了 this.props即便在建構函式返回之前就被設定好了。

    最後還有一點是 React 的長期使用者或許會好奇的。

    你或許已經注意到,當你在 Class 中使用 Context API 時(無論是舊版的語法還是 React 16.6 中新增的現代化語法),context 是被作為建構函式的第二個引數傳入的。

    那麼我們為什麼不寫 super(props, context) 呢?當然我們可以這麼做,但 context 的使用頻率沒那麼高,所以這個陷阱影響還沒那麼大。

    伴隨著 class fields proposal 的釋出,這個問題也就不復存在了。即便不顯式呼叫建構函式,所有引數也會自動傳入。這就允許像 state = {} 這樣的表示式在必要時可以直接引用 this.props.this.context

    在 Hooks 中,我們甚至都沒有 superthis。這個話題我們擇日再說。


    相關文章