0

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

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

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, но не знаю как это делать.

  • В дубликате указаны способы сохранения контекста, и причины почему он меняется. Так что вполне дубликат – Grundy Aug 22 '17 at 09:14
  • @Grundy, ни одного ответа там не приведено, относящихся к этому вопросу. И повторюсь — сохранение контекста это не цель данного вопроса. Вопрос состоит в предопределении контекста методов. Прочитайте последнее предложение в тексте вопроса. – Unknown User Aug 22 '17 at 09:19
  • @Grundy и ежели я ошибаюсь, будьте добры, объясните, как же применять ответы из того вопроса к моему с приведением примеров – Unknown User Aug 22 '17 at 09:20
  • Какие-то недомиксины... – Qwertiy Aug 22 '17 at 09:33
  • И повторюсь — сохранение контекста это не цель данного вопроса. Вопрос состоит в предопределении контекста методов. - ты как раз и говоришь о сохранении контекста, чтобы контекст внутри onClick всегда был на объект, а не на элемент. И там для этого приведено как минимум два решения: bind и стрелочные функции – Grundy Aug 22 '17 at 09:44
  • 2
    Возможный дубликат вопроса: Потеря контекста вызова – Pavel Mayorov Aug 22 '17 at 12:58
  • 1
    Обновил свой ответ. – Qwertiy Aug 22 '17 at 13:10

2 Answers2

2

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

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' &amp;&amp; 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: От тайпскрипта избавился, поскольку на ответ он не влияет, а без него можно запускаемый сниппет сделать.

Qwertiy
  • 123,725
  • Мутный полумиксин нужен что бы избежать длинных конструкций вроде «this.component.setState». Спасибо за ответ :) – Unknown User Aug 22 '17 at 13:24
  • @VasyaShmarovoz, в принципе, этот код совместим со свойствами, которые были в вопросе. – Qwertiy Aug 22 '17 at 13:26
0

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

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, что восстановили справедливость и переоткрыли вопрос

  • 1
    ну то есть как и в дубликате: стрелочная функция, либо bind. – Grundy Aug 22 '17 at 12:39
  • Плохое решение, т. к. создаётся уйма лишних функций. – Qwertiy Aug 22 '17 at 12:57
  • @Grundy ну то есть это не то решение, которое мне надо, всё верно – Unknown User Aug 22 '17 at 13:26
  • 2
    @VasyaShmarovoz все-таки гляньте тот вопрос, дубликатом которого является ваш. Я там как раз новый ответ написал, который подходит как раз для TypeScript и обработчиков событий. – Pavel Mayorov Aug 22 '17 at 13:28