凡是參閱過react官方英文文檔的童鞋大體上都能知道對於一個組件來說,其state的改變(調用this.setState()方法)以及從父組件接受的props發生變化時,會導致組件重渲染,正所謂"學而不思則罔",在不斷的學習中,我開始思考這一些問題:
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仍然沒有任何變化,但此時控制台已經沒有任何輸出了,沒有意義的重渲染被我們阻止了!

組件的state沒有變化,並且從父組件接受的props也沒有變化,那它就還可能重渲染嗎?——【可能!】
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的數組元素,從而使父組件重渲染,繼而導致子組件重渲染
demo:(點擊前)

點擊1后:

控制台輸出:

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
- fromJS(obj)把傳入的obj封裝成immutable對象,在賦值給新對象時傳遞的只有本身的值而不是指向內存的地址。
- obj.set(屬性名,屬性值)給obj增加或修改屬性,但obj本身並不變化,只返回修改后的對象
- obj.get(屬性名)從immutable對象中取得屬性值
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()“私人定制”那般隨心所欲
具體代碼就不放了

