react整理(二)元件之間的傳值
上一節說到如何在react中建立元件,知道了建立,還得知道怎麼使用,這一節來整理react元件之間的傳值。
react的核心思想就是元件化,元件之間UI的展示主要通過值的傳遞。我們可以將元件的定義看成以下的公式:
UI = Component(props, state)
也就是說,元件是根據props,state來定義介面UI的。props是對外的資料,而state是對內的資料。
props與state的區別在於,props資料是不可變的,而state的資料是可變的。
對於props來說,元件裡可以引用別的元件,元件之間的引用會構成一個狀態樹。那麼,父級元件可以通過子元件的props來傳值,但是這種值的傳遞是單向的,子元件不被允許更改父級元件的值,若要改變,只能更改在父級元件中更改。這裡我們暫且說說,父級元件如何傳值給子元件,至於子元件如何主動去更改父級元件,我們在後續詳細說明。
通過一個例子來了解父級元件如何向子元件傳遞資料:
有兩個元件,一個是父級元件,我們叫它index,一個元件,我們叫Children。父級元件index引用子元件Children,可通過子元件的Props來傳值。
import react, {Component} from 'React'; import Children from 'Children'; export default class Index extends Component{ render(){ // 向Children傳入值 const params = { sort: 0, name: 'lily' } return ( <Children {...params}/> // es6解構傳入 ) } }
import react, {Component} from 'React';
export default class Children extends Component{
render(){
// 通過props獲取到父級傳遞的值
const { sort, name } = this.props;
return (
<div>{name}</div>
<div>{sort}</div>
)
}
}
除開外部資料,元件自身也需要一個小倉庫來儲存自身的狀態與資料。而state就是用來記錄、改變元件內部資料狀態。在react裡,只需要更新元件的state,然後根據新的state重新渲染使用者介面(不要操作DOM)
如何定義的簡單例子:
export default class Index extends Component{
// 通過建構函式來初始化state
construct(props){
super(props);
this.state = {
timeout: new Date();
}
}
changeTime(){
// 改變state的值
this.setState({
timeout: new Date();
})
}
render(){
return(
// 獲取timeout的值
<span>this.state.timeout.toLocalString()</span>
<Button OnClick={this.changeTime.bind(this)}>改變timeout</Button>
)
}
}
現在我們知道了怎樣使用state了。那麼接下來我們需要知道,如何定義好一個state。
正確建立一個元件的第一步就是定義合適的state,我們不能將元件中所有的值都通過state來儲存,造成state的濫用,一是影響程式碼結構,二會影響效能(一個setState會經歷四個生命週期)。
react中state的特點:
1.是非同步操作的函式
2.元件在還沒有渲染之前,this.setState還沒有被呼叫。
3.批量執行State轉變時讓DOM渲染更快(對比一個個執行state)
state應該能提現以下方面:
1.state中的資料應該是能體現出元件UI每一次狀態的變化,且是每一個元件UI呈現出來的狀態集。
2.state的資料,不應該通過其他狀態來計算出中間狀態。
所以,在每一次需要在state中定義狀態之前,我們都應該去考慮以下幾點:
1.我們定義的state中的值是否可以通過上層元件拿到,或者說可以從其他state狀態中計算而得,如果是,放棄新增。
2.我們定義的state是否不需要在render方法中執行,如果是,我們可以考慮在元件的全域性定義一個普通屬性。
3.我們定義的state是否在整個生命週期中都保持不變,如果是,放棄新增。
我們知道了如何正確使用,但還需使用時的注意事項。
使用state我們應該注意哪些事項?
1.state是不可以直接修改的,比如說要修改一條資料,使用如下方式:
this.state.time = new Date();
這種情況是絕不允許的,更改state中的狀態必須使用setState()函式。
2.state是非同步執行的。
舉個例子,假設有一個購物車,當點選一次購買按鈕,購買的數量就會加1,如果我們連續點選了兩次按鈕,就會連續呼叫兩次this.setState({quantity: this.state.quantity + 1}),但是在React中,合併多次修改為一次的情況下,相當於等價執行了如下程式碼:
Object.assign(
previousState,
{quantity: this.state.quantity + 1},
{quantity: this.state.quantity + 1}
)
後面的操作覆蓋掉了前面的操作,最終購買的數量只增加了1個。
我們要明白的是,在react中,setState是非同步執行的,會將待改變的狀態新增到佇列中,比較更新,找到一個特定的時機,執行一次setState()方法。我們在每一次呼叫setState()這個函式時,資料並不會立馬就更新,下面來個錯誤示範:
changeState(){
this.setState({ quantity: this.state.quatity});
this.setState({ quantity: this.state.quatity});
this.fetchData(this.state.quatity);
}
在剛剛接觸state時,我們就會像以上那樣寫。這裡存在兩個錯誤,一是setState()這個函式只會執行一次,而是setState()這個是非同步更新的,執行完setState(),作為fetchData的引數並不是立馬就更新了。
如果你真的需要實現以下需求。
(1)下一個資料依賴上一個資料,可以使用另一個接收一個函式作為引數的setState,這個函式有兩個引數,第一個引數是元件的前一個state(本次元件狀態修改成功前的state),第二個引數是元件當前最新的props。
// 錯誤
changeState(){
this.setState({ quantity: this.state.quatity});
this.setState({ quantity: this.state.quatity});
}
// 正確
this.setState((preState, props) => ({
counter: preState.quantity + 1;
}))
(2)方法需要依賴state的值
執行的方法需要使用state更新後的值。可以使用es6的寫法,當然和第一種情況其實是一樣的。將非同步變成同步。
// 錯誤
changeState(){
this.setState({ quantity: this.state.quatity});
this.fetchData(this.state.quatity);
}
// 正確
changeState(){
this.setState({ quantity: this.state.quatity}).then(()=>{
this.fetchData(this.state.quatity);
});
(3)一個地方有多個地方setState如何優化?
我們在一個方法裡,有時間很難去避免要執行多個setState()方法。比如下面這樣:
changeOnTab(num){
switch(num){
case 1:
this.setState({
username: mm,
age: 13,
})
break
case 2:
this.setState({
username: dd,
age: 14,
})
break
case 3:
this.setState({
username: dd,
age: 15,
})
break
}
}
按以上程式碼這樣寫,我們發現會特別冗餘,假設這不是case,那我們需要執行多少次setState方法呢?所以我們可以更改成以下簡便的寫法:
changeOnTab(num){
const state = {
username: dd,
age: 15,
}
switch(num){
case 1:
state.username = mm,
state.age = 14
break
case 2:
state.username = mm,
break
}
this.setState(state);
}
參考文件:
https://juejin.im/post/5ad458c7f265da239c7bd37c
http://www.runoob.com/react/react-state.html