最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React: how to pass arguments to the callback - Stack Overflow

programmeradmin2浏览0评论

I have a list of elements inside my react component, and I want them to be clickable. On click I call some external function passing item ID in arguments:

render () {
  return (
    <ul>
      {this.props.items.map(item => (
        <li key={item.id} onClick={() => {doSomething(item.id)}></li>
      ))}
    </ul>
  )
}

This code works, but it has a big performance drawback: a lot of new anonymous functions are being created on each call to render.

How can I pass that doSomething function as a reference here while still being able to provide a item.id to it?

I have a list of elements inside my react component, and I want them to be clickable. On click I call some external function passing item ID in arguments:

render () {
  return (
    <ul>
      {this.props.items.map(item => (
        <li key={item.id} onClick={() => {doSomething(item.id)}></li>
      ))}
    </ul>
  )
}

This code works, but it has a big performance drawback: a lot of new anonymous functions are being created on each call to render.

How can I pass that doSomething function as a reference here while still being able to provide a item.id to it?

Share Improve this question asked Jan 23, 2017 at 13:31 Anton TeleshAnton Telesh 3,8223 gold badges24 silver badges33 bronze badges 4
  • @AndrewLi I cannot, because item.id is changing because it's inside a list – Anton Telesh Commented Jan 23, 2017 at 13:34
  • I mean either way, you're going to have use bind or arrow functions (I'd prefer the latter), which both create new functions... is it really a performance problem? – Andrew Li Commented Jan 23, 2017 at 13:36
  • @AndrewLi consider this list containing thousands of elements. render itself is being called frequently, but anonymous function is called even more frequently because it's inside the map. I don't think it would cause any problems on small sets, but I'm seeking for solution for large datasets – Anton Telesh Commented Jan 23, 2017 at 13:40
  • 2 I don't think this is possible. There might be an extremely hacky way but I don't think this is really supported without bind or arrow functions. – Andrew Li Commented Jan 23, 2017 at 13:45
Add a comment  | 

3 Answers 3

Reset to default 17

You could use data-attributes, to set the correct id on each item while using the same function:

function doSomethingFromEvent(event){
  return doSomething(event.target.dataset.id);
}

render () {
  return (
    <ul>
      {this.props.items.map(item => (
        <li key={item.id} data-id={item.id} onClick={doSomethingFromEvent}></li>
      ))}
    </ul>
  )
}

When setting data-* attributes in your element, you can get it back with dataset, in the form of a hash. For example, in doSomethingFromEvent I have event.target.dataset = {id: *id*}. See more on MDN

This is even cleaner when updating a hash (the state for example), with <li key={item.id} data-myattriute={myvalue} onClick={this.handleClick}></li>, I can simply define handleClick such as:

handleClick(event){
    // Here event.target.dataset = {myattribute: myvalue}

    Object.assign(myObject, event.target.dataset);
    // or
    this.setState(event.target.dataset);
}

Coming back to your problem, the great thing with this approach is that if you ensure your container element (ul) cannot be clicked outside its children with data-attributes (li), which is your case, you can declare the function on it:

render () {
  return (
    <ul onClick={doSomethingFromEvent}>
      {this.props.items.map(item => (
        <li key={item.id} data-id={item.id}></li>
      ))}
    </ul>
  )
}

Now your function is created a single time, and is not even repeated in each item.

What you can do is create a partially applied or higher order function to enclose the item.id and pass it along. So let's look at a toy example of this:

class App {

   partiallyApplied = id => e => {
     console.log(id,'this is passed in first')
     console.log(e,'this is passed in second')
   }

   render(){
     return (
       <button onClick={this.partiallyApplied(1234)}>Click Me</button>
     )
   }

}

Now you have access to 1234 along with your event object

This is use transform-class-properties babel plugin. If do not or cannot use that, you can probably do something like this:

partiallyApplied(id){
  return function(e){
   console.log(id,'this is id')
   console.log(e,'this is event')
  }
}

but then you will have to bind this during your call and I just don't like that everywhere.

You could create a new component for every item in the array and use the props, like this:

class Li extends React.Component {
  render() {
    return <li onClick={this.onClick}> {this.props.children} </li>;
  }
  onClick = () => {
    console.log(this.props.item);
  };
}

class App extends React.Component {
  state = {
    items: [
      {id: 1, name: 'one'},
      {id: 2, name: 'two'},
      {id: 3, name: 'three'},
    ]
  };
  render() {
    return <ul> 
      {this.state.items.map(i => 
        <Li key={i.id} item={i}>{i.name}</Li>
      )} 
    </ul>;
  }
}
发布评论

评论列表(0)

  1. 暂无评论