React文檔 state and lifecycle

狀態和生命週期

這篇介紹 React 組件中狀態和聲明週期的概念。詳情能夠查看API參考javascript

思考前一部分中時鐘的例子。渲染元素中,咱們僅學習了一種更新 UI 的方式。調用 ReactDOM.render() 改變渲染後的輸出。html

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

在線嘗試 java

這部分,咱們學習如何編寫真正可複用的封裝 Clock 組件。 它會設置本身的計時器每秒更新本身。react

咱們從封裝 時鐘的外層開始:segmentfault

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 的細節實現。瀏覽器

理想狀況下咱們寫一次讓 Clock 更新自身:異步

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

實現這一需求咱們須要給 Clock 組件添加 "state".函數

State 很相似 props, 不一樣的是它徹底私有由組件控制。post

函數轉化爲類

轉化一個相似 Clock 的函數組件爲類組件須要五步:

  1. 建立一個 [ES6標準的類](), 名稱不變,繼承 React.Component.
  2. 重寫 render()方法。
  3. 函數的祖逖移到 render() 方法中。
  4. render() 方法體重使用 this.props替換 props
  5. 刪除空的函數聲明。
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

[在線嘗試]()

函數組件定義的 Clock 如今由類組件定義。

render() 方法會在每次更新時調用,可是隻要咱們渲染 <Clock /> 到一樣的 DOM 節點,就會使用 Clock 類的單一實例。這讓咱們可使用如 local state 和 lifecycle 鉤子等額外的特性。

爲類添加本地狀態

三步把 date 從 props 移動到 state:

  1. render() 方法中使用 this.state.date 替換 this.props.date
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. 添加一個[類構造器]()來分管 this.state 的初始化:
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

注意咱們傳遞 props 到基礎構造器:

construct(props) {
    super(props);
    this.state = {date: new Date()};
  }

類組件老是經過 props 調用基礎構造器。

  1. 移除<Clock /> 元素中的 date props :
ReactDOM.render(
    <Clock />,
    document.getElementById('root')
  );

待會咱們在將計時器代碼回寫到 組件自己。

結果以下:

class Clock extends React.Component {
  constructor(props) {
    super(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')
);

[在線嘗試]()

下面,咱們編寫 Clock 設置他本身的計時器每秒更新自身。

爲類添加聲明週期方法

多個組件的應用中,當組件銷燬時釋放組件佔用的資源很是重要。

咱們想 [設置一個計時器]() 不管什麼時候 Clock 第一次被渲染到 DOM. React 中稱之爲 」mounting".

咱們也想 [清楚一個計時器]() 不管什麼時候 Clock 被DOM 移除。 React 中稱之爲 「unmounting".

當組件 mounts 和 unmounts 時咱們能夠在組件類聲明特殊的方法來運行。

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

  componentDidMount() {
   
  }

  componentWillMount() {
 
  }

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

這些方法稱爲 "lifecycle hooks(生命週期鉤子)".

componentDidMount() 鉤子在組件輸出渲染至DOM 後運行。這個位置很適合創建一個計時器:

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

注意咱們如何正確的將 計時器 ID 保存到 this.

當 React 設置 this.props this.state 有了特別的含義,你能夠隨意爲類手動添加額外字段,若是你須要保存一些不參與數據流(好比 timerID)。

咱們在 componentWillUnmount() 生命週期鉤子函數中去掉 計時器。

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

最後咱們,實現一個稱爲 tick() 的方法實現 Clock 組件每秒運行。

這會用到 this.setState() 來調度更新組件的本地狀態:

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')
);

在線嘗試

如今時鐘按秒運行。

快速的回顧下發生了什麼還有方法調用順序:

  1. <Clock /> 傳給 ReactDOM.render(), React 調用Clock 組件的構造器。從 Clock 須要顯示當前時間,它經過包含當前時間的對象初始化 this.state。稍後更新 state.
  2. React 調用 Clock 組件的 render() 方法,知道應該在屏幕上顯示什麼。React 以後更新 DOM 來匹配 Clock 的渲染後輸出。
  3. Clock 的輸出被插入 DOM, React 調用 componentDidMount()生命週期鉤子。在方法內部,Clock 組件讓瀏覽器設置一個計時器按秒來調用組件的 tick()方法。
  4. 瀏覽器按秒調用 tick() 方法。其中,Clock組件經過包含當前時間的對象調用setState() 來調度 UI 更新。React經過 setState() 犯法調用知曉組件狀態發生改變,隨後調用 render() 方法再次知曉屏幕上應該顯示什麼。這時,render() 方法中 this.state.date 會發生改變,所以渲染結果將會包含更新後的時間。React 相應的更新 DOM.
  5. 若是 Clock 組件一旦移除DOM, React 調用 componentWillUnmount() 生命週期鉤子,而後計時器中止。

正確的更新 State

關於setState() 的三個須知:

不要直接修改修改 State

例如,這樣不會 從新渲染一個組件:

// Wrong
this.state.comment = 'Hello';

應該使用 setState():

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

構造器是惟一可爲 this.state 賦值的地方。

State 更新可能同步

React 爲了性能可能批量屢次 調用 setState() 一個單獨的更新。

由於 this.propsthis.state 可能異步更新,不該該依賴它們的值來計算下一個狀態。

例如,以下代碼可能更新計數器失敗:

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

修復這個問題,使用 setState() 的第二種形式,接受函數而不是一個對象。這個函數接受以前的 state 做爲第一個參數, 當時間更新時 props 做爲第二個參數。

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

上面的例子中咱們使用了 [箭頭函數](),但常規函數也是能夠的。

// Correnct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
    };
  });

狀態更新合併

當調用 setState(), React會合並你提供給當前狀態的對象。

例如, 你的狀態可能包含多個獨立的變量:

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

那麼你能夠經過分別調用 setState()獨立更新他們:

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.

數據流向下

不管子組件仍是父組件都沒法知道一個特定的主鍵是有狀態仍是無狀態,並且他們也不該當關心它是用函數方式仍是類方式定義。

這是爲何 state 常常被成爲本地或者被封裝的。它對任何組件不可達,不管是組件擁有它或者是其組成部分。

一個組件可能選擇傳遞他的 state 向下做爲 props 給它的子組件:

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

這對用戶自定義組件也一樣有效:

<FormattedDate date={this.state.date} />

FormattedDate 組件接受它 props 中的 date,並不知道他來自 Clock的 state,仍是來自 Clock 的 props, 或者是手動輸入:

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

在線嘗試

這個一般稱爲 "top-down(自上而下)" 或者 "unidirectional(單向)" 數據流。任何 state 總數被特定的組件所擁有,任何經過 state 傳遞的數據或 UI 都只能影響樹形結構的下方組件。

你能夠家鄉組件樹是一個 props 瀑布,每一個組件的狀態就像一個額外的水源在隨機點加入它同時向下流。
爲展現全部組件真正獨立,咱們建立一個 App 組件 渲染三個 <Clock />:

function App() {
return (

<div>
 <Clock />
 <Clock />
 <Clock />
</div>
);

}

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

[在線嘗試]()

每一個 **Clock** 設置他本身的計時器獨立更新他們。

React 應用中,不管一個組件是有狀態仍是無狀態都被當作一個組件可隨時間改變的實現細節。
你可在有狀態組件中使用無狀態組件,反之亦然。
相關文章
相關標籤/搜索