如何在React子组件中设置事件处理程序

21

我无法将菜单项与事件处理程序连接起来。这里是一个 UI 的模拟,显示随时间的状态变化。它是一个下拉菜单(使用 Bootstrap),根菜单项显示当前选择:

[ANN]<click  ...  [ANN]             ...    [BOB]<click  ...  [BOB]  
                    [Ann]                                      [Ann]
                    [Bob]<click + ajax                         [Bob]
                    [Cal]                                      [Cal]

最终目标是根据用户的选择异步更改页面内容。点击Bob应该触发handleClick,但没有。

另外,我对componentDidMount调用this.handleClick();的方式并不满意,但暂时可以作为从服务器获取初始菜单内容的一种方式。

/** @jsx React.DOM */

var CurrentSelection = React.createClass({
  componentDidMount: function() {
    this.handleClick();
  },

  handleClick: function(event) {
    alert('clicked');
    // Ajax details ommitted since we never get here via onClick
  },
  getInitialState: function() {
    return {title: "Loading items...", items: []};
  },
  render: function() {
    var itemNodes = this.state.items.map(function (item) {
      return <li key={item}><a href='#' onClick={this.handleClick}>{item}</a></li>;
    });

    return <ul className='nav'>
      <li className='dropdown'>
        <a href='#' className='dropdown-toggle' data-toggle='dropdown'>{this.state.title}</a>
        <ul className='dropdown-menu'>{itemNodes}</ul>
      </li>
    </ul>;
  }
});


$(document).ready(function() {
  React.renderComponent(
    CurrentSelection(),
    document.getElementById('item-selection')
  );
});

我几乎可以肯定我的对JavaScript作用域的模糊理解是有问题的,但到目前为止,我尝试过的所有方法都失败了(包括尝试通过props向下传递处理程序)。

2个回答

40

问题在于你正在使用匿名函数创建项节点,而在该函数内部,this指的是window。解决方法是在匿名函数中添加.bind(this)

var itemNodes = this.state.items.map(function (item) {
  return <li key={item}><a href='#' onClick={this.handleClick}>{item}</a></li>;
}.bind(this));

或者创建 this 的副本并使用该副本:

var _this = this, itemNodes = this.state.items.map(function (item) {
  return <li key={item}><a href='#' onClick={_this.handleClick}>{item}</a></li>;
})

15
你也可以传递给 map 一个第二个参数 this,它将作为上下文来执行映射函数。 - Sophie Alpert
太棒了,非常感谢!我最终将函数参数分解为 map,以使其更易读,将 this 作为第二个参数:var itemNodes = this.state.items.map(this.itemNode, this); - clozach
@clozach- 如果这个回答解决了你的问题,你能否接受这个答案呢? - imjared
1
@clozach 如果你将其提取到一个函数中,你就不再需要map的上下文对象了——组件上的所有方法都会自动绑定到当前实例。所以这样做是可以正常工作的:var itemNodes = this.state.items.map(this.itemNode) - WickyNilliams
这个答案很棒,简明易懂,我能够理解。相比之下,许多文档似乎是为那些已经知道的人编写的...谢谢。 - landed

0

根据我理解针对 "Anna", "Bob", "Cal" 的任务规范,以下可能是解决方案(基于React组件和ES6):

这里是基本的演示

import React, { Component } from "react"

export default class CurrentSelection extends Component {
  constructor() {
    super()
    this.state = {
      index: 0
    }
    this.list = ["Anna", "Bob", "Cal"]
  }

  listLi = list => {
    return list.map((item, index) => (
      <li key={index}>
        <a
          name={item}
          href="#"
          onClick={e => this.onEvent(e, index)}
        >
          {item}
        </a>
      </li>
    ))
  }

  onEvent = (e, index) => {
    console.info("CurrentSelection->onEvent()", { [e.target.name]: index })
    this.setState({ index })
  }

  getCurrentSelection = () => {
    const { index } = this.state
    return this.list[index]
  }

  render() {
    return (
      <div>
        <ul>{this.listLi(this.list)}</ul>
        <div>{this.getCurrentSelection()}</div>
      </div>
    )
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接