【譯】為何我們要寫super(props)?

easyLi發表於2018-12-12

我聽說現在Hooks是新的熱點。諷刺地是,我想描述類的相關事實作為這片部落格的開始。那是怎麼樣的呢?

這些坑對於有效地使用React並不重要。但如果你想更深入地瞭解事物的工作原理,你可能會發現它們很有趣。

這是第一個。


我的生命中我寫的 super(props)

在我的生命中我寫的super(props)比我知道的多:

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

當然,類相關的提議讓我們跳過了這個儀式:

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

2015年React 0.13增加了對純類的支援時,就計劃了這樣的語法。定義constructor函式和呼叫super(props)一直是一種臨時解決方案,直到類欄位提供了一種符合人體工程學的替代方案。

但是讓我們回到這個例子,只使用ES2015特性:

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

為何我們要呼叫super?我們可以不呼叫它嗎?如果我們不得不呼叫它,不傳props引數會發生什麼呢?還存在其它的引數嗎? 讓我們一探究竟。

為何我們要呼叫 super?呼叫不當會發生什麼呢?


在JavaScript中,super引用的是父類的constructor(在我們的例子中,它指向React.Component實現)。

重要的是,你不能使用this直到你呼叫父級的constructor後。JavaScript不會讓你那樣做:

class Checkbox extends React.Component {
  constructor(props) {
    // ? Can’t use `this` yet
    super(props);
    // ✅ Now it’s okay though
    this.state = { isOn: true };
  }
  // ...
}
複製程式碼

有一個很好的理由可以解釋為什麼JavaScript會強制父建構函式在你訪問this之前執行。考慮一個類層次結構:

class Person {
    constructor(name) {
        this.name = name;
    }
}

class PolitePerson extends Person {
    constructor(name) {
        this.greetColleagues(); // ? This is disallowed, read below why
        super(name);
    }
    greetColleagues() {
        alert('Good morning forks!');
    }
}
複製程式碼

想象一下thissuper之前被呼叫是被允許的。一個月後,我們可能改變greetColleagues中包含在person中的資訊:

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

但是我們忘記了super()在有機會設定this.name之前呼叫了this.greetemployees(),因此this.name都還沒定義。正如你所看到的一樣,很難思考像這樣的程式碼。

為了避免這樣的坑,JavaScript會迫使你在使用this之前先呼叫super. 讓父級做它該做的事情!這個限制也適用於定義為類的React元件:

construtor(props) {
    super(props);
    // ✅ Okay to use `this` now
    this.state = { isOn: true };
}
複製程式碼

這裡還存在另外一個問題:為什麼要傳遞props?


你也許認為傳遞propssuper是必要的,這樣React.Component建構函式就能初始化this.props了:

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

這與事實相去不遠——確實,事實就是如此

但是,即使你呼叫super沒帶props引數,你依然可以在render或者其它方法中獲取到props。(如果你不信我,你可以自己試試!)

那到底是怎樣工作的呢?事實表明 React也會在呼叫建構函式後立即在例項上初始化props:

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

因此即使你忘記了給super傳遞propsReact也會立即設定它們。這是有原因的。

當React新增了對類的支援時,它不僅僅只支援ES6中的類。目標是支援儘可能廣泛的類抽象。目前還不清楚使用ClojureScript,CoffeeScript,ES6,Fable,Scala.js,TypeScript,或者其它方式定義元件會有多成功。因此,React故意不明確是否需要呼叫super()——即使ES6的類需要。

所以這就意味著你可以使用super()代替super(props)了嗎?

可能不是因為它仍然令人困惑。 當然,React會在你執行了constructor之後為this.props賦值。但是在super呼叫和建構函式結束之間,this.props仍然是未定義的:

// Inside React
class Component {
    construtor(props) {
        this.props = props;
        // ...
    }
}

// Inside your code
class Button extends React.Component {
  constructor(props) {
    super(); // ? We forgot to pass props
    console.log(props);      // ✅ {}
    console.log(this.props); // ? undefined 
  }
  // ...
}
複製程式碼

如果在從建構函式呼叫的某個方法中發生這種情況,除錯可能會更加困難。這就是為什麼我總是建議大家傳super(props),儘管它不是必需的:

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

保證在constructor存在之前this.props就被設定了。


還有最後一點React使用者可能會感到好奇的。

你可能注意到在類中使用Context API時(包括舊的contextTypes和在React16.6中新增的現代contextType API),context作為第二個引數被傳給constructor

為什麼我們不用super(props, context)代替呢?我們能夠,只是上下文的使用頻率較低,所以這個坑不會經常出現。

隨著類欄位的提議,這個坑基本上消失了。 如果沒有顯式建構函式,所有引數都將被自動傳遞下去。這就是允許像state ={}這樣的表示式包含對this.props或者this.context的引用的原因。

使用Hooks,我們甚至不會使用到super或者this。但是那是下次的話題。

原文連結:overreacted.io/why-do-we-w… by Dan Abramov

相關文章