React.js 組件的 props vs state 原 薦

看到一篇無比蛋疼的文章: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,有的沒的相似的不是的方法、屬性提示你一大堆。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章