这篇文章源自 。
现在的热点是 ,所以 Dan 决定写一篇关于 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 }; // ...}复制代码
2015 年初的时候,React 0.13 版本就已经支持该语法。在此之前,我们需要不断地写 constructor
,然后再调用 super(props)
。
现在我们先回顾一下之前的写法:
class Checkbox extends React.Component { constructor(props) { super(props); this.state = { isOn: true }; } // ...}复制代码
我们为什么要调用 super
?能不能不调用?如果调用的时候不传入props
呢?还可以传入其他参数么?带着这些问题往下看。
在 JavaScript 中,super
引用的是父类构造函数。(在 React 中,引用的自然就是 React.Component
)
需要注意的是,在调用父类构造函数之前,无法用 this
。其实这不是 React 的限制,而是 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!'); }}复制代码
如果 JavaScript 允许在调用 super
之前使用 this
,一个月之后,我们需要修改 greetColleagues
方法,方法中使用了 name 属性:
greetColleagues() { alert('Good morning folks!'); alert('My name is ' + this.name + ', nice to meet you!');}复制代码
不过我们可能已经忘了 this.greetColleagues();
是在调用 super
之前调用的;因此,this.name
还没有赋值。这样的代码,很难定位 bug。
为了避免这样的错误,JavaScript 强制开发者在构造函数中先调用 super
,才能使用this
。这一限制,也被应用到了 React 组件:
constructor(props) { super(props); // ✅ 现在可以用 `this` 啦 this.state = { isOn: true };}复制代码
问题又来了:为什么要传入 props
呢?
你可能以为必须给 super
传入 props
,否则 React.Component
就没法初始化 this.props
:
// Inside Reactclass Component { constructor(props) { this.props = props; // ... }}复制代码
en...离真相不远 —— 事实上,React 也的确。
不过,如果你不小心漏传了 props
,直接调用了 super()
,你仍然可以在 render
和其他方法中访问 this.props
(不信的话可以试试嘛)。
为啥这样也行?因为React 会在构造函数被调用之后,会把 props
赋值给刚刚创建的实例对象:
// Inside Reactconst instance = new YourComponent(props);instance.props = props;复制代码
props
不传也能用,是有原因的。
React 添加对 class 支持的时候,不仅仅要支持 ES6 的 class,还需要考虑其他的 class 实现, CoffeeScript, ES6, Fable, Scala.js, TypeScript 中 class 的使用方式并。所以,即使有了 ES6 class,在调用 super()
这个问题上,React 没做太多限制。
但这意味着你在使用 React 时,可以用 super()
代替 super(props)
了么?
别这么干,因为会带来其他问题。 虽然 React 会在构造函数运行之后,为 this.props
赋值,但在 super()
调用之后与构造函数结束之前, this.props
仍然是没法用的。
// Inside Reactclass Component { constructor(props) { this.props = props; // ... }}// Inside your codeclass Button extends React.Component { constructor(props) { super(); // ? 忘了传入 props console.log(props); // ✅ {} console.log(this.props); // ? undefined } // ...}复制代码
要是构造函数中调用了某个访问 props
的方法,那这个 bug 就更难定位了。因此我强烈建议始终使用super(props)
,即使这不是必须的:
class Button extends React.Component { constructor(props) { super(props); // ✅ We passed props console.log(props); // ✅ {} console.log(this.props); // ✅ {} } // ...}复制代码
上面的代码确保 this.props
始终是有值的。
还有一个问题可能困扰 React 开发者很久了。你应该已经注意到,当你在 class 中使用 Context API 时(无论是之前的 contextTypes
还是现在的 contextType
API),context
都是作为构造函数的第二个参数。
我们为什么不用写 super(props, context)
?我们当然可以这么写,不过 context API 用的相对较少,所以引发的 bug 也比较少。
感谢 ,这样的 bug 几近绝迹。只要没有显式声明构造函数,所有参数都会被自动传递。所以,在state = {}
表达式中,你可以访问this.props
以及 this.context
。