新东家的merge request比前任要严格好多。javascript
一些平时习觉得常的习惯,发现会引发好多问题。java
曾经由于组件拆分的很差,merge request卡了两天之久,改了三版。有兴趣的话能够留言,到时候能够开篇讲讲~node
也由于被提的次数多了,致使如今在node端看到接口请求就条件反射try catch。函数
今天这个主题来源自merge request被提出的黑榜。性能
第一次写技术文章,若是有写的不清楚或者很差的地方,求轻拍~ui
在只有作setState
操做的时候偷懒写函数,喜欢直接在函数里写() => this.setState({state:value})
this
class MyButton extends React.Component {
render(){
return <Button onClick={() => this.setState({ visible: true })}>这是个按钮</Button>
}
}
复制代码
咱们知道,在React
中,render
函数在每次props
或者state
更改的时候会触发,而箭头函数使用时都会有一个返回值,咱们打印一下它:spa
// console下() => this.setState({ visible: true })
ƒ () {
return _this.setState({ visible: true });
}
复制代码
每执行一次render
,都建立一次_this.setState
实例,能够想象,在频繁触发render
时,内存里有多少的实例。code
所以,咱们应该避免直接在render
内使用箭头函数,纵然只有一行代码,也要定义一个函数,箭头函数不是万能的。cdn
class MyButton extends React.Component {
handleButton = () => {
this.setState({ visible: true })
}
render(){
return <Button onClick={this.handleButton}>这是个按钮</Button>
}
}
复制代码
只在组件中定义箭头函数一次,对比上面直接使用箭头函数的console
// console下this.handleButton
ƒ () {
_this.setState({ visible: true });
}
// console下() => this.setState({ visible: true })
ƒ () {
return _this.setState({ visible: true });
}
复制代码
但是,当咱们传参的时候,好像没办法避免使用箭头函数传参耶…好比
class MyButton extends React.Component {
state = {
list: [ { id:1 }, { id:2 } ]
}
handleButton = (e) => {
this.setState({ list: [] })
}
render(){
return
list.map((item) =>
<Button onClick={() => this.handleButton(item.id)}>这是个按钮</Button>)
}
}
复制代码
一切好像又回到了最开始的问题,咱们应该就这样向传参认输吗?
社会主义教会咱们,不能认输,咱们再想(kan)想(kan)问(she)题(fang)。
咱们使用社区上另外一种流行的写法来试试看。
class MyButton extends React.Component {
state = {
list: [ { id:1 }, { id:2 } ]
}
handleButton = (id) => (e) => {
this.setState({ list: [ { id: id+1 }, { id: 2 } ] })
}
render(){
return
list.map((item) =>
<Button onClick={this.handleButton(item.id)}>这是个按钮</Button>)
}
}
复制代码
它的原理是高阶函数,什么是高阶函数,JavaScript的函数其实都指向某个变量。既然变量能够指向函数,函数的参数能接收变量,那么一个函数就能够接收另外一个函数做为参数,这种函数就称之为高阶函数。 咱们能够将它看作:
handleButton = (id) => {
return (e) => {
this.setState({ list: [ { id:id+1 }, { id:2 } ] })
}
}
复制代码
打印下这个函数
// console下this.handleButton(item.id)
ƒ () {
_this.setState({
list: [{ id: id + 1 }, { id: 2 }]
});
}
复制代码
它和this.handleButton
(看起来)同样。
扩展一下,若是在父子组件内使用,高阶函数和普通函数是否有差异呢?
React
官方不推荐箭头函数绑定的缘由里有一句话:若是该回调函数做为 prop 传入子组件时,这些组件可能会进行额外的从新渲染,怎么理解这句话?
咱们来更改一下咱们最初的例子,经过父子组件的方式
class MyButton extends React.Component {
state = {
list: [ { id:1 }, { id:2 } ]
}
handleButton = () => {
this.setState({ list: [ { id:3 }, { id:2 } ] }) // 图快先这么写着 只更改第一个组件的id
}
render(){
return list.map((item) => <Test id={item.id} onClick={this.handleButton} />
}
}
复制代码
Test.js
export default class Test extends React.PureComponent {
render () {
return <p onClick={this.props.onClick}>{this.props.id}</p>
}
}
复制代码
从PureComponent
的原理来讲,当传入的props
不改变时,组件不会从新渲染。
所以,以上代码中,只有第一个组件会进行render函数,而第二个组件因为props
并未改变,因此不会render
。PureComponent
是生效的。
class MyButton extends React.Component {
state = {
list: [ { id:1 }, { id:2 } ]
}
handleButton = (e) => {
this.setState({ list: [ { id:e+1 }, { id:2 } ] }) // 只更改第一个组件的id
}
render(){
return list.map((item) =>
<Test id={item.id} onClick={() => this.handleButton(item.id)} />)
}
}
复制代码
若是咱们使用箭头函数,则会使两个组件都触发从新render
,因为每次箭头函数返回的都是新的实例,每次父组件渲染,传给子组件的 props.onClick
都会变,PureComponent
的 Shallow Compare 基本上就失效了,除非你手动实现 shouldComponentUpdate
.(——@黑猫)。
click
一下第一个组件,获得的结果是,两个组件都进行了render
。
缘由是每次父组件render返回的依旧是新的function实例,这个实例被绑定在了onclick
事件上,PureComponent
不生效,渲染浪费,这个很好理解。
那咱们来试试上文提到的,看似相同的高阶函数写法
class MyButton extends React.Component {
state = {
list: [ { id:1 }, { id:2 } ]
}
handleButton = (e) => () => {
this.setState({ list: [ { id:e+1 }, { id:2 } ] }) // 只更改第一个组件的id
}
render(){
return list.map((item) =>
<Test id={item.id} onClick={this.handleButton(item.id)} />)
}
}
复制代码
结果是,PureComponent
也不生效。表现和直接使用箭头函数传参是同样的。
到此,咱们不由困惑?说好的高阶呢?咱们来推理下。
影响PureComponent
不生效的缘由是props
没有经过浅比较,咱们传入Test
组件的props
只有两个属性,id
属性和onClick
事件,可能的就是传入的onClick
事件没有经过比较。
咱们在父组件层打印一下两种写法:
// 使用高阶函数写法
handleButton = (e) => () => {
this.setState({ list: [ { id:e+1 }, { id:2 } ] })
}
// 普通写法
handleButton2 = () => {
this.setState({ list: [{id: 3}, {id: 2}] })
}
...
this.handleButton(1) === this.handleButton(1) // false
this.handleButton2 === this.handleButton2 // true
复制代码
函数是引用类型,引用类型的比较是引用的比较,由此,咱们能够知道,高阶函数使得函数的引用变化了。引用类型是按引用访问的,换句话说就是比较两个对象的堆内存中的地址是否相同,那很明显,handleButton(1)
和handleButton(1)
在堆内存中地址是不一样的
咱们知道,在React中
直接使用this.myFunction()
是会直接执行函数的,因此当咱们将高阶函数绑定在了render
内,无疑在每次render
时运行了这个函数:
handleButton = (id) => {
console.log('运行了handleButton')
return () => {
this.setState({
list: [{id: id + 2}, {id: 2}]
})
}
}
复制代码
以上是只有一个Test
组件绑定了一个事件时,handleButton
运行了两次,每次返回一个箭头函数的引用,能够想象,当有10个组件,绑定了5个以上不一样的事件(实际在工程中,列表的组件可能更多,老代码里的绑定事件也不止5个),会有多少的引用。
当咱们点击handleButton
事件时,子组件表现为
触发父组件render
,又一次更改了handleButton
的引用,因而以后的全部子组件都从新渲染一遍。
至此,咱们发现父子组件里,高阶函数传参与箭头函数传参基本是同样的。
那么回到故事的开始,不论是page
页面仍是父子组件,只要传参,不论是高阶函数,仍是箭头函数,咱们都没法避免性能上的损耗。
只能作到的是,在不须要传参的事件中不使用箭头函数。须要传参的函数,没法避免箭头函数形成的性能浪费。
怎么感受探索到底,最后的结局让我有点小小的失落呢。
欢迎交流更好的方案。