新東家的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
頁面仍是父子組件,只要傳參,不論是高階函數,仍是箭頭函數,咱們都沒法避免性能上的損耗。
只能作到的是,在不須要傳參的事件中不使用箭頭函數。須要傳參的函數,沒法避免箭頭函數形成的性能浪費。
怎麼感受探索到底,最後的結局讓我有點小小的失落呢。
歡迎交流更好的方案。