译注:
原文地址 https://overreacted.io/why-do-we-write-super-props/javascript
我据说Hooks是新的热点。可笑的是,我想经过描述一些关于class组件的事实来做为第一篇博客。这个想法怎么样!
这些陷阱对于有效的使用React并不重要。可是若是你喜欢深刻挖掘运行机制,就会发现这些东西的又去之处。
下面介绍第一个。
我写过不少次 super(props)
但不少状况下,我并不了解为何要写它。html
class Checkbox extends React.Component { constructor(props) { super(props); this.state = { isOn: true }; } // ... }
固然,class fields 提案让咱们能够跳过这步操做。java
class Checkbox extends React.Component { state = { isOn: true }; // ... }
以前计划的一种支持plain class的语法(注:即类变量),已经在2015年,React
0.13版中加入。在class fields完整肯定以前,定义 constructor
和调用 super(props)
被当作一种临时方案。
可是,让咱们回到只使用ES2015的例子里:react
class Checkbox extends React.Component { constructor(props) { super(props); this.state = { isOn: true }; } // ... }
为何要调用 super
? 能不能不用它?若是不得不用它,在调用它时,发生了什么?还有其余的参数吗?git
在JavaScript里,super
指向父类构造器。(在咱们的例子里, 他指向React.Component
实现类)。
重点在于,若是调用了父类构造器,在调用super以前,没法使用this
关键字。JavaScript不容许这么干。github
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
以前执行父类构造器?这里有一个好的解释。考虑一个类的层级结构:ide
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 folks!'); } }
想象一下若是在super
以前使用this
是容许的。一个月以后,咱们可能修改greetColleagues
来动态加载信息中的姓名。this
greetColleagues() { alert('Good morning folks!'); alert('My name is ' + this.name + ', nice to meet you!'); }
可是咱们忘记了this.greetColleagues()
是在super()
以前调用,它已经和this.name
创建了联系。而this.name
甚至尚未定义。你能发现,像这样的代码,真的很难理解。
为了不这样的陷阱, 若是在构造器中调用this,JavaScript强制要求super要在this以前书写,让父类作它们应该作的事。这条限制也被应用到了React组件类定义中:debug
constructor(props) { super(props); // ✅ Okay to use `this` now this.state = { isOn: true }; }
这样就留给咱们另外一个问题: 为何要传递props
?code
你也许以为对于React.Component
构造器初始化this.props
而言,经过super
传递props
很是重要。
// Inside React class Component { constructor(props) { this.props = props; // ... } }
这和真相相去甚远。事实上,这才是它作了什么。
但不知道为啥,即便你不传入props,直接调用super()
,仍是能够在render
和其余方法里访问到this.props
(若是你不相信我,本身试一下)。
这是怎么个状况?它实际上证明了React也会在调用constructor以后,马上合并props
。
// Inside React const instance = new YourComponent(props); instance.props = props;
因此即便你忘记把props
传递给super()
,React也会及时设置上去的。下面是缘由之一:
当React支持类方法声明组件时,并非单单支持了ES6类语法。它的目标是支持全部抽象类范围内的声明方法。JavaScript有不少变种,如ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript,或者是其余方式,并非很比如较到底哪一种方式去定义一个组件更合适。因此React故意执拗得要求super()
,虽然ES6 class是这样。
这下明白为何能只写super()
而不用写super(props)
了吗?
也许还不明白,不要紧,这个东西太使人困惑了。固然,React将会在构造器执行完毕后去合并this.props
。可是在super和构造器结尾之间,this.props
还是undefined。
// Inside React class Component { constructor(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 } // ... }
若是一些方法在构造器中调用,这样会给debug形成很大的挑战。这也是为何我推荐传递super(props)
,虽然它不是必须的。
class Button extends React.Component { constructor(props) { super(props); // ✅ We passed props console.log(props); // ✅ {} console.log(this.props); // ✅ {} } // ... }
这样确保了this.props
在构造器存在前就已经被设置。
还有一点,React长期使用者可能会好奇。
你也许注意到了Context API传递了第二个参数给构造器。(不管是古老的contextTypes仍是如今16.6新加的ContextAPI)。
为何要写super(props, context)
来代替super(props)
?固然也行,可是context不多使用,因此这个陷阱不常出现。
在class fields提案经过以后,这些陷阱都没得差很少了。没有一个明确的constructor,全部的参数都会自动传递。这也是为何一个表达式相似state={}
能够包含this.props
和this.context
引用。
经过使用Hooks,就不须要super
和this
了。但那是另外一个主题了。