【问题标题】:React.js ES6 avoid binding 'this' to every methodReact.js ES6 避免将“this”绑定到每个方法
【发布时间】:2015-11-18 11:44:46
【问题描述】:

最近,我开始修改 React.js,我喜欢它。我从普通的 ES5 开始,为了掌握事情的窍门,文档都是用 ES5 编写的......

但现在我想尝试 ES6,因为它闪亮又新,而且它似乎确实简化了一些事情。令我困扰的是,对于我添加到组件类中的每个方法,我现在都必须将“this”绑定到,否则它不起作用。所以我的构造函数最终看起来像这样:

constructor(props) {
  super(props);
  this.state = { ...some initial state... }

  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
}

如果我要在我的类中添加更多方法,这将变得更大、更丑陋。

我的问题是,有没有办法解决这个问题,或者至少让它更容易、更短、更丑?我想用 ES6 尝试 React 的主要原因之一是让我的代码更简洁,但这恰恰相反。任何建议或意见将不胜感激。

【问题讨论】:

  • 你真的在某处使用所有方法作为处理程序吗?
  • 另见this answer
  • 嗯,这是从更高级别的组件中获取的,所以是的,它们都在层次结构的某个地方使用。我试图遵循他们的理念,并试图最好地弄清楚哪个组件需要知道什么,这就是我最终得到的。
  • 但是它们不是(不应该)作为组件对象上的方法调用,而不是作为函数传递吗?
  • 我个人为此使用装饰器 (specifically autobind) 而不是类属性初始化器。

标签: javascript reactjs ecmascript-6


【解决方案1】:

您可以使用class fields 在构造函数之外进行绑定。它们如下所示:

class Foo extends React.Component {

  handleBar = () => {
    console.log('neat');
  };

  handleFoo = () => {
    console.log('cool');
  };

  render() {
    return (
      <div
        onClick={this.handleBar}
        onMouseOver={this.handleFoo}
      />
    );
  }

}

Babel 通过其 class properties transform 实验性地支持类字段,但它们仍然是“实验性的”,因为它们是 Stage 3 Draft(尚未在 Babel 预设中)。

但是,在 ES7 或在 Babel 中启用该功能之前,您将需要手动进行绑定。 Babel 在React on ES6+ 上的博客文章中简要介绍了这个主题。

【讨论】:

  • 谢谢。真可惜。我使用 gulp 作为我的构建系统,似乎 gulp 包没有该选项。关于如何让这更容易忍受的任何建议?
  • 哦,是的,所以你可以。我用babel({ stage: 0 }) 运行它并且它有效。这似乎是一个临时解决方案,因为它还处于早期阶段,但现在效果很好。感谢您的帮助。
  • @Brandon 他们有同样的效果。每次创建新实例时都会调用constructor 方法,因此每个实例都会调用bind。 React 文档建议避免在 render 函数中调用 bind,因为 render 在给定实例上被多次调用,而构造函数只被调用一次。
  • @DivyanshuMaithani 类属性初始化器目前仍是一个提议,因此无法说原生版本是否会影响性能。但是,如果您使用的是 Babel,那么属性初始化器将被转换为在构造函数中分配绑定实例;它们完全相同:babeljs.io/repl/…
  • 这个例子真的很有帮助,而且这种方法使组件看起来更干净,会用它:)
【解决方案2】:

另一种选择是使用装饰器。您在原型上声明一个 getter,并在第一次访问实例时定义一个具有该函数绑定版本的自己的属性。

但是有一个问题!在开发中,它不会替换属性,它会在每次访问时绑定。 这意味着您不会破坏 react-hot-loader。至少对我来说,这很重要。

我创建了一个库,class-bind,它提供了这个。

import {bound} from 'class-bind';

class App {
  constructor(){
    this.foo = 'bar';
  }

  @bound
  returnsFoo(){
    return this.foo;
  }

  render(){
    var returnsFoo = this.returnsFoo;
    return (
      <div>
        {returnsFoo()} === 'bar'
      </div>
    );
  }
}

装饰器对你来说太不稳定了?您可以将所有或某些事物绑定到相同的好处。

import {bind, bindAll} from 'class-bind';

bind(App.prototype, 'returnsFoo');

// or
bindAll(App.prototype);

【讨论】:

  • 这看起来很棒。我一定会试一试的。我从来没有真正看过热加载器,所以这对我来说不是问题,但它看起来确实非常有用。谢谢。
【解决方案3】:

Ssorallen 的建议很棒,但如果你想要另一种方式:

    class AppCtrlRender extends Component {
        binder(...methods) { methods.forEach( (method) => this[method] = this[method].bind(this) ); }

        render() {
            var isMobile = this.state.appData.isMobile;
            var messages = this.state.appData.messages;
            return (
                <div id='AppCtrlSty' style={AppCtrlSty}>
                    React 1.3 Slider
                    <br/><br/>
                    <div className='FlexBoxWrap'>
                        <Slider isMobile={isMobile}/>
                        <JList data={messages}/>
                    </div>
                </div>
            );
        }
    }

    var getAppState = function() {
        return {
            appData: AppStore.getAppData()
        };
    };

    export default class AppCtrl extends AppCtrlRender {
        constructor() {
            super();
            this.state = getAppState();
            this.binder('appStoreDidChange');
        }

        componentDidMount() {
            var navPlatform = window.navigator.platform;
            Actions.setWindowDefaults(navPlatform);
        }
        componentWillMount() { AppStore.onAny(this.appStoreDidChange); }
        componentWillUnmount() { AppStore.offAny(this.appStoreDidChange); }
        appStoreDidChange() { this.setState(getAppState()); }
    }

