Подтвердить что ты не робот

React.js ES6 избегает связывания 'this' с каждым методом

Недавно я начал возиться с 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", заключалась в том, чтобы сделать мой код более кратким, но это делается наоборот. Любые предложения или ввод будут оценены.

4b9b3361

Ответ 1

Вы можете использовать поля класса для связывания вне конструктора. Они выглядят так:

class Foo extends React.Component {

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

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

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

}

Поля классов экспериментально поддерживаются Babel через преобразование свойств класса, но они все еще являются "экспериментальными", потому что они являются черновым этапом 3 (еще не в предустановке Babel).

Однако вам нужно будет выполнить привязку вручную до ES7 или до включения функции в Babel. Эта тема кратко освещена в блоге Babel на React на ES6+.

Ответ 2

Другой альтернативой является использование декораторов. Вы объявляете геттер на прототипе, и при первом доступе к экземпляру он определяет собственное свойство со связанной версией этой функции.

Но есть улов! В процессе разработки он не заменит свойство, он будет привязываться при каждом доступе. Это означает, что вы не сломаете реактивный жар-загрузчик. По крайней мере, для меня это очень важно.

Я создал библиотеку 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',...)

Ответ 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), который, как мне кажется, обычно достаточно эффективен.

Ответ 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/

Ответ 6

Я действительно предпочитаю имитировать наследование ООП, передавая родителям родительский контекст.

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);
        }
    }
}