谈谈JavaScript中super(props)的重要性
我听说Hooks最近很火。讽刺的是,我想用一些关于class组件的有趣故事来开始这篇文章。你觉得如何?
本文中这些坑对于你正常使用React并不是很重要。但是假如你想更深入的了解它的运作方式,就会发现实际上它们很有趣。
开始第一个。
首先在我的职业生涯中写过的super(props)自己都记不清:
classCheckboxextendsReact.Component{ constructor(props){ super(props); this.state={isOn:true}; } //... }
当然,在类字段提案(classfieldsproposal)中建议让我们跳过这个开头:
classCheckboxextendsReact.Component{ state={isOn:true}; //... }
在2015年React0.13增加对普通类的支持时,曾经打算用这样的语法。定义constructor和调用super(props)始终是一个临时的解决方案,可能要等到类字段能够提供在工程学上不那么反人类的替代方案。
不过还是让我们回到前面这个例子,这次只用ES2015的特性:
classCheckboxextendsReact.Component{ constructor(props){ super(props); this.state={isOn:true}; } //... }
为什么我们要调用super?可以调用它吗?如果必须要调用,不传递prop参数会发生什么?还有其他参数吗?接下来我们试一试:
在JavaScript中,super指的是父类的构造函数。(在我们的示例中,它指向React.Component的实现。)
重要的是,在调用父类构造函数之前,你不能在构造函数中使用this。JavaScript是不会让你这样做的:
classCheckboxextendsReact.Component{ constructor(props){ //这里还不能用`this` super(props); //现在可以用了 this.state={isOn:true}; } //... }
为什么JavaScript在使用this之前要先强制执行父构造函数,有一个很好的理由能够解释。先看下面这个类的结构:
classPerson{ constructor(name){ this.name=name; } } classPolitePersonextendsPerson{ constructor(name){ this.greetColleagues();//这行代码是无效的,后面告诉你为什么 super(name); } greetColleagues(){ alert('Goodmorningfolks!'); } }
如果允许在调用super之前使用this的话。一段时间后,我们可能会修改greetColleagues,并在提示消息中添加Person的name:
greetColleagues(){ alert('Goodmorningfolks!'); alert('Mynameis'+this.name+',nicetomeetyou!'); }
但是我们忘记了super()在设置this.name之前先调用了this.greetColleagues()。所以此时this.name还没有定义!如你所见,像这样的代码很难想到问题出在哪里。
为了避免这类陷阱,JavaScript强制要求:如果想在构造函数中使用this,你必须首先调用super。先让父类做完自己的事!这种限制同样也适用于被定义为类的React组件:
constructor(props){ super(props); //在这里可以用`this` this.state={isOn:true}; }
这里又给我们留下了另一个问题:为什么要传props参数?
你可能认为将props传给super是必要的,这可以使React.Component的构造函数可以初始化this.props:
//InsideReact classComponent{ constructor(props){ this.props=props; //... } }
这与正确答案很接近了——实际上它就是这么做的。
但是不知道为什么,即便是你调用super时没有传递props参数,仍然可以在render和其他方法中访问this.props。(不信你可以亲自去试试!)
这是究竟是为什么呢?事实证明,在调用构造函数后,React也会在实例上分配props:
//InsideReact constinstance=newYourComponent(props); instance.props=props;
因此,即使你忘记将props传给super(),React仍然会在之后设置它们。这是有原因的。
当React添加对类的支持时,它不仅仅增加了对ES6类的支持。它的目标是尽可能广泛的支持类抽象。目前还不清楚ClojureScript、CoffeeScript、ES6、Fable、Scala.js、TypeScript或其他解决方案是如何相对成功地定义组件的。所以React故意不关心是否需要调用super()——即使是ES6类。
那么这是不是就意味着你可以写super()而不是super(props)呢?
可能不行,因为它仍然是令人困惑的。当然,React稍后会在你的构造函数运行后分配this.props,但是在调用super()之后和构造函数结束前这段区间内this.props仍然是未定义的:
//InsideReact classComponent{ constructor(props){ this.props=props; //... } } //Insideyourcode classButtonextendsReact.Component{ constructor(props){ super();//我们忘记了传递props参数 console.log(props);//{} console.log(this.props);//undefined } //... }
如果这种情况发生在从构造函数调用的某个方法中,可能会给调试工作带来很大的麻烦。这就是为什么我建议总是调用super(props),即使在没有必要的情况之下:
classButtonextendsReact.Component{ constructor(props){ super(props);//传递了props参数 console.log(props);//{} console.log(this.props);//{} } //... }
这样就确保了能够在构造函数退出之前设置好this.props。
最后一点是长期以来React用户总是感到好奇的。
你可能已经注意到,当你在类中使用ContextAPI时(无论是旧版的contextTypes或在React16.6中新添加的contextTypeAPI),context会作为第二个参数传递给构造函数。
那么为什么我们不写成super(props,context)呢?我们可以这样做,但是使用context的频率较低,所以这个坑并没有那么多影响。
根据类字段提案的说明,这些坑大部分都会消失。如果没有显式构造函数,则会自动传递所有参数。这允许在像state={}这样的表达式中包含对this.props或this.context的引用(如果有必要的话)。
而有了Hooks之后,我们甚至不再有super或this。不过这是另外一个的话题了。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。如果你想了解更多相关内容请查看下面相关链接