您可以向 this.binder('method1', 'method2', ...) 添加任意数量的方法

【讨论】:

  • 感谢您的建议,这将使绑定方法不那么痛苦,但仍然需要我指定要绑定的方法。目前,ssorallens 的建议工作正常,我只是在 babel 上启用了实验性功能。我不知道这将是一个可行的选择多久,但就目前而言,我认为这是最短和最干净的选择。
  • 我测试了 binder 方法,效果很好。我只是将方法重命名为bindPublicMethods,所以使用它有某种信息优势,因为在 es6 中无法标记公共和私有方法。
【解决方案4】:

如果您使用stage-0,则有一个函数绑定语法。

class MyComp extends Component {

  handleClick() { console.log('doing things') }

  render() {
    return <button onClick={::this.handleClick}>Do Things</button>
  }

}

这会分解为this.handleClick.call(this),我认为它通常足够高效。

【讨论】:

  • 哇,这真是太棒了,但它处于第 0 阶段,我可能不建议将它用于任何严重的事情。我不知道他们提出了这个功能。
  • 绝对可靠的建议,但我承认我并不太担心它并大量使用它:D
  • React 文档建议不要这样做,因为在组件的 render 方法中放置绑定方法将强制它与每次重新渲染绑定。 "我们建议您在构造函数中绑定事件处理程序,这样每个实例只绑定一次" source
  • 简单的 怎么样?
  • &lt;button onClick={() =&gt; this.handleClick(arguments)}&gt;应该有糖
【解决方案5】:

避免绑定的一个想法

class MyComp extends Component {

  render() {
    return <button onClick={e => this.handleClick(e)}>Do Things</button>
  }

}

免责声明:未经测试,也不能轻易处理多个参数(在这种情况下,有一个,事件(e)。

此外,根据这篇文章,这可能是一个应该做的例子,这可能值得一读:

https://daveceddia.com/avoid-bind-when-passing-props/

【讨论】:

  • onClick = {(e, arg1, arg2, arg3) =&gt; this.handleClick(e, arg1, arg2, arg3)} 将有 4 个参数。箭头函数可以使用任意数量的参数。
  • 这样每次调用 render 时都会创建一个新函数
【解决方案6】:

我实际上更喜欢通过将父上下文传递给子级来模仿 OOP 继承。

class Parent extends Component {
  state = {happy: false}

  changeState(happy) {
    this.setState({happy})
  }

  render() {
    return (
      <Child parent={this} >
    )
  }
}

class Child extends Component {
   //...
   this.props.parent.changeState(true)
}

0.02 美元, 乔恩

【讨论】:

    【解决方案7】:

    我创建了一种方法来组织所有“绑定”。

    class MyClass {
      constructor() {
    
        this.bindMethods([
          'updateLocationFields',
          'render',
          'loadCities',
        ]);
      }
    
      bindMethods(methods) {
        methods.forEach((item) => {
          this[item] = this[item].bind(this);
        });
      }
    
      ...
    }
    

    【讨论】:

    • >.> 。 . . . . .
    【解决方案8】:

    我使用一个辅助函数 doBinding(this),我在每个构造函数中调用它。在这个例子中,它绑定了_handleChange1()_handleChange2()

    class NameForm extends React.Component {
        constructor(props) {
            super(props);
            doBinding(this);
            this.state = {value1: "", value2: ""};
        }
        _handleChange1(event) {
            this.setState({value1: event.target.value});
        }
        _handleChange2(event) {
            this.setState({value2: event.target.value});
        }
        render() {
           ...
        }
    }
    

    即使您不使用 Babel,该方法也有效。

    我的处理程序方法都以_ 开头(表示它们是私有的约定)。所以doBinding() 寻找_。如果您不使用此约定,则可以删除 if (key.startsWith("_"))

    function doBinding(obj) {
        const proto = Object.getPrototypeOf(obj);
        for (const key of Object.getOwnPropertyNames(proto)) {
            if (key.startsWith("_")) {
                obj[key] = obj[key].bind(obj);
            }
        }
    }
    

    【讨论】:

      【解决方案9】:

      如何使用一个通用函数来完成这样的绑定工作:

      // common function:
      function bind(self,methods){
           for(var key in methods){
             self[key] = methods[key].bind(self);
           }
      }
      
      // your class:
      class MyClass {
           constructor() {
                bind(this,{
                    someHandler1(event){ 
                      //... 
                    },
                    someHandler2(event){ 
                      //...
                    }
                })
           }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-09-21
        • 1970-01-01
        • 2017-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-04
        相关资源
        最近更新 更多