React.js的狀態(tài)和生命周期(二)

在上一篇文章:React.js的基礎知識及一些demo(一)中牺弄,我們介紹了React.js的元素板惑、JSX語法邓夕、組件和屬性等相關基礎語法及一些簡單demo掰茶。這篇文章我們繼續(xù)往下了解React的語法萤厅。

狀態(tài)和生命周期

在上一篇文章更新已渲染的元素一節(jié)中橄抹,有一個時鐘的例子。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

在時鐘例子中祈坠,我們通過調(diào) ReactDOM.render() 方法來更新渲染的輸出害碾,這是一種更新UI的方式。
接下來我們將時鐘功能封裝成一個組件

 function Clock(props) {
      return (
        <div>
          <h1>Hello,world!</h1>
          <h2>It is {props.date.toLocaleTimeString()}</h2>
        </div>
      );
    }
 function tick() {
      ReactDOM.render(
        <Clock date={new Date()}/>,
        document.getElementById('root')
      );
 }
setInterval(tick,1000);

然而赦拘,它沒有滿足一個關鍵的要求:Clock 設置定時器并每秒更新 UI 慌随,事實上應該是 Clock 自身實現(xiàn)的一部分。

理想情況下躺同,我們應該只引用一個 Clock , 然后讓它自動計時并更新:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

要實現(xiàn)這點阁猜,我們需要添加 state 到 Clock 組件。state 和 props 類似蹋艺,但是它是私有的剃袍,并且由組件本身完全控制。
在上一篇文章中提到捎谨,組件有兩種定義方式:類組件和函數(shù)組件民效。用類定義的組件有一些額外的特性憔维。 這個”類專有的特性”, 指的就是局部狀態(tài)畏邢。

如何將函數(shù)式組件轉換為類組件

在上一小節(jié)中业扒,我們定義的Clock組件屬于函數(shù)式組件,我們以Clock為例舒萎,介紹函數(shù)組件轉換為類組件程储。

  1. 創(chuàng)建一個繼承自 React.Component 類的 ES6 class 同名類。
  2. 添加一個名為 render() 的空方法臂寝。
  3. 把原函數(shù)中的所有內(nèi)容移至 render() 中章鲤。
  4. render() 方法中使用 this.props 替代 props
  5. 刪除保留的空函數(shù)聲明咆贬。
class Clock extends React.Component{
      render(){
        return (
          <div>
            <h1>Hello,world!</h1>
            <h2>It is {this.props.date.toLocaleTimeString()}</h2>
          </div>
        );
      }
 }

Clock 現(xiàn)在被定為類組件败徊,而不是函數(shù)式組件。類允許我們在其中添加本地狀態(tài)(state)和生命周期鉤子掏缎。

在類組件中添加本地狀態(tài)(state)

我們現(xiàn)在通過以下3步, 把date從屬性(props) 改為 狀態(tài)(state):
1.替換 render() 方法中的 this.props.date 為 this.state.date集嵌;
2.添加一個 類構造函數(shù)(class constructor) 初始化 this.state
3.移除 <Clock /> 元素中的 date 屬性御毅;
結果如下所示:

class Clock extends React.Component{
      constructor(props){
        super(props);//調(diào)用父類的constructor(props)
        this.state = {date:new Date()};
      }
      render(){
        return (
          <div>
            <h1>Hello,world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}</h2>
          </div>
        );
      }
    }
    ReactDOM.render(
      <Clock />,
      document.getElementById('root')
    );

這里:super關鍵字代表父類的實例(即父類的this對象)根欧。
注意我們?nèi)绾螌?props 傳遞給基礎構造函數(shù),類組件應始終使用 props 調(diào)用基礎構造函數(shù)端蛆。
接下來凤粗,我們將使 Clock 設置自己的計時器,并每秒更新一次今豆。

在類中添加生命周期方法

在一個具有許多組件的應用程序中嫌拣,在組件被銷毀時釋放所占用的資源是非常重要的。

Clock 第一次渲染到DOM時呆躲,我們要設置一個定時器 异逐。 這在 React 中稱為 “掛載(mounting)” 。

Clock 產(chǎn)生的 DOM 被銷毀時插掂,我們也想清除該計時器灰瞻。 這在 React 中稱為 “卸載(unmounting)” 。

