这篇文章源自 Dan 的博客.
现在的热点是 https://reactjs.org/docs/hooks-intro.html , 所以 Dan 决定写一篇关于 class 组件的文章 .
文章中描述的问题, 应该不会影响你写代码; 不过如果你想深入研究 React 是怎么工作的, 这篇文章可能会对你有帮助.
第一个问题:
我自己都不知道我写了多少遍 super(props):
- class Checkbox extends React.Component {
- constructor(props) {
- super(props);
- this.state = { isOn: true };
- }
- // ...
- }
当然, class 属性提案 https://github.com/tc39/proposal-class-fields 可以简化代码:
- 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 React
- class Component {
- constructor(props) {
- this.props = props;
- // ...
- }
- }
en... 离真相不远 -- 事实上, React 也的确这么干了.
不过, 如果你不小心漏传了 props, 直接调用了 super(), 你仍然可以在 render 和其他方法中访问 this.props(不信的话可以试试嘛).
为啥这样也行? 因为 React 会在构造函数被调用之后, 会把 props 赋值给刚刚创建的实例对象:
- // Inside React
- const 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 React
- class Component {
- constructor(props) {
- this.props = props;
- // ...
- }
- }
- // Inside your code
- class 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 也比较少.
感谢 class 属性提案 , 这样的 bug 几近绝迹. 只要没有显式声明构造函数, 所有参数都会被自动传递. 所以, 在 state = {} 表达式中, 你可以访问 this.props 以及 this.context.
来源: https://juejin.im/post/5c034e0f6fb9a04a016410c6