凡是參閱過react官方英文文檔的童鞋大致上都能知道對於一個組件來講,其state的改變(調用this.setState()方法)以及從父組件接受的props發生變化時,會致使組件重渲染,正所謂"學而不思則罔",在不斷的學習中,我開始思考這一些問題:javascript
import React from 'react' class Test extends React.Component{ constructor(props) { super(props); this.state = { Number:1//設state中Number值爲1 } } //這裏調用了setState可是並無改變setState中的值 handleClick = () => { const preNumber = this.state.Number this.setState({ Number:this.state.Number }) } render(){ //當render函數被調用時,打印當前的Number console.log(this.state.Number) return(<h1 onClick = {this.handleClick} style ={{margin:30}}> {this.state.Number} </h1>) } } export default Test //省略reactDOM的渲染代碼...
import React from 'react' class Test extends React.Component{ constructor(props) { super(props); this.state = { Number:1 } } //這裏調用了setState可是並無改變setState中的值 handleClick = () => { const preNumber = this.state.Number this.setState({ Number:this.state.Number }) } //在render函數調用前判斷:若是先後state中Number不變,經過return false阻止render調用 shouldComponentUpdate(nextProps,nextState){ if(nextState.Number == this.state.Number){ return false } } render(){ //當render函數被調用時,打印當前的Number console.log(this.state.Number) return(<h1 onClick = {this.handleClick} style ={{margin:30}}> {this.state.Number} </h1>) } }
點擊標題1,UI仍然沒有任何變化,但此時控制檯已經沒有任何輸出了,沒有意義的重渲染被咱們阻止了!java
組件的state沒有變化,而且從父組件接受的props也沒有變化,那它就還可能重渲染嗎?——【可能!】react
import React from 'react' class Son extends React.Component{ render(){ const {index,number,handleClick} = this.props //在每次渲染子組件時,打印該子組件的數字內容 console.log(number); return <h1 onClick ={() => handleClick(index)}>{number}</h1> } } class Father extends React.Component{ constructor(props) { super(props); this.state = { numberArray:[0,1,2] } } //點擊後使numberArray中數組下標爲index的數字值加一,重渲染對應的Son組件 handleClick = (index) => { let preNumberArray = this.state.numberArray preNumberArray[index] += 1; this.setState({ numberArray:preNumberArray }) } render(){ return(<div style ={{margin:30}}>{ this.state.numberArray.map( (number,key) => { return <Son key = {key} index = {key} number ={number} handleClick ={this.handleClick}/> } ) } </div>) } } export default Father
在這個例子中,咱們在父組件Father的state對象中設置了一個numberArray的數組,而且將數組元素經過map函數傳遞至三個子組件Son中,做爲其顯示的內容(標題1,2,3),點擊每一個Son組件會更改對應的state中numberArray的數組元素,從而使父組件重渲染,繼而致使子組件重渲染git
demo:(點擊前)es6
點擊1後:github
控制檯輸出:npm
demo如咱們設想,但這裏有一個咱們沒法滿意的問題:輸出的(1,1,2),有咱們從0變到1的數據,也有未發生變化的1和2。這說明Son又作了兩次多餘的重渲染,可是對於1和2來講,它們自己state沒有變化(也沒有設state),同時父組件傳達的props也沒有變化,因此咱們又作了無用功。數組
那怎麼避免這個問題呢?沒錯,關鍵仍是在shouldComponentUpdate這個鉤子函數上函數
import React from 'react' class Son extends React.Component{ shouldComponentUpdate(nextProps,nextState){ if(nextProps.number == this.props.number){ return false } return true } render(){ const {index,number,handleClick} = this.props //在每次渲染子組件時,打印該子組件的數字內容 console.log(number); return <h1 onClick ={() => handleClick(index)}>{number}</h1> } } class Father extends React.Component{ constructor(props) { super(props); this.state = { numberArray:[0,1,2] } } //點擊後使numberArray中數組下標爲index的數字值加一,重渲染對應的Son組件 handleClick = (index) => { let preNumberArray = this.state.numberArray preNumberArray[index] += 1; this.setState({ numberArray:preNumberArray }) } render(){ return(<div style ={{margin:30}}>{ this.state.numberArray.map( (number,key) => { return <Son key = {key} index = {key} number ={number} handleClick ={this.handleClick}/> } ) } </div>) } } export default Father
在這種簡單的情景下,只要利用好shouldComponent一切都很美好,可是當咱們的state中的numberArray變得複雜些的時候就會遇到頗有意思的問題了,讓咱們把numberArray改爲性能
[{number:0 /*對象中其餘的屬性*/}, {number:1 /*對象中其餘的屬性*/}, {number:2 /*對象中其餘的屬性*/} ]
import React from 'react' class Son extends React.Component{ shouldComponentUpdate(nextProps,nextState){ if(nextProps.numberObject.number == this.props.numberObject.number){ return false } return true } render(){ const {index,numberObject,handleClick} = this.props //在每次渲染子組件時,打印該子組件的數字內容 console.log(numberObject.number); return <h1 onClick ={() => handleClick(index)}>{numberObject.number}</h1> } } class Father extends React.Component{ constructor(props) { super(props); this.state = { numberArray:[{number:0 /*對象中其餘的屬性*/}, {number:1 /*對象中其餘的屬性*/}, {number:2 /*對象中其餘的屬性*/} ] } } //點擊後使numberArray中數組下標爲index的數字值加一,重渲染對應的Son組件 handleClick = (index) => { let preNumberArray = this.state.numberArray preNumberArray[index].number += 1; this.setState({ numberArray:preNumberArray }) } render(){ return(<div style ={{margin:30}}>{ this.state.numberArray.map( (numberObject,key) => { return <Son key = {key} index = {key} numberObject ={numberObject} handleClick ={this.handleClick}/> } ) } </div>) } } export default Father
what!!!個人代碼結構明明沒有任何變化啊,只是改傳遞數字爲傳遞對象而已。嗯嗯,問題就出在這裏,咱們傳遞的是對象,關鍵在於nextProps.numberObject.number == this.props.numberObject.number這個判斷條件,讓咱們思考,這與前面成功例子中的nextProps.number == this.props.number的區別:
let obj = object.assign({},{a:1},{b:1})//obj爲{a:1,b:1}
import React from 'react' class Son extends React.Component{ shouldComponentUpdate(nextProps,nextState){ //舊的number Object對象的number屬性 == 新的number Object對象的number屬性 if(nextProps.numberObject.number == this.props.numberObject.number){ console.log('前一個對象' + JSON.stringify(nextProps.numberObject)+ '後一個對象' + JSON.stringify(this.props.numberObject)); return false } return true } render(){ const {index,numberObject,handleClick} = this.props //在每次渲染子組件時,打印該子組件的數字內容 console.log(numberObject.number); return <h1 onClick ={() => handleClick(index)}>{numberObject.number}</h1> } } class Father extends React.Component{ constructor(props) { super(props); this.state = { numberArray:[{number:0 /*對象中其餘的屬性*/}, {number:1 /*對象中其餘的屬性*/}, {number:2 /*對象中其餘的屬性*/} ] } } //點擊後使numberArray中數組下標爲index的數字值加一,重渲染對應的Son組件 handleClick = (index) => { let preNumberArray = this.state.numberArray //把作修改的number Object先拷貝到一個新的對象中,替換原來的對象 preNumberArray[index] = Object.assign({},preNumberArray[index]) //使新的number Object對象的number屬性加一,舊的number Object對象屬性不變 preNumberArray[index].number += 1; this.setState({numberArray:preNumberArray}) } render(){ return(<div style ={{margin:30}}>{ this.state.numberArray.map( (numberObject,key) => { return <Son key = {key} index = {key} numberObject ={numberObject} handleClick ={this.handleClick}/> } ) } </div>) } } export default Father
點擊0後打印1,問題解決!
2深拷貝/淺拷貝或利用JSON.parse(JSON.stringify(data))
let a =2,b; b = a;//將a的值賦給b a = 1;//改變a的值 console.log('a =' + a);//輸出 a = 1 console.log('b =' + b);//輸出 b = 2,代表賦值後b,a毫無關聯
let obj1 ={name:'李達康'},obj2; obj2 = obj1;//將obj1的地址賦給obj2 obj1.name = '祁同偉';//改變obj1的name屬性值 console.log('obj1.name =' + obj1.name);//輸出 obj1.name = '祁同偉' console.log('obj2.name =' + obj2.name);//輸出 obj2.name = '祁同偉',代表賦值後obj1/obj2造成耦合關係,二者互相影響
const { fromJS } = require('immutable') let obj1 = fromJS({name:'李達康'}),obj2; obj2 = obj1;//obj2取得與obj1相同的值,但兩個引用指向不一樣的對象 obj2 = obj2.set('name','祁同偉');//設置obj2的name屬性值爲祁同偉 console.log('obj1.name =' + obj1.get('name'));//obj1.name =李達康 console.log('obj2.name =' + obj2.get('name'));//obj2.name =祁同偉
【注意】
1這個時候obj1=obj2並不會使二者指向同一個堆內存中的對象了!因此這成功繞過了咱們前面的所提到的對象賦值表達式所帶來的坑。因此咱們能夠爲所欲爲地像使用普通基本類型變量複製 (a=b)那樣對對象等引用類型賦值(obj1 = obj2)而不用拷貝新對象
2對於immutable對象,你不能再用obj.屬性名那樣取值了,你必須使用immuutable提供的API
1優勢:深拷貝/淺拷貝自己是很耗內存,而immutable自己有一套機制使內存消耗降到最低
2缺點:你多了一整套的API去學習,而且immutable提供的set,map等對象容易與ES6新增的set,map對象弄混
import React from 'react' const { fromJS } = require('immutable') class Son extends React.Component{ shouldComponentUpdate(nextProps,nextState){ //舊的number Object對象的number屬性 == 新的number Object對象的number屬性 if(nextProps.numberObject.get('number') == this.props.numberObject.get('number')){ return false } return true } render(){ const {index,numberObject,handleClick} = this.props console.log(numberObject.get('number')); //在每次渲染子組件時,打印該子組件的數字內容 return <h1 onClick ={() => handleClick(index)}>{numberObject.get('number')}</h1> } } class Father extends React.Component{ constructor(props) { super(props); this.state = { numberArray:fromJS([{number:0 /*對象中其餘的屬性*/}, {number:1 /*對象中其餘的屬性*/}, {number:2 /*對象中其餘的屬性*/} ]) } } //點擊後使numberArray中數組下標爲index的數字值加一,重渲染對應的Son組件 handleClick = (index) => { let preNumberArray = this.state.numberArray //使新的number Object對象的number屬性加一,舊的number Object對象屬性不變 let newNumber = preNumberArray.get(index).get('number') + 1; preNumberArray = preNumberArray.set(index,fromJS({number: newNumber})); this.setState({numberArray:preNumberArray}) } render(){ return(<div style ={{margin:30}}>{ this.state.numberArray.map( (numberObject,key) => { return <Son key = {key} index = {key} numberObject ={numberObject} handleClick ={this.handleClick}/> } ) } </div>) } } export default Father
成功,demo效果同上
這篇文章實在太過冗長,不過既然您已經看到這裏了,那麼我就介紹解決上述問題的一種簡單粗暴的方法——
4繼承react的PureComponent組件
若是你只是單純地想要避免state和props不變下的冗餘的重渲染,那麼react的pureComponent能夠很是方便地實現這一點:
import React, { PureComponent } from 'react' class YouComponent extends PureComponent { render() { // ... } }
固然了,它並非萬能的,因爲選擇性得忽略了shouldComponentUpdate()這一鉤子函數,它並不能像shouldComponentUpdate()「私人定製」那般爲所欲爲
具體代碼就不放了
【完】--喜歡這篇文章的話不妨關注一下我喲