[譯]React核心概念4:state&生命週期

原文連接:reactjs.org/docs/state-…html

引言

本節內容介紹React組件中關於狀態(state)及生命週期的概念react

咱們來看看上節中編寫的計時器。目前咱們只學習了一種方式來更新UI,那就是調用ReactDOM.render()來改變渲染輸出。數組

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);
複製代碼

在本節中,咱們將學習如何封裝Clock組件並使其可以被複用。而且Clock組件將會設置本身的計時器而且每秒更新一次。bash

首先,咱們能夠封裝Clock組件的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組件就可以自動更新UI。post

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
複製代碼

爲了實現這個功能,咱們就須要在Clock組件中加入狀態(state)性能

狀態(state)與props類似,但確是屬於組件的私有屬性而且徹底受控於組件。學習

將函數組件轉化爲class組件

咱們能夠經過如下5個步驟將一個函數組件(如Clock)轉化爲class組件。fetch

  1. 建立一個相同名稱的ES6 class,而且繼承React.component
  2. 在其中添加一個方法render()
  3. 將函數組件中的UI元素添加到render()方法中。
  4. render()中的props替換成this.props
  5. 刪除以前編寫的函數組件。
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
複製代碼

如今Clock組件已是一個類組件了。

每當UI更新時render方法就會被調用,可是隻要在相同的DOM節點渲染Clock組件時,咱們僅會用到一個Clock的實例,這就使得咱們可使用如state或者生命週期方法等其餘的特性。

向class組件中添加局部state

咱們將會經過如下三個步驟將dateprops中轉移到state中。

1.將render()方法中的this.props.date替換成this.state.date

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
複製代碼

2.在class中添加一個class constructor並在其中初始化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傳遞給constructor的。

constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
複製代碼

class組件必須建立帶有props參數的constructor方法。

3.將<Clock />中的date刪去。

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組件設置本身的計時器而且每秒更新一次UI。

向class中添加生命週期方法

若是應用中使用了多個組件,那麼在組件被銷燬時釋放它所佔用的資源是十分必要的。

當Clock組件第一次在DOM上渲染時想要設置一個計時器,在React中這稱爲mounting(掛載)。

當Clock組件所建立的DOM被移出時咱們想要清除它的計時器,這在React中稱爲unmounting(解除掛載)。

咱們能夠在class組件中聲明一些特殊的方法,以此在組件掛載或卸載時運行某些特定的代碼。

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

  componentDidMount() {

  }

  componentWillUnmount() {
  
  }

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

上述代碼中componentDidMount()componentWillUnmount()統稱爲生命週期方法。

componentDidMount()方法在組件被渲染後調用,適合設置計時器。

componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
複製代碼

注意咱們經過this(this.timerID)保存計時器ID。

當你須要存儲一些不參與數據流的數據時(如timerID),你能夠手動添加額外的字段。

咱們將會在componentWillUnmount()生命週期方法中銷燬計時器。

componentWillUnmount() {
    clearInterval(this.timerID);
  }
複製代碼

最後,咱們將要實現tick()方法來使得Clock組件每秒更新一次。

在這裏咱們使用this.setState()方法來定時更新組件的局部state。

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組件的constructor方法。因爲如今Clock組件須要展現在頁面上,因此它初始化了this.state(包含了當前時間對象),以後咱們將更新state。
  2. 以後React調用Clock組件的render()方法,這個方法告訴React如何在頁面上渲染Clock。React以此更新DOM來匹配Clock的渲染輸出。
  3. 當Clock組件已經在DOM中渲染時,React調用componentDidMount()方法。在這個方法中,Clock組件設置了一個計時器來每秒調用一次tick()方法。
  4. 在每秒調用一次的tick()方法中,Clock組件使用setState()定時更新當前時間。經過調用setState(),React知道了state被改變,因而從新調用render()方法來更新頁面。由於此時,在render()方法中的this.state.date與以前不一樣,因此從新渲染的輸出時最新的時間,React也所以更新了它的DOM。
  5. 若是Clock組件將要從DOM中移除,React將會調用componentWillUmmount()來終止計時器.

正確地使用state

使用setState()必須知道的三點。

不要直接修改state

如下代碼不會從新渲染組件。

// 錯誤
this.state.comment = 'Hello';
複製代碼

須要使用setState()才能使得組件從新渲染。

// 正確
this.setState({comment: 'Hello'});
複製代碼

能直接給this.state賦值的地方只有個constructor()構造函數。

state更新多是異步的

出於性能考慮,React可能會把多個setState()調用合併成一個。

因爲this.props和this.state可能會異步更新,因此不能根據當前的值計算下一個state的值。

下面的代碼就可能會致使更新counter失敗。

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

爲了能成功更新counter,咱們須要傳遞一個函數給setState()而不是傳遞一個對象。這個函數中,上一個state將會做爲第一個參數,而這次更新所應用的props做爲第二個參數。

// 正確
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));
複製代碼

在上面的例子中咱們使用的是箭頭函數,可是常規的函數也是適用的。

// 正確
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});
複製代碼

state的更新會被合併

當你調用setState()方法時,React會將你傳入的參數合併到state中對應的對象中。

好比,你的state中可能會包含幾個獨立的變量。

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
      });
    });
  }
複製代碼

數據是向下流動的

不管是父組件仍是子組件都沒法知道一個組件是有狀態的仍是無狀態的,同時也無需關心它是函數組件仍是class組件。

這也是state被稱爲是局部的或是封裝的緣由。除了擁有這個state的組件外,其餘組件是沒法獲取此state的。

可是組件能夠選擇把自身的state做爲props傳遞給它的子組件。

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
複製代碼

固然這也適用於用戶自定義組件。

<FormattedDate date={this.state.date} />
複製代碼

FormattedDate 組件將會在props中獲取到date,可是它並不知道這是Clock組件的state傳遞的仍是props傳遞的或者是自定義的。

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

這一般被稱爲自頂而下單向 數據流。任意state都是從屬於特定的組件。從該state派生的數據或UI只能影響在組件樹中位於該組件之下的組件。

若是把組件樹看成是props的瀑布流,任意組件的state就像是這個瀑布的補充水源,隨着這個瀑布流下去。

爲了更好地展現組件的獨立性,咱們建立一個App組件並在其中渲染三個Clock。

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

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
複製代碼

運行以後能夠看到每個Clock都是獨立更新的。

在React應用中,組件時有狀態的仍是無狀態的屬於組件的實現細節,它是會隨着時間變化的。你能夠在有狀態組件中使用無狀態組件,反之亦然。

相關文章
相關標籤/搜索