想要理解數據是如何在組件樹內自上往下流動(Data Flow
),須要先明白React
組件的組合、嵌套和組件樹的構成。html
注意:自定義的組件都必需要用大寫字母開頭,普通的
HTML
標籤都用小寫字母開頭。react
下面是用React.js
組建的房子爲例子來講明一下組件的組和應用。一個房子裏面有一個房間和一個廁所。房間裏有一臺電腦和兩條狗,以下:git
// 電腦
class Computer extends Component { render () { return <h3>this is Computer</h3> } } // 狗 class Dog extends Component { render () { return <h3>this is Dog</h3> } } // 房間 class Room extends Component { render () { return ( <div> <h2>this is Room</h2> <Computer /> <Dog /> <Dog /> </div> ) } } // 廁所 class WC extends Component { render () { return <h2>this is WC</h2> } } // 房子 class House extends Component { render () { return <div> <h1>this is House</h1> <Room /> <WC /> </div> } } 複製代碼
最後頁面會輸出以下圖:github
組件能夠和組件組合在一塊兒,組件內部可使用別的組件。這樣的組合嵌套,最後構成一個所謂的組件樹。用下圖表示這種樹狀結構它們之間的關係:web
state
state
是組件的當前狀態,React
根據狀態state
呈現不一樣的UI
展現。React.js
的state
就是用來存儲這種可變化的狀態的。一旦狀態(數據)更改,組件就會自動調用render
從新渲染 UI
,這個更改的動做經過this.setState()
方法來觸發。數組
下面是一個簡單的修改狀態的小例子:異步
class Like extends Component {
constructor(props) { super(props) this.state = { isLike: false } } handleClick () { this.setState({ isLike: !this.state.isLike }) } render () { return ( <h3 onClick={this.handleClick.bind(this)}> 你{this.state.isLike ? '喜歡' : '不喜歡'}她 </h3> ) } } 複製代碼
isLike
存放在實例的state
對象當中,這個對象在構造函數裏面初始化。在這個組件的render
函數內,會根據組件的state
的中的isLike
的不一樣顯示喜歡或不喜歡。setState()
方法由父類Component
所提供。編輯器
state
和setState()
方法setState()
方法,它接受一個對象或者函數做爲參數。函數
handleClick () {
// 只須要傳入須要更新的部分,而不須要入整個對象。 this.setState({ isLike: !this.state.isLike }) } 複製代碼
handleClick () {
this.setState(() => { return { isLike: !this.state.isLike } }) } 複製代碼
state
不能直接修改state
,若是直接修改state
,組件並不會從新觸發render
方法。性能
// 錯誤
this.state.isLike = !this.state.isLike // 正確 this.setState({ isLike: !this.state.isLike }) 複製代碼
state
的更新是異步的在調用setState()
的時候React.js
並不會立刻修改state
。setState()
只是把要修改的狀態放入一個更新隊列中。React會優化真正的執行時機。以下:
class AddCount extends Component {
constructor(props) { super(props) this.state = { count: 0 } } handleClick () { this.setState({ count: this.state.count + 1 }) // 0 console.log(this.state.count) } render () { return ( <div> {/* 1 */} <h2>{this.state.count}</h2> <button onClick={this.handleClick.bind(this)}>添加</button> </div> ) } } 複製代碼
當點擊添加按鈕的時候頁面輸出的是1
,打印出來的倒是0
。因此在調用setState()
以後,this.state
不會當即映射爲新的值。
解決方案是setState(updater, [callback])
接收更新後能夠傳入一個回調函數,一旦setState()
完成而且組件重繪以後,這個回調函數將會被調用。
handleClick () {
this.setState({ count: this.state.count + 1 }, () => { // 1 console.log(this.state.count) }) } 複製代碼
setState()
淺合併React.js
出於性能緣由,可能會將屢次setState()
的狀態修改合併成一次狀態修改。因此不要依賴當前的setState()
計算下個State
。以下的一個計數器:
class AddCount extends Component {
constructor(props) { super(props) this.state = { count: 0 } } handleClick () { this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) } render () { return ( <div> <h2>{this.state.count}</h2> <button onClick={this.handleClick.bind(this)}>添加</button> </div> ) } } 複製代碼
當點擊添加按鈕的時候,會發現頁面輸出的是1
。雖然咱們setState()
方法調用了兩次。是由於當咱們調用setState()
修改組件狀態時,組件state
的更新實際上是一個淺合併的過程,至關於:
Object.assign(
previousState, {count: state.count + 1}, {count: state.count + 1}, ... ) 複製代碼
因此若是後續操做要依賴前一個setState()
的結果的狀況下就要使用函數來做爲setState()
參數。React.js
會把上一個setState()
的結果傳入這個函數,你就可使用上一個正確的結果進行操做,而後返回一個對象來更新state。
handleClick () {
this.setState({ count: this.state.count + 1 }) this.setState((prevState) => { // 1 console.log(prevState.count) return { count: prevState.count + 1 } }) }} 複製代碼
把上次操做setState()
更新的值傳入到下一個setState()
裏,就能夠正確的顯示count
了。
state
的Immutable
(不可變性)React
官方建議把state
看成是的Immutable
(不可變性)對象,state
中包含的全部狀態都應該是不可變對象。當state
中的某個狀態發生變化,咱們應該從新建立這個狀態對象,而不是直接修改原來的狀態。
關於
Immutable
能夠參考Immutable詳解及React中實踐。
names
,當向
name
中添加一個名字時,使用數組的
concat
方法或
ES6
的擴展運算符:
// 1
this.setState(prevState => ({ names: prevState.names.concat(['lisi']) })) // 2 this.setState(prevState => ({ names: [...prevState.names,'lisi'] })) 複製代碼
注意不要使用
push
、pop
、shift
、unshift
、splice
等方法修改數組類型的狀態,由於這些方法都是在原數組的基礎上修改,而concat
、slice
等返回一個新的數組。
person
,爲了避免改變本來的對象,咱們可使用
Object.assign
方法或者對象擴展屬性:
// 1
function updatePerson (person) { return Object.assign({}, person, { age: 20 }) } // 2 function updatePerson (person) { return {...person,age:20} } 複製代碼
建立新的狀態對象要避免使用會直接修改原對象的方法,而是使用能夠返回一個新對象的方法。
當處理深層嵌套對象時,以immutable
(不可變)的方式更新會很麻煩。可使用一些Immutable
的JS庫,如Immutable.js
來簡化開發。
props
組件是相互獨立、可複用的單元,一個組件可能在不一樣地方被用到。可是在不一樣的場景下對這個組件的需求可能會根據狀況有所不一樣,因此要針對相同的組件傳入不一樣的配置項。
React.js
的props
就能夠幫助咱們達到這個效果。每一個組件均可以接受一個props
參數,它是一個對象,包含了全部你對這個組件的配置。
下面是一個能夠經過傳入props
來控制是否是能夠點擊修改喜歡或不喜歡的文字:
class Like extends Component {
constructor(props) { super(props) this.state = { isLike: false } } handleClick () { this.setState({ isLike: !this.state.isLike }) } render () { const clickable = this.props.clickable return ( <h3 onClick={clickable ? this.handleClick.bind(this) : () => { }} > 你{ this.state.isLike ? '喜歡' : '不喜歡'}她 </h3> ) } } class App extends Component { render () { return ( <div> <Like clickable={true} /> <Like clickable={false} /> </div> ) } } 複製代碼
組件內部是經過this.props
的方式獲取到組件的參數的,若是this.props
裏面有須要的屬性咱們就採用相應的屬性,沒有的話就用默認的屬性。在使用一個組件的時候,能夠把參數放在標籤的屬性當中,全部的屬性都會做爲props`對象的鍵值。
defaultProps
React.js
也提供了一種方式defaultProps
,能夠方便的作到默認配置。
class Like extends Component {
static defaultProps = { clickable: true } //... } 複製代碼
props
一旦傳入進來就不能夠在組件內部對它進行修改。可是能夠經過父組件主動從新渲染的方式來傳入新的props
,從而達到更新的效果。以下:
class App extends Component {
constructor(props) { super(props) this.state = { clickable: false } } handleClickOnChange () { this.setState({ clickable: !this.state.clickable }) } render () { return ( <div> <Like clickable={this.state.clickable} /> <Like clickable={this.state.clickable} /> <button onClick={this.handleClickOnChange.bind(this)}> 修改clickable </button> </div> ) } 複製代碼
https://zh-hans.reactjs.org/
https://juejin.im/entry/59522bdb6fb9a06b9a516113
https://github.com/camsong/blog/issues/3
https://github.com/lcxfs1991/blog/issues/8
http://huziketang.mangojuice.top/books/react/
https://dev.to/rleija_/master-the-art-of-react-state-and-props-in-5-minutes-3hao
http://product.dangdang.com/25249546.html
本文使用 mdnice 排版