React.js ES6 避免将“this”绑定到每个方法

IT技术 javascript reactjs ecmascript-6
2021-04-18 08:45:45

最近,我开始修改 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 的主要原因之一是让我的代码更简洁,但这恰恰相反。任何建议或意见将不胜感激。

6个回答

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

class Foo extends React.Component {

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

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

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

}

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

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

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

另一种选择是使用装饰器。您在原型上声明了一个 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);
这看起来非常棒。我一定会试一试。我从来没有真正看过热加载器,所以这对我来说不是问题,但它看起来确实非常有用。谢谢你。
2021-05-22 08:45:45

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', ...) 添加任意数量的方法

我测试了活页夹方法,效果很好。我只是将方法重命名为bindPublicMethods,因此使用它有一些信息上的好处,因为无法在 es6 中标记公共和私有方法。
2021-05-27 08:45:45
谢谢你的建议,这会使绑定方法不那么痛苦,但它仍然需要我指定要绑定的方法。目前,ssorallens 的建议工作正常,我只是在 babel 上启用了实验性功能。这将是一个可行的选择多久,我不知道,但就目前而言,我认为这是最短和最干净的选择。
2021-06-10 08:45:45

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

class MyComp extends Component {

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

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

}

这会分解为this.handleClick.call(this),我认为它通常具有足够的性能。

应该有糖 <button onClick={() => this.handleClick(arguments)}>
2021-05-24 08:45:45
简单的 <button onClick={() => this.handleClick()}>Do Things</button> 怎么样?
2021-06-12 08:45:45
React 文档建议不要这样做,因为在render组件的方法中放置一个绑定方法将强制它与每次重新渲染绑定。我们建议您在构造函数中绑定您的事件处理程序,以便它们只为每个实例绑定一次
2021-06-13 08:45:45
绝对可靠的建议,但我承认我并不太担心它并自由地使用它:D
2021-06-17 08:45:45
哇,这非常棒,但它处于第 0 阶段,我可能不建议将它用于任何严肃的事情。我不知道他们提出了这个功能。
2021-06-18 08:45:45

一种避免绑定的想法

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) => this.handleClick(e, arg1, arg2, arg3)}将有 4 个参数。箭头函数可以使用任意数量的参数。
2021-06-08 08:45:45
这样每次调用渲染时都会创建一个新函数
2021-06-20 08:45:45