我听说 Hooks 最近很火热。具有讽刺意味的是,我想通过描述关于类组件的有趣事实来开始这个博客。怎么样?

    这些 gotchas 对于使用 React 并不重要。但如果你喜欢深入研究事物是如何运作的,你可能会发现它们的有趣之处。

    这是第一个。


    当然你可以是用 class fields proposal

    1. class Checkbox extends React.Component {
    2. state = { isOn: true };
    3. // ...
    4. }

    当React 0.13在2015年增加了对普通类的支持时,就计划使用这样的语法。定义构造函数和调用super(props)始终是临时解决方案,直到类字段提供了替代方案。

    但是让我们回到这个例子,只使用 ES2015 特性:

    1. class Checkbox extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = { isOn: true };
    5. }
    6. // ...
    7. }

    为什么要调用 super,可以不调用它吗?如果我们必须调用它,我们不传 props 会发生什么?还有其他参数吗?接着往下看:


    在JavaScript中,super 指向父类构造函数。(在我们的例子中,它指向了React.Component )。

    重要的是,在调用父构造函数之后,才能在构造函数中使用 this。

    1. class Checkbox extends React.Component {
    2. constructor(props) {
    3. // ? Can’t use `this` yet
    4. super(props);
    5. // ✅ Now it’s okay though
    6. this.state = { isOn: true };
    7. }
    8. // ...
    9. }
    1. class Person {
    2. constructor(name) {
    3. this.name = name;
    4. }
    5. }
    6. class PolitePerson extends Person {
    7. constructor(name) {
    8. this.greetColleagues(); // ? 这是不允许的,为什么请往下看
    9. super(name);
    10. }
    11. greetColleagues() {
    12. alert('Good morning folks!');
    13. }
    14. }

    想象一下如果 super 之前可以使用 this。一个月后,我们可能会更改 greetColleagues,在消息中包括此人的姓名:

    这样的话,调用就出错了,调用 greetColleagues 的时候 this.name 从未定义。

    所以js规定使用this之前必须调用 super,在react当中:

    1. constructor(props) {
    2. super(props);
    3. // ✅ Okay to use `this` now
    4. this.state = { isOn: true };
    5. }

    那么,只剩下为什么传 props


    您可能认为将 props 传递到super是必要的,以便 React.Component 构造函数可以初始化 this.props:

    1. class Component {
    2. constructor(props) {
    3. // ...
    4. }
    5. }

    这离真相不远——事实上,。

    但即使调用 super() 时没有 props 参数,您仍然可以在 render 和其他方法中访问 this.props。(如果你不相信我,你自己试试!)

    怎么做到的?答案如下:

    1. // Inside React
    2. const instance = new YourComponent(props);
    3. instance.props = props;

    所以,即使你忘记把道具传给super(),React 仍然会在事后正确设置。这是有原因的。

    这是否意味着你可以只写 super() 而不写 super(props)?

    1. // Inside React
    2. class Component {
    3. constructor(props) {
    4. this.props = props;
    5. // ...
    6. }
    7. }
    8. // Inside your code
    9. class Button extends React.Component {
    10. constructor(props) {
    11. super(); // ? We forgot to pass props
    12. console.log(props); // ✅ {}
    13. console.log(this.props); // ? undefined
    14. }
    15. // ...
    16. }

    如果这种情况发生在从构造函数调用的一些方法中,那么调试带来疑惑。这就是为什么我建议总是 super(props),即使它不是严格必要的:

    您可能已经注意到,当在类中使用Context API时(React 16.6中添加的 Context API),Context 作为第二个参数传递给构造函数。

    那么我们为什么不写 super(props, context) 替代, 但是 context 很少被使用,所以这个困扰就没有那么多了。

    随着 class fields proposal 的应用,没有 constructor, 所有参数会自动传递下去.

    使用 Hooks, 我们甚至不需要 super 和 this,这是另一个话题。

    在 Omi 框架的源码中, 常用的两个类有 WeElementModelView

    1. class WeElement extends HTMLElement {
    2. constructor() {
    3. super()
    4. ...
    5. }
    6. }
    1. class ModelView extends WeElement {
    2. static observe = true
    3. beforeInstall() {
    4. this.data = this.vm.data
    5. }
    6. observed() {
    7. this.vm.data = this.data
    8. }
    9. }

    类的构造函数 constructor 总是先调用 super() 来建立正确的原型链继承关系。HTMLElement 继承自父接口 Element 和 GlobalEventHandlers 的属性,所以一些节点增删改查的操作以及时间监听和绑定都是通过 super 建立起来。

    对 Omi 框架的使用者,完全不需要 constructor、super等。props 和 data 以及 store 都会在内部注入进去。而且是在生命周期勾子 install 之前就注入了,所以你可以在 install 的时候使用 this.props this.data 甚至 this.store(如果从根节点注入了 store 的话)。

    1. import { render, WeElement, define } from 'omi'
    2. static observe = true
    3. data = {
    4. count: 1
    5. }
    6. sub = () => {
    7. this.data.count--
    8. }
    9. add = () => {
    10. this.data.count++
    11. }
    12. render() {
    13. return (
    14. <div>
    15. <button onClick={this.sub}>-</button>
    16. <span>{this.data.count}</span>
    17. <button onClick={this.add}>+</button>
    18. </div>
    19. )
    20. }
    21. })
    22. render(<my-counter />, 'body')

    或者:

    1. import { define, render } from 'omi'
    2. define('my-counter', function() {
    3. const [count, setCount] = this.use({
    4. data: 0,
    5. effect: function() {
    6. document.title = `The num is ${this.data}.`
    7. }
    8. })
    9. this.useCss(`button{ color: red; }`)
    10. return (
    11. <div>
    12. <button onClick={() => setCount(count - 1)}>-</button>
    13. <span>{count}</span>
    14. <button onClick={() => setCount(count + 1)}>+</button>
    15. </div>
    16. )
    17. })
    18. render(<my-counter />, 'body')

    如果没有 effect,可以直接使用 useData: