如何避免渲染方法中的绑定或内联箭头函数

How to avoid bind or inline arrow functions inside render method

我们应该避免在 render 中绑定方法,因为在重新渲染期间它会创建新方法而不是使用旧方法,这会影响性能。

所以对于这样的场景:

<input onChange = { this._handleChange.bind(this) } ...../>

我们可以在构造函数中绑定 _handleChange 方法:

this._handleChange = this._handleChange.bind(this);

或者我们可以使用property initializer syntax:

_handleChange = () => {....}

现在让我们考虑一下我们想要传递一些额外参数的情况,假设在一个简单的待办事项应用程序中,单击项目我需要从数组中删除项目,因为我需要传递项目索引或每个 onClick 方法中的待办事项名称:

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

现在假设待办事项名称是唯一的。

根据DOC

The problem with this syntax is that a different callback is created each time the component renders.

问题:

如何避免这种在 render 方法中绑定的方式,或者有什么替代方法?

请提供任何参考或示例,谢谢。

首先: 一个简单的解决方案是在 map 函数中为内容创建一个组件,并将值作为 props 传递,当您从 child 组件,你可以将值传递给作为 props 向下传递的函数。

Parent

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

)

我的组件

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

示例片段

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>

编辑:

其次:另一种方法是使用 memoize 和return一个函数

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

_deleteTodo = (element) => {
   //delete handling here
}

并像

一样使用它
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.

第三:然而,更合适的解决方案是将 attribute 添加到最上面的 div 并从 [=18 获取值=]喜欢

_deleteTodo = (e) => {
     console.log(e.currentTarget.getAttribute('data-value'));

 }

 todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)

但是,在这种情况下,使用 toString 方法将属性转换为字符串,因此 and object 将被转换为 [Object Object] 和数组,如 ["1" , "2", "3"] as "1, 2, 3"

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

如果您关心 re-rendering 那么 shouldComponentUpdatePureComponent 就是您的朋友,他们会帮助您优化渲染。

您必须从 "Parent" 中提取 "Child" 组件并始终传递相同的道具并实施 shouldComponentUpdate 或使用 PureComponent。我们要的是去掉一个child,其他children不应该是re-rendered.

的情况

例子

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'));

演示:https://codesandbox.io/s/99nZGlyZ

预期行为

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

当我们删除 <Product id=2 ... 时,只有 <App /> 是 re-rendered。

  • 渲染()

要在演示中查看这些消息,请打开开发工具控制台。

François Zaninotto 的文章 React is Slow, React is Fast: Optimizing React Apps in Practice 中使用和描述了相同的技术。

这个答案 绝对是详尽无遗的,但我想说的是,与过多的重新渲染作斗争而不是仅仅重新创建微小的回调会给您带来更多的性能改进。这通常通过在子组件中实施适当的 shouldComponentUpdate 来实现。

即使道具完全相同,以下代码仍会重新渲染子项,除非他们在自己的 shouldComponentUpdate 中阻止它(他们可能从 PureComponent 继承它):

handleChildClick = itemId => {}

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

证明:https://jsfiddle.net/69z2wepo/92281/.

因此,为了避免重新渲染,子组件无论如何都必须实现 shouldComponentUpdate。现在,唯一合理的实现是完全忽略 onClick 而不管它是否已经改变:

shouldComponentUpdate(nextProps) {
    return this.props.array !== nextProps.array;
}

Documentation 鼓励使用 data-attributes 并从 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>
)

另外 note 这只有在您遇到性能问题时才有意义:

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!