Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

为什么要写 super(props) #8

Open
oliver1204 opened this issue Dec 10, 2018 · 0 comments
Open

为什么要写 super(props) #8

oliver1204 opened this issue Dec 10, 2018 · 0 comments

Comments

@oliver1204
Copy link
Owner

oliver1204 commented Dec 10, 2018

听过这样一个小玩笑,「我这辈子写过的 super(props) 比我想象的要多得多」。不过这也证明了,super的重要性。

class myComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}

当然,class fields proposal 允许我们跳过这个仪式。

class Checkbox extends React.Component {
  state = { isOn: true };
  // ...
}

我们为什么要调用super?能不能不调用它?如果非要调用,如果不传 props 会怎样?还有其它参数吗?让我们来看一下。

在 JavaScript 中,super 指代父类的构造函数。(在我们的案例中,它指向 React.Component 这个实现)

重点在于,在你调用父类构造函数之前,你无法在构造函数中使用 this。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!');
  }
}

想象一下如果在调用 super 前使用 this 是被允许的。一个月之后。我们或许会改变 greetColleagues 把 person 的 name 加到消息中。

greetColleagues() {
  alert('Good morning folks!');
  alert('My name is ' + this.name + ', nice to meet you!');
}

但我们忘了 �this.greetColleagues() 是在 super() 有机会设置 this.name 之前被调用的。this.name 甚至还没被定义!如你所见,像这样的代码理解起来会很困难。

为了避免这样的陷阱,JavaScript 强制规定,如果你想在构造函数中只用this,就必须先调用 super。让父类做它该做的事!这一限制也适用于定义成类的 React 组件。

constructor(props) {
  super(props);
  // ✅ 现在可以使用 `this` 了
  this.state = { isOn: true };
}

这给我们留下了另一个问题:为什么要传 props?

你或许觉得把 props 传进 super 是必要的,这使得基类 React.Component 可以初始化 this.props:

// React 内部
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

很接近了——事实上,它就是这么做的。

然而,即便在调用 super() 时没有传入 props 参数,你依然能够在 render 和其它方法中访问 this.props。(你要是不相信我,可以自己试一试)这是什么原理?其实 React 在调用你的构造函数之后,马上又给实例设置了一遍 props:

// React 内部
const instance = new YourComponent(props);
instance.props = props;

因此,即便你忘了把 props 传入 super(),React 依然会在事后设置它们。这是有理由的。

当 React 添加对 Class 的支持时,它并不是只添加了对 ES6 的支持,而是希望能够支持尽可能广泛的 class 抽象。由于不是很确定 ClojureScript、CoffeeScript、ES6、Fable、Scala.js、TypeScript 或其他解决方案谁更适合用来定义组件,React 对于是否有必要调用 super() 刻意不表态。

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

或许并非如此,因为这依然让人困扰。诚然,React 会在你的构造函数运行之后设置 this.props。但在 super 调用一直到构造函数结束之前,this.props 依然是未定义的。

// React 内部
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}
// 你的代码
class Button extends React.Component {
  constructor(props) {
    super(); // 😬 我们忘了传入 props
    console.log(props);      // ✅ {}
    console.log(this.props); // 😬 undefined
  }
  // ...
}

如果这发生在某些从构造函数中调用的函数,调试起来会更加麻烦。这也是为什么我推荐总是使用 super(props) 的写法,即便这是非必要的:

class Button extends React.Component {
  constructor(props) {
    super(props); // ✅ 我们传了 props
    console.log(props);      // ✅ {}
    console.log(this.props); // ✅ {}
  }
  // ...
}

这样的写法确保了 this.props即便在构造函数返回之前就被设置好了。

换句话说,如果给足够的时间等待返回,也是可以得到的预期的效果的。如下:

constructor(props){
	super(props);
        setTimeout(() => {
             console.log(this.props)
        }, 5000) ;
  }

最后还有一点是 React 的长期用户或许会好奇的。

你或许已经注意到,当你在 Class 中使用 Context API 时(无论是旧版的语法还是 React 16.6 中新增的现代化语法),context 是被作为构造函数的第二个参数传入的。

那么我们为什么不写 super(props, context) 呢?当然我们可以这么做,但 context 的使用频率没那么高,所以这个陷阱影响还没那么大。

伴随着 class fields proposal 的发布,这个问题也就不复存在了。即便不显式调用构造函数,所有参数也会自动传入。这就允许像 state = {} 这样的表达式在必要时可以直接引用 this.props. 或 this.context。

在 Hooks 中,我们甚至都没有 super 或 this。这个话题我们择日再说。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant