Передача контекста в метода класса

237
23 августа 2017, 19:46

Существует реакт-компонент и класс с обработчиками этого компонента, который строится на специальном абстрактном классе. Выглядит это так:

Абстрактный класс

export default abstract class HandlersCreator<C extends Component> {
    constructor(public component: C) {}
    protected get props(): C['props'] {
        return this.component.props
    }
    protected get state(): C['state'] {
        return this.component.state
    }
    protected setState<K extends keyof C['state']>(
        state: Pick<C['state'], K>,
        callback?: () => any
    ): void {
        return this.component.setState(state, callback)
    }
    protected dispatch<A extends Action>(action: A): A {
        const dispatchFunc: Dispatch<C> = (<any>this.component.props).dispatch
        return dispatchFunc(action)
    }
}

Класс с обработчиками

export default class Handlers extends HandlersCreator<MyComponent> {
    public onClick() {
        this.setState({ property: value })
    }
}

Компонент

class MyComponent extends Component {
    constructor() {
        super()
        this.handlers = new Handlers(this)
    }
    public render() {
        return <div onClick={this.handlers.onClick}/>
    }
}

Проблема заключается в том, что контекст (this) передаётся в метод «onClick» не от экземпляра класса Handlers, а от div'а, соответственно такой код выдаст исключение, мол this.setState is not a function.

Каким образом можно предустановить контекст без прямого бинда внутри onClick'а div'а? Спасибо.

UPD
В голове появляется мысль пробежаться в конструкторе абстрактного класса по методам экземпляра и пробиндить их все на this, но не знаю как это делать.

Answer 1

Не надо городить какие-то мутные полумиксины. Если уж есть необходимость вынесения хэндлеров в отдельный класс (переиспользование хэндлеров?), то надо, чтобы эти хэндлеры работали непосредственно с компонентом, а не выволакивали из него какие-то куски:

class HandlersCreator { 
  constructor(component) { 
    this.component = component 
 
    for (var p=this; (p=Object.getPrototypeOf(p))!==Object.prototype; ) { 
      for (var key of Object.getOwnPropertyNames(p)) { 
        if (key !== 'constructor' && typeof p[key] === 'function') { 
          this[key] = this[key].bind(this) 
        } 
      } 
    } 
  } 
} 
 
class Handlers extends HandlersCreator { 
  onClick() { 
    var {state} = this.component 
   
    this.component.setState({ 
      count: state.count + 1 
    }) 
  } 
} 
 
class MyComponent extends React.Component { 
  constructor(props) { 
    super(props) 
    this.state = { count: 0 } 
    this.handlers = new Handlers(this) 
  } 
 
  render() { 
    return <div onClick={this.handlers.onClick}>{this.state.count}</div> 
  } 
} 
 
ReactDOM.render(<MyComponent />, document.querySelector('main'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> 
<main></main>

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

Answer 2

Пока использую вложенные функции для обработчиков событий элементов, но это не самое красивое решение

export default class Handlers extends HandlersCreator<MyComponent> {
    public onClick(): eventHandler {
        return (event: Event) => {
            this.setState({ property: value })
        }
    }
}
// ...
class MyComponent extends Component {
    constructor() {
        super()
        this.handlers = new Handlers(this)
    }
    public render() {
        return <div onClick={this.handlers.onClick()}/>
    }
}

З.Ы. Спасибо сообществу stackoverflow, что восстановили справедливость и переоткрыли вопрос

READ ALSO
Как в jsfiddle писать на ES6 из под IE?

Как в jsfiddle писать на ES6 из под IE?

писал пример на Babel + JSX, при проверке в IE11 получил ошибку на array function (в IE11 её поддержки нет)

416
Задача с областью видимости JS [дубликат]

Задача с областью видимости JS [дубликат]

На данный вопрос уже ответили:

248