ReactJS 生命週期、資料流與事件

小米墨客發表於2016-03-22

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

相關文章