ReactJS 生命周期、數據流與事件


React是一個JavaScript庫文件,使用它的目的在於能夠解決構建大的應用和數據的實時變更。該設計使用JSX允許你在構建標簽結構時充分利用JavaScript的強大能力,而不必在笨拙的模板語言上浪費時間。

1 生命周期

在組件的整個生命周期中,隨着該組件的props或者state發生改變,它的DOM表現也將有相應的變化,一個組件就是一個狀態機:對於特定的輸入,它總會返回一致的輸出。 React為每個組件提供了生命周期鈎子函數去響應不同的時刻,組件的生命周期分為三個部分:(1)實例化;(2)存在期;(3)銷毀&清理期。具體周期如下圖所示:

1.1 實例化

創建在代碼加載過程中至關重要,重要之處體現什么地方呢,這里粗略的簡述幾點,(1)實例化是首次加載js展示給用戶最直觀的內容,效率的高低直接決定體驗的好壞;(2)實例化過程是對數據進行說明和描述的過程。(3)實例化過程完成了虛擬DOM和真實DOM的生成。下面看下示例來展示當前流程:

var React = require("react"); var ReactDOM = require("react-dom"); var List = React.createClass({ //1.創建階段 getDefaultProps:function() { console.log("getDefaultProps"); return {}; }, //2.實例化階段 getInitialState:function() { console.log("getInitialState"); return {}; }, //render之前調用,業務邏輯都應該放在這里,如對state的操作等 componentWillMount:function() { console.log("componentWillMount"); }, //渲染並返回一個虛擬DOM render:function() { console.log("render"); return( <div> hello <strong> {this.props.name} </strong></div> ); }, //該方法發生在render方法之后。在該方法中,ReactJS會使用render生成返回的虛擬DOM對象來創建真實的DOM結構 componentDidMount:function() { console.log("componentDidMount"); }, }); ReactDOM.render(<List name="ReactJS">children</List>, document.body);

輸出結果為:

getDefaultProps
getInitialState
componentWillMount
render
componentDidMount

上面經歷的實例化過程可細分成兩個階段:創建階段和實例化階段

1.1.1創建階段

該階段主要發生在創建組件類的時候,即調用React.createClass的時候。這個階段只會觸發一個getDefaultProps方法,該方法返回一個對象,並且緩存下來。然后與父組件指定的props對象合並,最后賦值給this.props作為該組件的默認屬性。對於那些沒有被父輩組件指定的props屬性的新建實例來說,這個方法返回的對象可用於為實例設置默認的props值。

props屬性又是什么呢,它是一個對象,是組件用來接收外面傳來的參數的,組件內部是不允許修改自己的props屬性的,只能通過父組件來修改。在getDefaultProps方法中,是可以設定props默認值的。

1.1.2實例化階段

該階段主要發生在實例化組件類的時候,也就是該組件類被調用的時候:

ReactDOM.render(<NewView name="ReactJS">children</NewView>, document.body);

調用順序在demo結果中頁

  • getInitialState 初始化組件的state的值,其返回值會賦值給組件的this.state屬性。對於組件的每個實例來說,這個方法的調用次數有且只有一次。與getDefaultProps方法不同的是,每次實例創建時該方法都會被調用一次。
  • componentWillMount 此方法會在完成首次渲染之前被調用。這也是在render方法調用前可以修改組件state的最后一次機會。
  • render 生成頁面需要的虛擬DOM結構,用來表示組件的輸出。render方法需要滿足:(1)只能通過this.props和this.state訪問數據;(2)可以返回null、false或者任何React組件;(3)只能出現一個頂級組件;(4)必需純凈,意味着不能改變組件的狀態或者修改DOM的輸出。
  • componentDidMount 該方法發生在render方法成功調用並且真實的DOM已經被渲染之后,在該函數內部可以通過this.getDOMNode()來獲取當前組件的節點。然后就可以像Web開發中的那樣操作里面的DOM元素了。

上面提到了兩個比較生分的術語——state和虛擬DOM

  • state:是組建的屬性,主要用來存儲組件自身需要的數據。它是可以改變的,它的每次改變都會引起組件的更新,這也是ReactJS中的關鍵點之一。每次數據的更新都是通過修改state屬性的值,然后ReactJS內部會監聽state屬性的變化,一旦發生變化,就會主動出發組件的render方法來更新DOM結構。
  • 虛擬DOM:它是ReactJS中提出的一個概念,是將真實的DOM結構映射成一個JSON數據結構,在有數據更改的時候更新真實的DOM,不需修改的時候不更新真實的DOM。

1.2 存在期

由1.1可知,此時組件已經渲染好並且用戶可以與它進行交互,通常是通過一次鼠標點擊、手指點按或者鍵盤事件來觸發一個事件處理器。隨着用戶改變了組件或者整個應用的state,便會有新的state流入組件結構樹。代碼如下所示:

var React = require("react"); var ReactDOM = require("react-dom"); var NewView = React.createClass({ //1.創建階段 getDefaultProps:function() { console.log("getDefaultProps"); return {}; }, //2.實例化階段 getInitialState:function() { console.log("getInitialState"); return { num:1 }; }, //render之前調用,業務邏輯都應該放在這里,如對state的操作等 componentWillMount:function() { console.log("componentWillMount"); }, //渲染並返回一個虛擬DOM render:function() { console.log("render"); return( <div> hello <strong> {this.props.name} </strong> <button onClick={this.handleAddNumber}> hello <strong> {this.state.num} </strong></button> </div> ); }, //該方法發生在render方法之后。在該方法中,ReactJS會使用render生成返回的虛擬DOM對象來創建真實的DOM結構 componentDidMount:function() { console.log("componentDidMount"); }, //3.更新階段 componentWillReceiveProps:function() { console.log("componentWillReceiveProps"); }, //是否需要更新 shouldComponentUpdate:function() { console.log("shouldComponentUpdate"); return true; }, //將要更新 componentWillUpdate:function() { console.log("componentWillUpdate"); }, //更新完畢 componentDidUpdate:function() { console.log("componentDidUpdate"); }, //4.銷毀階段 componentWillUnmount:function() { console.log("componentWillUnmount"); }, // 處理點擊事件 handleAddNumber:function() { console.log("add num"); this.setState({num:this.state.num+1}); this.setProps({name:"newName"}); } }); ReactDOM.render(<NewView name="ReactJS"></NewView>, document.body);

該段代碼的目的是,經歷第一階段實例化階段,然后提供button按鈕,改變state狀態,也是調用代碼中的handleAddNumber:function()方法,實現第二階段存在期的更新,該階段在state狀態f發生改變,組件逐漸受到影響。

輸出結果為(不包含):

add num
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
  • componentWillReceiveProps 在任意時刻,組件的props都可以通過父輩組件來更改。當組件接收到新的props(這里不同於state)時,會觸發該函數,我們同時也獲得更改props對象及更新state的機會。
  • shouldComponentUpdate 該方法用來攔截新的props和state,然后開發者可以根據自己設定邏輯,做出要不要更新render的決定,讓它更快。
  • componentWillUpdate 與componentWillMount方法類似,組件上會接收到新的props或者state渲染之前,調用該方法。但是不可以在該方法中更新state和props。
  • render 生成頁面需要的虛擬DOM結構,並返回該結構
  • componentDidUpdate 與componentDidMount類似,更新已經渲染好的DOM。

1.3生命周期之銷毀&清理

每當react使用完一個組件,這個組件就必須從DOM中卸載隨后被銷毀。此時,僅有的一個鈎子函數會做出響應,完成所有的清理與銷毀工作,這很必要。

componentWillUnmount

最后,隨着一個組件從它的層級結構中移除,這個組件的生命也就走到了盡頭。該方法會在組件被移出之前調被調用。在componentDidMount方法中添加的所有任務都需要在該方法中撤銷,比如說創建的定時器或者添加的事件監聽等。

1.4 反模式:把計算后的值賦給state

在getInitialState方法中,嘗試通過this.props來創建state的做法是一種反模式。

    getDefaultProps:function() { console.log("getDefaultProps"); return { date:new Date() }; }, getInitialState:function() { console.log("getInitialState"); return { num:this.props.date.getDay() }; },

在getInitialState中使用props的值,同時可能存在props的值沒有初始化完的狀態。導致計算后的值永遠不會與派生出他的props值同步。

    getDefaultProps:function() { console.log("getDefaultProps"); return { date:new Date() }; }, //渲染並返回一個虛擬DOM render:function() { console.log("render"); var day = this.props.date.getMonth; return( <div> hello <strong> Day:{day}</strong> </div> ); }

2 數據流與事件操作

在React中,數據流向是單向的——從父節點傳遞到子節點,因而組件是簡單且易於把握的,他們只需從父節點獲取props渲染即可。如果頂層組件的某個prop改變了,React會遞歸地向下遍歷整棵組建樹,重新渲染所有使用這個屬性的組件。 React組件內部還具有自己的狀態,這些狀態只能在組件內修改。React組件本身很簡單,你可以把他們看成是一個函數,他接受props和state作為參數,返回一個虛擬的DOM表現。

2.1 Props

props是properties的縮寫,你可以使用它把任意類型的數據傳遞給組件。我們先創建一個父組件Parent,它內部調用的是一個叫child的子組件,並將接收到的外部參數name傳遞給子組件child,代碼如下所示:

var React = require("react"); var ReactDOM = require("react-dom"); var Child = React.createClass({ getDefaultProps:function() { return {}; }, getInitialState:function() { return {}; }, //渲染並返回一個虛擬DOM render:function() { return( <button> hello {this.props.name}</button> ); } }); var Parent = React.createClass({ render:function() { return( <div onClick={this.onClick}> <Child name={this.props.name} id="child"> </Child> </div> ); }, onClick:function() { console.log(ReactDOM.findDOMNode(document.body)); } }); ReactDOM.render(<Parent name="123"></Parent>, document.body);

我們在Parent上設置name=”123″,這個name經由Parent傳遞給Child的props中,非常的方便。父組件可以傳遞任何值給到子組件。

2.2 PropTypes

通過在組件中定義一個配置對象,React提供了一種驗證props的方式:

var Child = React.createClass({ propTypes: { viewName:React.propTypes.shape({ id:React.propTypes.number.isRequired }).isRequired, onClick:React.propTypes.func }, ......

組件初始化時,如果傳遞的屬性和propTypes不匹配,則會打印一個console.warn日志,如果是可選的配置,可以去掉.required。 在應用使用中,propTypes並不是強制性的,但這提供了一種極好的方式來描述組件的API。

2.3 State

每一個React組件都可以擁有自己的state,state與props的區別在於前者只存在與組件的內部。並不是組件之間所共享的。state可以用來確定一個元素的視圖狀態。

var Parent = React.createClass({
    getInitialState:function() { return { number:1 }; }, render:function() { return( <div onClick={this.onClick}> <button > hello {this.state.number} </button> </div> ); }, onClick:function() { this.setState({ number:this.state.number+1 }); } }); ReactDOM.render(<Parent></Parent>, document.body);

如上代碼,可以通過點擊事件對state進行修改,調用setState的時候,會調用存在期的周期。也可以使用上面出現的getInitialState方法提供一組默認值,只要setState被調用,render就會被調用。如果render函數返回有變化,虛擬 DOM就會更新,真實的DOM也會被更新,最終用戶會在瀏覽器中看到變化。

注意不要直接修改this.state,永遠記得要通過this.setState方法修改。

3 事件

對於用戶而言,展示只占整體設計因素的一半。另一半則是響應用戶輸入,即通過JavaScript處理用戶產生的事件。 React通過將時間處理器綁定到組件上來處理事件。在事件被觸發的同時,更新組件的內部狀態。組件內部狀態的更新會觸發組件重繪。因此,如果視圖層想要渲染出時間觸發后的結果,它所需要做的就是在渲染函數中讀取組件的內部狀態。

3.1 綁定事件處理器

React處理的事件本質上和原生的JavaScript時間一樣:MouseEvents事件用於點擊處理器,Change事件用於表單元素變化,等等。所有的事件在命名上和原生的JavaScript規范一致且會在相同的場景下被觸發。 React綁定事件處理器的語法和HTML語法類似。比如一個按鈕,功能是添加,寫法如下:

<button className="btn-add" onClick={this.handleAddClicked}>Add</button>

用戶點擊這個按鈕時,組件的handleAddClicked方法會被調用。這個方法中會包含處理Add的行為邏輯。

這里我們可以懷疑onClick哪里來的呢,處理onClick還支持什么事件,這里里出了MouseEvents事件:

onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp

參考 https://facebook.github.io/react/docs/events.html

3.2 事件對象

很多事件處理器只要觸發就會完成,但是有時候也會需要關於用戶輸入的跟多信息,然后有時候我們需要在輸入的時候一直在監聽輸入的內容,及時的提醒給用戶輸入錯誤的提示,提高產品的體驗。

var React = require('react'); var ReactDOM = require("react-dom"); var InputListener = React.createClass({ handleInput:function(event) { console.log(event.target.value); }, render:function() { return( <div className="form-group"> <div className="input-item"> <textarea rows="3" onChange={this.handleInput}/> </div> </div> ); } }); ReactDOM.render(<InputListener className="input">input</InputListener>, document.body);

通常會有一個事件對象傳入到React的時間處理器函數中,類似原生的JavaScript事件監聽器的寫法。這里的handleInput方法會接受一個事件的對象,並通過存取event.target.value互相傳遞事件對象的內容。在事件處理器中,使用event.target.value獲取表單中的input值是一中常規的方法。 編譯腳本參考:

http://my.oschina.net/feiyangxiaomi/blog/640038#OSC_h2_8 第5.3小節:打包程序

4 參考

《React 引領未來的用戶界面開發框架》

https://facebook.github.io/react/docs/transferring-props.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM