看到一篇無比蛋疼的文章:https://www.oschina.net/translate/exploring-reacts-state-propagation。我不針對翻譯,只是針對這篇文章想提出的概念,說了跟沒說一樣,傻傻的。
要搞清楚React.js的props和state,必須要結合組件(組件類)從初始化到裝載(mount)的過程來說。
組件類
假定我們通過以下的代碼,創建了一個組件:
var SimpleComponent = React.createClass({
getDefaultProps: function() {
return {
name: ''
};
},
getInitialState: function() {
return {
};
},
render: function() {
return <div>A simple component named {this.props.name}</div>;
}
});
這裏SimpleComponent是一個組件類。
組件虛擬對象
當我們要使用這個組件類的時候,可以寫這樣的代碼:
<SimpleComponent name="abc"/>
實際上上述代碼會轉移成下面的js代碼:
React.createElement(SimpleComponent, null)
通過React.createElement函數創建的,並不是SimpleComponent的組件類實例(即不是new SimpleComponent),而是一個不可寫入的靜態對象(Object),並且這個靜態對象,具有props,沒錯,就是SimpleComponent組件類定義的getDefaultProps所返回的值。
我將這個靜態對象稱爲組件虛擬對象,他持有了SimpleComponent的初始化所需的props,並且我有理由懷疑他是全局唯一的(當然我沒精力去驗證了)。
所以,要知道,當我們在jsx文件中,寫了大量看似html標籤的結構,他實際上並沒有逐個創建對應的組件實例,取而代之的,僅僅是構造一大堆組件虛擬對象(靜態Object)和傳入參數而已。
注意,這個時候組件虛擬對象是沒有state的。
組件實體(實例)
當一個組件虛擬對象被裝載(mount)後——這裏所謂裝載,就是將一個或一大段組件虛擬對象插入到實際的DOM節點中時,纔會創建出真實的組件實體(實例)。
創建組件的實體的時候,纔會真正將調用參數傳入(比如上面的name=abc),並且混入組件虛擬對象上的props。
當組件被實體化後,才真正的擁有state。
而我們實際在編寫SimpleComponent這個組件類的方法時,都是假定針對的是組件實體的狀態下進行的。
最後,總結爲一個流程圖來說明的話:
props只讀、不可變?
我只能說,這是一個誤區。首先,假定你擁有React Development tools的話,通過修改React.js的組件props,也能促使組件發生變化。就好像這樣:
這個問題,仔細想想就能明白是別人給你挖了個坑(不是React.js,而是這些所謂的教程)。假定我們設計兩個組件:
var AComponent = React.createClass({
getDefaultProps: function() {
return {
name: ''
};
},
getInitialState: function() {
return {
name: this.props.name
};
},
render: function() {
return <div>I'm {this.props.name}</div>;
}
});
var BComponent = React.createClass({
getDefaultProps: function() {
return {
name: ''
};
},
getInitialState: function() {
return {
name: this.props.name
};
},
render: function() {
return <div>
<AComponent name={this.state.name} />
<button type="button" onClick={e => this.setState({name: 'Jack'})}>change name</button>
</div>;
}
});
在B組件內,是使用state.name作爲A組件的props.name傳入的,如果是隻讀、不可變的話,那麼B組件的按鈕被點擊後,B組件的state.name改變了,永遠也不可能觸發到A組件state更新了。
所以,嚴格來說,props不可變,只在組件虛擬對象這個階段,因爲那是一個不可寫入的靜態對象。
更進一步,從組件類到組件實例,實際上是完成了從props將數據傳遞給state。這是一個必然性的數據流向,無論多少次組件嵌套,這種數據流向都不會改變。所以一般而言,很少會去動(注意這裏只是說動,但沒有說不去讀)一個組件實例的props,而直接操作state。
傳統的OOP編程,並沒有嚴格的數據流向,到底是屬性到方法,還是方法到屬性,還是屬性到屬性,完全由開發者自己掌控,你愛怎麼寫,就怎麼寫。這在前端組件的源碼中,形成了很大的僞裝性,當發生錯誤的時候,你完全不知道該從哪裏開始着手,需要花一定的時間去解讀、調試,然後才能定位問題(當然有好的規範可以相對的緩解類似的問題)。
而React.js則提供一種簡單明確的機制(而且他只提供了這個機制而已),這也是React.js開發過程中會形成的一個思維定式。每當你開始編寫一個組件,你就需要設計,這個組件應該具有哪些動態(交互性)特徵需要放入state,而這些state又是怎麼從props傳遞過去的。僅此而已,剩下的就是組件的界面邏輯開發而已。
state - 狀態
這個應該不用多介紹了吧,React.js的賣點,組件實例的界面和自身的state動態結合,而且性能還相當的好。
不過其實這裏也有一個小坑,就是當你在設計一個組件的時候,應該認真的思考哪些特徵屬於動態的。如果你在state裏裝載了大量的數據,要監控這些state的更新,還是相當的時間(其實真正很大的數據,大概會有3-5ms的延遲,但這個數據真的很大了)。所以,特別注意:
setState方法是異步的!!!
setState方法是異步的!!!
setState方法是異步的!!!
重要的事情說三次,是的,所以如果你這樣寫,可能會導致你的界面發生不可預料的後果:
this.setState(bigData);
// ...乾點別的事情
正確的寫法應該這樣:
this.setState(bigData, function() {
// ...乾點別的事情
});
當你使用React.js開發的時間長了的話,慢慢的也會調整自己的思維方式,更加集中的設計state。這個下面還會提到。
es6 or typescript
如果可能的話,如果改用es6或者ts,上面的代碼,將會簡單很多很多:
class SimpleComponentES6 extends React.Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name || ''
}
}
render() {
return <div>A simple component named {this.props.name}</div>;
}
}
代碼是不是頓時清晰了很多?
ts現在已經邁進2.0時代了,各方面都比過去完善了很多。假如你的項目,超過1000行JS,最好能換成ts,因爲ts的語言特性,會讓你的代碼的自動提示得更加明確,而不像js或者es6,有的沒的相似的不是的方法、屬性提示你一大堆。