當組件掛載和卸載時辅甥,我們可以在組件類上聲明特殊的方法來運行一些代碼酝润,這些方法稱為 “生命周期鉤子”。
1.componentDidMount() 鉤子在組件輸出被渲染到 DOM 之后運行璃弄。這是設置時鐘的合適位置要销;

  • 注意我們把計時器ID直接存在 this 中。
  • this.props 由 React 本身設定, 而 this.state 具有特殊的含義夏块,但如果需要存儲一些不用于視覺輸出的內(nèi)容疏咐,則可以手動向類中添加額外的字段纤掸。
  • 如果在 render() 方法中沒有被引用, 它不應該出現(xiàn)在 state 中。

2.我們在componentWillUnmount()生命周期鉤子中取消這個計時器浑塞;

 componentDidMount(){
      this.timerID = setInterval(() => this.tick(),1000);
}

 componentWillUnmount(){
       clearInterval(this.timerID);
}

最后茁肠,我們將會實現(xiàn)每秒運行的 tick() 方法。它將使用 this.setState() 來周期性地更新組件本地狀態(tài)缩举。
最后完整代碼如下所示:

 class Clock extends React.Component{
      constructor(props){
        super(props);
        this.state = {date:new Date()};
      }

      componentDidMount(){
         this.timerID = setInterval(() => this.tick(),1000);
      }

      componentWillUnmount(){
         clearInterval(this.timerID);
      }

      tick(){
        this.setState({
          date:new Date()
        });
      }

      render(){
        return (
          <div>
            <h1>Hello,world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}</h2>
          </div>
        );
      }
    }
    ReactDOM.render(
      <Clock />,
      document.getElementById('root')
    );

關于這個例子的總結

我們來快速回顧一下該過程,以及調(diào)用方法的順序:

1.當 <Clock /> 被傳入 ReactDOM.render() 時, React 會調(diào)用 Clock組件的構造函數(shù)匹颤。 因為 Clock 要顯示的是當前時間仅孩,所以它將使用包含當前時間的對象來初始化 this.state 。我們稍后會更新此狀態(tài)印蓖。

2.然后 React 調(diào)用了 Clock 組件的 render() 方法辽慕。 React 從該方法返回內(nèi)容中得到要顯示在屏幕上的內(nèi)容。然后赦肃,React 更新 DOM 以匹配 Clock 的渲染輸出溅蛉。

3.當 Clock 輸出被插入到 DOM 中時,React 調(diào)用 componentDidMount() 生命周期鉤子他宛。在該方法中船侧,Clock 組件請求瀏覽器設置一個定時器來一次調(diào)用 tick()。

4.瀏覽器會每隔一秒調(diào)用一次 tick()方法厅各。在該方法中镜撩, Clock 組件通過 setState() 方法并傳遞一個包含當前時間的對象來安排一個 UI 的更新。通過 setState(), React 得知了組件 state(狀態(tài))的變化, 隨即再次調(diào)用 render() 方法队塘,獲取了當前應該顯示的內(nèi)容袁梗。 這次,render() 方法中的 this.state.date 的值已經(jīng)發(fā)生了改變憔古, 從而遮怜,其輸出的內(nèi)容也隨之改變锯梁。React 于是據(jù)此對 DOM 進行更新。

5.如果通過其他操作將 Clock 組件從 DOM 中移除了, React 會調(diào)用 componentWillUnmount() 生命周期鉤子, 所以計時器也會被停止。

使用 State(狀態(tài))的一些注意點

關于 setState() 有三件事是你應該知道的壁却。
1.不要直接修改 state(狀態(tài))
例如,這樣將不會重新渲染一個組件:

// 錯誤
this.state.comment = 'Hello';

應該使用 setState() 代替:

// 正確
this.setState({comment: 'Hello'});

唯一可以分配 this.state 的地方是構造函數(shù)盐肃。
2.state(狀態(tài)) 更新可能是異步的
React 為了優(yōu)化性能砸王,有可能會將多個 setState() 調(diào)用合并為一次更新榔昔。
因為 this.props 和 this.state 可能是異步更新的嘹朗,你不能依賴他們的值計算下一個state(狀態(tài))。
例如, 以下代碼可能導致 counter(計數(shù)器)更新失敱拱:

// 錯誤
this.setState({
  counter: this.state.counter + this.props.increment,
});

