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

因此,对于以下情况:

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


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

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


或者我们可以使用属性初始化程序语法:

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


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

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


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

根据DOC:


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



问题:

如何避免在render方法中使用这种绑定方式或更改内容

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

#1 楼

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

父母

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


示例代码段




 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> 





编辑:

第二:另一种方法是使用备忘录并返回一个函数

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但是,这不是最佳解决方案,它仍然会导致创建多个函数,但仍然是对
初始情况的一种改进。


对此更合适的解决方案是在最顶部的div上添加一个attribute并从event中获取值,例如
使用toString方法,因此and对象将被转换为[Object Object],并且将["1" , "2", "3"]之类的数组转换为"1, 2, 3"

评论


是的,我们可以做到这一点,但是我们需要创建单独的组件并放置父子关系,因为在大型应用程序中,我们曾经在多个地方进行过这种绑定,所以我认为这不是可扩展的。

– Mayank Shukla
17年7月12日在9:38



我也为此付出了很多努力,我的结论是,如果重新进行功能使您的应用程序变慢(我想...如果您有足够大的数据集可以大量渲染的话,可能会发生这种情况) ),则应针对这些组件采用这种方法。否则,这对于perf来说并不是真正的问题,因此可以安全地忽略。

–克里斯·塞尔贝克(Kris Selbekk)
17年7月12日在9:41

是的,但是如何避免想要的事情和可扩展性在这里应该不是问题

–Shubham Khatri
17年7月12日在9:42



创建类属性时,在前两个代码块中,使用deleteTodo,但是在引用它时,则使用_deleteTodo。这是一个错误还是下划线在这里有特殊用途?

–kojow7
18年2月12日在15:38

@akshaykishore,您可以在这种情况下使用“第三种”方法,而不是将索引传递给onClick

–Shubham Khatri
19年6月28日在7:42

#2 楼


如何避免在render方法中进行这种绑定或该方法的替代方法是什么?


如果您关心重新渲染,则shouldComponentUpdatePureComponent是您的朋友,他们将帮助您优化渲染。

您必须从“父母”中提取“孩子”组件,并传递始终相同的道具并实现shouldComponentUpdate或使用PureComponent。我们想要的是在删除一个孩子的情况下,不应重新渲染其他孩子。
示例示例

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

预期的行为


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

删除<Product id=2 ...时,仅重新渲染<App />


render()

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

文章中使用和描述了相同的技术:React很慢,React很快:FrançoisZaninotto在实践中优化React应用。

评论


感谢您的建议,但我认为使用唯一键将解决此问题。我们想要的是删除一个孩子的情况,不应重新渲染其他孩子,因为我只想使用文本来渲染一个div。当组件很大时,这种方法将发挥重要作用,我们希望避免对其进行重新渲染。

– Mayank Shukla
17年7月12日在10:02



使用键属性不能解决此问题,请查看:即使您使用键属性,也将调用其他的render()来查看codesandbox.io/s/xVZ7pL6E。演示和该链接之间的唯一变化是产品扩展了组件而不是PureComponent。

–Dawid Karabin
17年7月12日在10:46



#3 楼

这个答案https://stackoverflow.com/a/45053753/2808062绝对是详尽无遗的,但是我想说的是,与过度的重新渲染进行斗争而不是仅仅重新创建微小的回调,将为您带来更多的性能改进。通常,可以通过在子组件中实现适当的shouldComponentUpdate来实现。
即使道具完全相同,下面的代码仍将重新渲染子代,除非他们在自己的shouldComponentUpdate中阻止了它们(它们可能会从PureComponent继承它):
handleChildClick = itemId => {}

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

因此,为了避免重新渲染,子组件无论如何都必须实现shouldComponentUpdate。现在,唯一合理的实现方法是完全忽略onClick,无论它是否已更改:
shouldComponentUpdate(nextProps) {
    return this.props.array !== nextProps.array;
}


评论


在您的证明中,您两次调用ReactDom.render。这将迫使所有组件从上到下进行渲染,因此我不确定基于使用onClick的方法如何将其视为重新渲染的依据。实际上,您似乎在暗示官方文档为避免由于事件处理程序连线而导致的重新渲染的建议是错误的。

– mahonya
3月22日上午11:21

感谢您检查我的代码!好吧,尽管我意识到我的建议实际上回答了一个不同的问题,即如何避免不必要的重新渲染而不是如何避免创建过多的功能,但是同一段引用文档在同一段落中说,多余的功能几乎没什么大不了的,不像不必要的重新渲染。关于我两次调用ReactDOM.render,我坚信它的行为方式相同,这是一个类似的示例,其中我将显式重新渲染更改为由某些父状态更新引起的显式重新渲染:jsfiddle.net/7a9enxsb/1。

– grebenyuksv
3月23日22:56



#4 楼

文档鼓励使用数据属性并从evt.target.dataset内访问它们: />
可以在渲染方法中使用箭头函数吗?函数。

如果确实存在性能问题,请进行优化!


评论


由于您的答案现在是2018年,因此,现在要分享,您可以使用“ React Hook”

–艾萨克
18年11月13日在9:28

您在谈论useCallback吗?

–条纹
18年11月13日在9:30