How to avoid bind or inline arrow functions inside render method

前端 未结 4 1395
暖寄归人
暖寄归人 2020-11-22 06:10

We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.

相关标签:
4条回答
  • 2020-11-22 06:44

    Documentation encourages to use data-attributes and access them from within evt.target.dataset:

    _deleteTodo = (evt) => {
      const elementToDelete = evt.target.dataset.el;
      this.setState(prevState => ({
        todos: prevState.todos.filter(el => el !== elementToDelete)
      }))
    }
    
    // and from render:
    
    todos.map(
      el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
    )
    

    Also note that this makes sense only when you have performance issues:

    Is it OK to use arrow functions in render methods?

    Generally speaking, yes, it is OK, and it is often the easiest way to pass parameters to callback functions.

    If you do have performance issues, by all means, optimize!

    0 讨论(0)
  • 2020-11-22 06:48

    How to avoid this way of binding inside render method or what are the alternatives of this?

    If you care about re-rendering then shouldComponentUpdate and PureComponent are your friends and they will help you optimize rendering.

    You have to extract "Child" component from the "Parent" and pass always the same props and implement shouldComponentUpdate or use PureComponent. What we want is a case when we remove a child, other children shouldn't be re-rendered.

    Example

    import React, { Component, PureComponent } from 'react';
    import { render } from 'react-dom';
    
    class Product extends PureComponent {
      render() {
        const { id, name, onDelete } = this.props;
    
        console.log(`<Product id=${id} /> render()`);
        return (
          <li>
            {id} - {name}
            <button onClick={() => onDelete(id)}>Delete</button>
          </li>
        );
      }
    }
    
    class App extends Component {
      constructor(props) {
        super(props);
    
        this.state = {
          products: [
            { id: 1, name: 'Foo' },
            { id: 2, name: 'Bar' },
          ],
        };
    
        this.handleDelete = this.handleDelete.bind(this);
      }
    
      handleDelete(productId) {
        this.setState(prevState => ({
          products: prevState.products.filter(product => product.id !== productId),
        }));
      }
    
      render() {
        console.log(`<App /> render()`);
        return (
          <div>
            <h1>Products</h1>
            <ul>
              {
                this.state.products.map(product => (
                  <Product 
                    key={product.id}
                    onDelete={this.handleDelete}
                    {...product}
                  />
                ))
              }
            </ul>
          </div>
        ); 
      }
    }
    
    render(<App />, document.getElementById('root'));
    

    Demo: https://codesandbox.io/s/99nZGlyZ

    Expected behaviour

    • <App /> render()
    • <Product id=1... render()
    • <Product id=2... render()

    When we remove <Product id=2 ... only <App /> is re-rendered.

    • render()

    To see those messages in demo, open the dev tools console.

    The same technique is used and described in article: React is Slow, React is Fast: Optimizing React Apps in Practice by François Zaninotto.

    0 讨论(0)
  • 2020-11-22 06:49

    First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.

    Parent

    deleteTodo = (val) => {
        console.log(val)
    }
    todos.map(el => 
        <MyComponent val={el} onClick={this.deleteTodo}/> 
    
    )
    

    MyComponent

    class MyComponent extends React.Component {
        deleteTodo = () => {
            this.props.onClick(this.props.val);
        }
        render() {
           return <div  onClick={this.deleteTodo}> {this.props.val} </div>
        }
    }
    

    Sample snippet

    class Parent extends React.Component {
         _deleteTodo = (val) => {
            console.log(val)
        }
        render() {
            var todos = ['a', 'b', 'c'];
            return (
               <div>{todos.map(el => 
                 <MyComponent key={el} val={el} onClick={this._deleteTodo}/> 
            
               )}</div>
            )
        }
        
       
    }
    
    class MyComponent extends React.Component {
            _deleteTodo = () => {
                         console.log('here');   this.props.onClick(this.props.val);
            }
            render() {
               return <div onClick={this._deleteTodo}> {this.props.val} </div>
            }
        }
        
    ReactDOM.render(<Parent/>, document.getElementById('app'));
    <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>
    <div id="app"></div>

    EDIT:

    Second: The other approach to it would be to use memoize and return a function

    constructor() {
        super();
        this._deleteTodoListener = _.memoize(
                       this._deleteTodo, (element) => {
                            return element.hashCode();
                        }
                  )
    }
    
    _deleteTodo = (element) => {
       //delete handling here
    }
    

    and using it like

    todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
    

    P.S. However this is not a best solution and will still result in multiple functions being created but is still an improvement over the initial case.

    Third: However a more appropriate solution to this will be to add an attribute to the topmost div and get the value from event like

    _deleteTodo = (e) => {
         console.log(e.currentTarget.getAttribute('data-value'));
    
     }
    
     todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
    

    However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object] and and array like ["1" , "2", "3"] as "1, 2, 3"

    0 讨论(0)
  • 2020-11-22 06:56

    This answer https://stackoverflow.com/a/45053753/2808062 is definitely exhaustive, but I'd say fighting excessive re-renders instead of just re-creating the tiny callback would bring you more performance improvements. That's normally achieved by implementing a proper shouldComponentUpdate in the child component.

    Even if the props are exactly the same, the following code will still re-render children unless they prevent it in their own shouldComponentUpdate (they might inherit it from PureComponent):

    handleChildClick = itemId => {}
    
    render() {
        return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
    }
    

    Proof: https://jsfiddle.net/69z2wepo/92281/.

    So, in order to avoid re-renders, the child component has to implement shouldComponentUpdate anyway. Now, the only reasonable implementation is completely ignoring onClick regardless of whether it has changed:

    shouldComponentUpdate(nextProps) {
        return this.props.array !== nextProps.array;
    }
    
    0 讨论(0)
提交回复
热议问题