要解決這個問題许蓖,應該使用另一種 setState() 的形式自阱,它接受一個函數(shù)而不是一個對象。這個函數(shù)將接收前一個狀態(tài)作為第一個參數(shù)叫确,應用更新時的 props 作為第二個參數(shù):

// 正確
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

3.state(狀態(tài))更新會被合并
當你調(diào)用 setState(), React 將合并你提供的對象到當前的狀態(tài)中。
例如,你的狀態(tài)可能包含幾個獨立的變量:

constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
}

然后通過調(diào)用獨立的 setState() 調(diào)用分別更新它們:

componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

注意:合并是淺合并主卫,所以 this.setState({comments}) 不會改變 this.state.posts 的值,但會完全替換this.state.comments 的值。

數(shù)據(jù)向下流動

無論作為父組件還是子組件,它都無法獲悉一個組件是否有狀態(tài)备典,同時也不需要關心另一個組件是定義為函數(shù)組件還是類組件荤崇。

這就是 state(狀態(tài)) 經(jīng)常被稱為 本地狀態(tài) 或 封裝狀態(tài)的原因。 它不能被擁有并設置它的組件 以外的任何組件訪問雳攘。

一個組件可以選擇將 state(狀態(tài)) 向下傳遞喧兄,作為其子組件的 props(屬性):

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
<FormattedDate date={this.state.date} />

FormattedDate 組件通過 props(屬性) 接收了 date 的值恭理,但它仍然不能獲知該值是來自于 Clock的 state(狀態(tài)) 拯辙,還是 Clock 的 props(屬性),或者是直接手動創(chuàng)建的:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

這通常稱為一個“從上到下”颜价,或者“單向”的數(shù)據(jù)流涯保。任何 state(狀態(tài)) 始終由某個特定組件所有,并且從該 state(狀態(tài)) 導出的任何數(shù)據(jù) 或 UI 只能影響樹中 “下方” 的組件周伦。

如果把組件樹想像為 props(屬性) 的瀑布夕春,所有組件的 state(狀態(tài)) 就如同一個額外的水源匯入主流,且只能隨著主流的方向向下流動专挪。

要證明所有組件都是完全獨立的及志, 我們可以創(chuàng)建一個 App 組件,并在其中渲染 3 個 <Clocks>:

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

每個 Clock 都設置它自己的計時器并獨立更新寨腔。

在 React 應用中速侈,一個組件是否是有狀態(tài)或者無狀態(tài)的,被認為是組件的一個實現(xiàn)細節(jié)迫卢,隨著時間推移可能發(fā)生改變锌畸。你可以在有狀態(tài)的組件中使用無狀態(tài)組件,反之亦然靖避。

參考:

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末潭枣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子幻捏,更是在濱河造成了極大的恐慌盆犁,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篡九,死亡現(xiàn)場離奇詭異谐岁,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門伊佃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窜司,“玉大人,你說我怎么就攤上這事航揉∪恚” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵帅涂,是天一觀的道長议薪。 經(jīng)常有香客問我,道長媳友,這世上最難降的妖魔是什么斯议? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮醇锚,結果婚禮上哼御,老公的妹妹穿的比我還像新娘。我一直安慰自己焊唬,他們只是感情好恋昼,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著求晶,像睡著了一般焰雕。 火紅的嫁衣襯著肌膚如雪衷笋。 梳的紋絲不亂的頭發(fā)上芳杏,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音辟宗,去河邊找鬼爵赵。 笑死,一個胖子當著我的面吹牛泊脐,可吹牛的內(nèi)容都是我干的空幻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼容客,長吁一口氣:“原來是場噩夢啊……” “哼秕铛!你這毒婦竟也來了?” 一聲冷哼從身側響起缩挑,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤但两,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后供置,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谨湘,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了紧阔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坊罢。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖擅耽,靈堂內(nèi)的尸體忽然破棺而出活孩,到底是詐尸還是另有隱情,我是刑警寧澤秫筏,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布诱鞠,位于F島的核電站,受9級特大地震影響这敬,放射性物質(zhì)發(fā)生泄漏航夺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一崔涂、第九天 我趴在偏房一處隱蔽的房頂上張望阳掐。 院中可真熱鬧,春花似錦冷蚂、人聲如沸缭保。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽艺骂。三九已至,卻和暖如春隆夯,著一層夾襖步出監(jiān)牢的瞬間钳恕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工蹄衷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忧额,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓愧口,卻偏偏與公主長得像睦番,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耍属,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容