我從merge request得到了什麼之箭頭函數

故事的開頭

新東家的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並未改變,因此不會renderPureComponent是生效的。

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頁面仍是父子組件,只要傳參,不論是高階函數,仍是箭頭函數,咱們都沒法避免性能上的損耗。

只能作到的是,在不須要傳參的事件中不使用箭頭函數。須要傳參的函數,沒法避免箭頭函數形成的性能浪費。

怎麼感受探索到底,最後的結局讓我有點小小的失落呢。

歡迎交流更好的方案。

相關文章
相關標籤/搜索