译注:
原文地址正文
我听说是新的热点。好笑的是,我想通过描述一些关于class组件的事实来作为第一篇博客。这个想法怎么样!
这些陷阱对于有效的使用React并不重要。但是如果你喜欢深入挖掘运行机制,就会发现这些东西的又去之处。 下面介绍第一个。 我写过很多次super(props)
但很多情况下,我并不了解为什么要写它。 class Checkbox extends React.Component { constructor(props) { super(props); this.state = { isOn: true }; } // ...}
当然,让我们可以跳过这步操作。
class Checkbox extends React.Component { state = { isOn: true }; // ...}
之前计划的一种支持plain class的(注:即类变量),已经在2015年,React
0.13版中加入。在class fields完整确定之前,定义 constructor
和调用 super(props)
被当做一种临时方案。
class Checkbox extends React.Component { constructor(props) { super(props); this.state = { isOn: true }; } // ...}
为什么要调用 super
? 能不能不用它?如果不得不用它,在调用它时,发生了什么?还有其他的参数吗?
在JavaScript里,super
指向父类构造器。(在我们的例子里, 他指向React.Component
实现类)。
this
关键字。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 folks!'); }}
想象一下如果在super
之前使用this
是允许的。一个月之后,我们可能修改greetColleagues
来动态加载信息中的姓名。
greetColleagues() { alert('Good morning folks!'); alert('My name is ' + this.name + ', nice to meet you!');}
但是我们忘记了this.greetColleagues()
是在super()
之前调用,它已经和this.name
建立了联系。而this.name
甚至还没有定义。你能发现,像这样的代码,真的很难理解。
constructor(props) { super(props); // ✅ Okay to use `this` now this.state = { isOn: true };}
这样就留给我们另一个问题: 为什么要传递props
?
你也许觉得对于React.Component
构造器初始化this.props
而言,通过super
传递props
非常重要。
// Inside Reactclass Component { constructor(props) { this.props = props; // ... }}
这和真相相去甚远。事实上,这才是。
但不知道为啥,即使你不传入props,直接调用super()
,还是可以在render
和其他方法里访问到this.props
(如果你不相信我,自己试一下)。 这是怎么个情况?它实际上证实了React也会在调用constructor之后,立刻合并props
。 // Inside Reactconst 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 Reactclass Component { constructor(props) { this.props = props; // ... }}// Inside your codeclass 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
了。但那是另一个主题了。