react为什么需要手动绑定方法?

React干货 admin 暂无评论

我们从javascript开始吧

在js中,函数的上下文是指函数调用的时候,而不是定义的时候。

有以下四中调用函数的模式:

  • 函数调用模式

  • 方法调用模式

  • 构造函数调用模式

  • 应用调用模式

所有这些使用的模式都不同地定义函数上下文。接下来我们看看各种模式的区别。

函数调用模式

定义:如果在调用时没有.操作,那么上下文可能为window。

调用函数最直接的方法就是直接调用它:

1.var func = function(){
2.    //...
3.};
4.func();

这时的上下文(this)将会设置成javascript操作环境的全局变量,在浏览器中,它是window变量。

我们再来看另一个例子:

1.var unicorns = {
2.  func: function() { // ... }
3.};
4.var fun = unicorns.func;
5.fun();

你认为在func中的上下文为uniconrns对象?那是错误的,由于上下文时通过调用此函数时确定的,所以这里的上下文还是window。

方法调用模式

定义:如果函数调用中有点操作,则上下文将会是一序列点中最右边的那个变量。

如上面的例子中,如果我们直接调用unicorns.func(),上下文会是unicorns对象。

1.var frog = {
2.  RUN_SOUND: "POP!!",
3.  run: function() {
4.    return this.RUN_SOUND;
5.  }
6.};
7.frog.run(); // returns "POP!!" since this points to the `frog` object.
8.var runningFun = frog.run;
9.runningFun(); // returns "undefined" since this points to the window

构造函数模式

定义:每次看到一个new函数名后,你this将指向一个新创建的新对象。

1.function Wizard() {
2.  this.castSpell = function() { return "KABOOM"; }
3.}

直接调用它将会是window(因为它是一个函数调用),但是如果通过new来调用:

1.function Wizard() {
2.  this.castSpell = function() { return "KABOOM"; };
3.}
4.var merlin = new Wizard(); // this is set to an empty object {}. Returns `this` implicitly.
5.merlin.castSpell() // returns "KABOOM";

这将会发生两件事:

  • 函数将会有一个指向当前对象的上下文this。

  • 如果没有指定return或者这个函数返回一个非对象值,this将从这个函数返回。

应用调用模式

当你对函数有引用的时候,你可以通过两种方法来手动提供上下文:

  • call

  • apply

1.function addAndSetX(a, b) {
2.  this.x += a + b;
3.}
4.var obj1 = { x: 1, y: 2 };
5.addAndSetX.call(obj1, 1, 1); // this = obj1, obj1 after call = { x: 3, y : 2 }
6.// It is the same as:
7.// addAndSetX.apply(obj1, [1, 1]);

如果您需要调用从某个其他地方传递的函数(例如,作为参数到函数中)与某个上下文对象,这是非常方便的。它不是非常可用于异步回调,因为绑定与一个函数调用相结合。

要使用回调设置正确的上下文,您可能需要另一种方便的技术 - 您可以从中创建有界函数。

绑定功能

有界函数是一个限定给定上下文的函数,这意味着无论你怎么调用它,它的上下文都是不变的。唯一例外是总是返回一个新上下文的new运算符。

要是普通函数变成有界函数,应该使用bind方法,bind方法将您要将函数绑定到的上下文作为第一个参数。其余的参数是将始终传递给这样的函数的参数。

结果返回有界函数。我们来看一个例子:

1.function add(x, y) {
2.  this.result += x + y;
3.}
4.var computation1 = { result: 0 };
5.var boundedAdd = add.bind(computation1);
6.boundedAdd(1, 2); // `this` is set to `computation1`.
7.                  //  computation1 after call: { result: 3 }
8.var boundedAddPlusTwo = add.bind(computation1, 2);
9.boundedAddPlusTwo(4); // `this` is set to `computation1`.
10.                      // computation1 after call: { result: 9 }

被绑定了的函数甚至不能在通过call或apply改变上下文:

1.var obj = { boundedPlusTwo: boundedAddPlusTwo };
2.obj.boundedPlusTwo(4); // `this` is set to `computation1`.
3.                       // even though method is called on `obj`.
4.                       // computation1 after call: { result: 15 }
5.var computation2 = { result: 0 };
6.boundedAdd.call(computation2, 1, 2); // `this` is set to `computation1`.
7.                                     // even though context passed to call is
8.                                     // `computation2`
9.                                     // computation1 after call: { result: 18 }

您现在已经掌握了关于JavaScript的知识,现在让我们来看react中的情况。

怎么绑定以及绑定什么

ECMAScript 2015(ECMAScript 6)引入了一种新的类语法,可用于创建React组件类。实际上,这个类语法是面向对象JavaScript 的旧的原型系统的语法糖。

这意味着ES2015类中的函数上下文调用遵循与其余JavaScript相同的原则。

1.class Foo {
2.  constructor() {
3.    this.x = 2;
4.    this.y = 4;
5.  }
6.  bar() {
7.    // ...
8.  }
9.  baz() {
10.    // ...
11.  }
12.}

与以下大致相同:

1.function Foo() {
2.  this.x = 2;
3.  this.y = 4;
4.  this.bar = function() { // ... };
5.  this.baz = function() { // ... };
6.}

记住这只是一个简化。在确定函数上下文调用的情况下,这个更复杂的逻辑遵循与上面的代码片段相同的原理。

React.createClass

在这个语法下,绑定问题是不存在的,在传递给对象的对象中定义的所有方法React.createClass将自动绑定到组件实例。这意味着你可以随时使用setState,访问props和state等等这些方法。

尽管在99%的情况下可能完全可以接受,但它限制了您对任意设置上下文的能力 - 这可能是更复杂的代码库中的一个大问题。

ECMAScript 2015 classes

在ECMAScript 2015 classes写法中,你需要手动绑定方法。

以下是React库中是可以识别为方法调用模式执行调用:

  • 组件生命周期方法。它仅仅通过component.componentDidUpdate(…)方式调用(因此,this已经正确绑定到组件实例本身)。

  • render方法。它也是被识别为方法调用模式执行调用。大多数的非事件处理函数在render方法中调用,它已经被自动绑定到组件实例,所以你可以放心使用。

但是,传递给事件处理属性的函数可能有许多来源,甚至可能通过顶级组件的属性从非React级别传递给他们。

在React.createClassReact假定它们来自您的组件并自动绑定它们。但是在ES2015 classes中你有自由。在引擎中,它们被以函数调用模式调用。

这意味这,在默认情况下,你无法在事件处理程序中读取组件属性、状态和组件的方法,为此,你需要明确地绑定他们。

绑定事件处理程序的最佳位置是构造函数:

1.class InputExample extends React.Component {
2.  constructor(props) {
3.    super(props);
4.    this.state = { text: '' };
5.    this.change = this.change.bind(this);
6.  }
7.  change(ev) {
8.    this.setState({ text: ev.target.value });
9.  }
10.  render() {
11.    let { text } = this.state;
12.    return (<input type="text" value={text} onChange={this.change} />);
13.  }
14.}

这样你的事件处理程序的上下文将会绑定到组件实例中。

类属性

有一个实验功能,称为类属性,可以帮助您明确避免绑定方法。它是用于在构造函数中定义字段和函数的语法糖。看起来像这样:

1.class InputExample extends React.Component {
2.  state = { text: '' };
3.  // ...
4.}

并编译成以下:

1.class InputExample extends React.Component {
2.  constructor(...arguments) {
3.    super(...arguments);
4.    this.state = { text: '' };
5.  }
6.  // ...
7.}

那么怎么定义一个方法呢?

1.class InputExample extends React.Component {
2.  state = { text: '' };
3.  change = function(ev) {
4.    this.setState({ text: ev.target.value });
5.  };
6.  // ...
7.}

所以现在,你得到一个等同于以下类:

1.class InputExample extends React.Component {
2.  constructor(...arguments) {
3.    super(...arguments);
4.    this.state = { text: '' };
5.    this.change = function(ev) {
6.      this.setState({ text: ev.target.value });
7.    };
8.  }
9.  // ...
10.}

但是这样有一个问题,this.change函数上下文还是错误的,所以我们要结合箭头函数:

1.class InputExample extends React.Component {
2.  state = { text: '' };
3.  change = ev => this.setState({text: ev.target.value});
4.  render() {
5.    let {text} = this.state;
6.    return (<input type="text" value={text} onChange={this.change} />);
7.  }
8.}

该解决方案的缺点是类属性仍处于实验阶段。这意味着此功能可以在ECMAScript 2016(也称为ECMAScript 7或ES7)的后续迭代中被删除,而不会发出警告。

createClass以及class语法编译完的不同

我们先来看类写法:

1.class Todo extends Component{
2.    handleClick(){
3.        console.info(this);
4.    }
5.    method(){
6.        console.info(this);
7.    }
8.    render(){
9.        this.method();
10.        return (
11.            <div>
12.                <p onClick={this.handleClick}>Hello</p>
13.            </div>
14.        )
15.    }
16.}

编译完:

1.var Todo = function (_Component) {
2.    _inherits(Todo, _Component);
3.    function Todo() {
4.        _classCallCheck(this, Todo);
5.        return _possibleConstructorReturn(this, _Component.apply(this, arguments));
6.    }
7.    Todo.prototype.handleClick = function handleClick() {
8.        console.info(this);
9.    };
10.    Todo.prototype.method = function method() {
11.        console.info(this);
12.    };
13.    Todo.prototype.render = function render() {
14.        this.method();
15.        return __WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement(
16.            'div',
17.            null,
18.            __WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement(
19.                'p',
20.                { onClick: this.handleClick },
21.                'Hello'
22.            )
23.        );
24.    };
25.    return Todo;
26.}(__WEBPACK_IMPORTED_MODULE_0_react__["Component"]);

this.handleClick被放在{onClick: this.handleClick}中,所以当被调用的时候会被识别为函数调用模式,所以这时的上下文是null(为什么不是window或其他的???)


转载请注明:React教程中文网 - 打造国内领先的react学习网站-react教程,react学习,react培训,react开 » react为什么需要手动绑定方法?

喜欢 ()or分享