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
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
相關文章
- ReactJS前端學習-元件生命週期ReactJS前端元件
- 大資料時代之hadoop(三):hadoop資料流(生命週期)大資料Hadoop
- View生命週期與Activity生命週期的關係View
- Django ORM 資料庫生命週期DjangoORM資料庫
- 淺談資料庫生命週期資料庫
- MVN命令與生命週期
- Servlet生命週期與方法Servlet
- Omi教程-生命週期和事件處理事件
- ASP.NET頁面生命週期與應用程式生命週期ASP.NET
- 感知生命週期的資料 -- LiveDataLiveData
- 生命週期
- React元件的狀態及生命週期事件React元件事件
- 12c ILM資料生命週期管理
- 資料科學的原理與技巧 一、資料科學的生命週期資料科學
- Activity生命週期與啟動模式模式
- React的生命週期與應用React
- PHP 生命週期PHP
- Flutter - 生命週期Flutter
- sessionStorag 生命週期Session
- Fragment生命週期Fragment
- Activity生命週期
- vue - 生命週期Vue
- React生命週期React
- ubuntu生命週期Ubuntu
- React 生命週期React
- vue生命週期Vue
- PWA:Service worker生命週期事件對網路資源的處理事件
- Salesforce 生命週期管理(一)應用生命週期淺談Salesforce
- Vue 生命週期與鉤子函式Vue函式
- Activity生命週期onDestroy
- Flutter -- Element生命週期Flutter
- Flutter 的生命週期Flutter
- SQL的生命週期SQL
- java servlet 生命週期JavaServlet
- React-生命週期React
- vue 生命週期梳理Vue
- Laravel的生命週期Laravel
- 理解VUE生命週期Vue