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

IT技术 javascript reactjs arrow-functions function-binding class-fields
2021-01-12 01:09:55

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

所以对于这样的场景:

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

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

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

或者我们可以使用属性初始值设定项语法

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

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

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

现在只假设 todo 名称是唯一的。

根据DOC

这种语法的问题在于每次渲染组件时都会创建不同的回调。

问题:

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

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

4个回答

第一:一个简单的解决方案是为 map 函数内的内容创建一个组件,并将值作为 props 传递,当您从子组件调用该函数时,您可以将值传递给作为 props 传递的函数。

家长

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 并返回一个函数

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

PS 然而,这不是最好的解决方案,仍然会导致创建多个函数,但仍然比最初的情况有所改进。

第三:然而,更合适的解决方案是attribute在最上面的 div添加一个并从eventlike 中获取值

_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]and 和数组,["1" , "2", "3"]"1, 2, 3"

在创建类属性时的前两个代码块中,您使用deleteTodo,但是当您引用它时,您使用_deleteTodo. 这是一个错误还是下划线在这里有什么特殊用途?
2021-03-16 01:09:55
是的,但是如何避免想要的东西和可扩展性在这里应该不是问题
2021-03-25 01:09:55
我也一直在努力解决这个问题,我的结论是,如果这种函数的重新创建会减慢您的应用程序的速度(这......我猜......如果你有足够大的数据集重新渲染很多),您应该对这些组件遵循此方法。否则,这对于性能来说并不是真正的问题,因此可以安全地忽略。
2021-03-27 01:09:55
@akshaykishore,在这种情况下,您可以使用第三种方法,而不是将索引传递给 onClick
2021-03-29 01:09:55
是的,我们可以这样做,但是有了这个,我们需要创建单独的组件并放置父子关系,我认为这不会那么可扩展,因为在大型应用程序中我们曾经在多个地方进行这种绑定。
2021-04-02 01:09:55

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

如果您关心重新渲染shouldComponentUpdate并且PureComponent是您的朋友,他们将帮助您优化渲染。

您必须从“父”中提取“子”组件并始终传递相同的props并实现shouldComponentUpdate或使用PureComponent. 我们想要的是当我们移除一个孩子时,其他孩子不应该被重新渲染。

例子

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 />重新渲染。

  • 使成为()

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

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

感谢您的建议,但我认为使用唯一键可以解决这个问题我们想要的是一种情况,当我们删除一个孩子时,不应该重新渲染其他孩子,因为我只想用文本渲染单个 div。当组件很大并且我们希望避免重新渲染它时,这种方法将发挥重要作用。
2021-03-20 01:09:55
使用keyproperty并不能解决这个问题,看:codeandbox.io/s/xVZ7pL6E即使你使用keyproperty,其他的render()<Product />也会被调用。演示和该链接之间的唯一变化是Product extends Component而不是PureComponent
2021-04-02 01:09:55

文档鼓励使用数据属性并从内部访问它们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>
)

请注意,这仅在您遇到性能问题时才有意义:

可以在渲染方法中使用箭头函数吗?

一般来说,是的,是可以的,而且通常是给回调函数传递参数最简单的方式。

如果您确实有性能问题,请务必进行优化!

由于您的答案现在是在 2018 年,所以现在分享您可以使用“React Hook”
2021-03-15 01:09:55
你是在说useCallback吗?
2021-03-15 01:09:55

这个答案https://stackoverflow.com/a/45053753/2808062绝对是详尽无遗的,但我想说与过度的重新渲染作斗争而不是仅仅重新创建微小的回调会给您带来更多的性能改进。这通常是通过shouldComponentUpdate在子组件中实现一个正确的来实现的

即使 props 完全相同,下面的代码仍然会重新渲染孩子,除非他们自己阻止它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;
}
感谢您查看我的代码!好吧,虽然我意识到我的建议实际上回答了一个不同的问题,即如何避免不必要的重新渲染而不是如何避免创建多余的函数,但同一个引用的文档在同一段落中说,多余的函数几乎没有什么大不了的,不像不必要的重新渲染。关于我调用 ReactDOM.render 两次,我坚信它的行为方式相同,这是一个类似的示例,我将显式重新渲染更改为由某些父状态更新引起的重新渲染:jsfiddle.net/7a9enxsb/1
2021-03-14 01:09:55
在你的证明中,你调用了 ReactDom.render 两次。这将强制所有组件从上到下呈现,所以我不确定这如何作为基于使用 onClick 的方法重新呈现的证据。事实上,您似乎建议官方文档建议避免由于事件处理程序接线而重新渲染的内容是错误的。
2021-03-19 01:09:55