【全棧React】第6天: 狀態

全棧講師_金雲龍發表於2017-08-02

本文轉載自:眾成翻譯
譯者:iOSDevLog
連結:http://www.zcfy.cc/article/3824
原文:https://www.fullstackreact.com/30-days-of-react/day-6/

今天我們開始瞭解React中有狀態元件的工作原理,並且看看我們何時以及為什麼要使用狀態。

我們幾乎完成了在React開始執行的第一週。我們通過JSX工作,構建我們的第一個元件,設定父子關係,並使用React驅動元件屬性。我們還有一個重要的想法,我們還沒有討論React _狀態_相關的知識。

有關狀態的事

React並沒有讓我們修改this.props 我們有充分的理由的元件。想象一下,如果我們將 title 屬性支援傳遞給Header 元件,並且Header 元件能夠修改它。我們如何知道titleHeader 元件的什麼 ?我們設定了競爭條件,混亂的資料狀態,並且將是一個全面的壞主意,修改由父元件傳遞給我們的變數並在小孩中修改。

然而,有時元件需要能夠更新自己的狀態。例如,active 在秒錶上設定標誌或更新計時器。

雖然最好props 儘可能多地使用,但有時我們需要堅持元件的狀態。為了處理這個問題,React使我們有能力在元件中擁有_狀態_。

元件裡的state 意圖完全是內部的元件,它的孩子(即元件和任何孩子使用它訪問)。類似於我們如何props 在元件中訪問,可以通過this.state 元件訪問狀態。無論何時狀態改變(通過 this.setState() ),元件將重新投遞。

例如,假設我們有一個簡單的時鐘元件來顯示當前時間:

即使這是一個簡單的時鐘元件,它確實保留狀態,因為它需要知道當前顯示的時間。沒有使用state,我們可以設定一個計時器並重新渲染整個React元件,但頁面上的其他元件可能不需要重新渲染…這將是一個頭痛的問題。

相反,我們可以設定一個計時器來呼叫元件內部的rerender並更改此元件的內部狀態。

我們來建立這個元件。首先,我們將建立我們將要呼叫的元件Clock。在進入狀態之前,我們來構建元件並建立該render() 函式。我們需要考慮數字,如果數字小於10,在數字前面加上一個零(0),並進行相應的設定 am/pmrender()函式的最終結果可能如下所示:

class Clock extends React.Component {
  render() {
    const currentTime = new Date(),
          hours = currentTime.getHours(),
          minutes = currentTime.getMinutes(),
          seconds = currentTime.getSeconds(),
          ampm = hours >= 12 ? `pm` : `am`;

    return (
      <div className="clock">
        {
          hours == 0 ? 12 :
            (hours > 12) ?
              hours - 12 : hours
        }:{
          minutes > 9 ? minutes : `0${minutes}`
        }:{
          seconds > 9 ? seconds : `0${seconds}`
        } {ampm}
      </div>
    )
  }
}
// ...
export default Clock

如果我們渲染我們的新Clock 元件,我們只會在元件本身重新執行時獲得時間。這不是一個非常有用的時鐘(還)。為了將靜態時間顯示Clock 元件轉換為顯示時間的時鐘,我們需要每秒更新一次。

為了做到這一點,我們需要跟蹤元件狀態下的_current_ 時間。 為此,我們需要設定初始狀態值。 在ES6類樣式中,我們可以通過將this.state 設定為一個值來設定constructor() 中元件的初始狀態。

constructor(props) {
    super(props);
    this.state = this.getTime();
}

建構函式的第一行應該_始終_呼叫 super(props)。如果您忘記了這一點,元件將不會非常喜歡(即會有錯誤)。

現在我們this.stateClock 元件中有一個定義,我們可以在 render() 函式中引用它this.state。讓我們更新我們的 render() 函式this.state來獲取以下值:

class Clock extends React.Component {
  // ...
  render() {
    const {hours, minutes, seconds, ampm} = this.state;
    return (
      <div className="clock">
        {
          hours === 0 ? 12 :
            (hours > 12) ?
              hours - 12 : hours
        }:{
          minutes > 9 ? minutes : `0${minutes}`
        }:{
          seconds > 9 ? seconds : `0${seconds}`
        } {ampm}
      </div>
    )
  }
}

我們現在可以更新 state 元件而不是直接使用資料值。為了更新狀態,我們將使用該函式 this.setState(),這將觸發元件重新渲染。

在我們的Clock 元件中,我們使用本機setTimeout() JavaScript函式建立一個定時器,以this.state 在1000毫秒內更新物件。我們將把這個功能放在一個函式中,我們再次呼叫它。

class Clock extends React.Component {
  // ...
  constructor(props) {
    super(props);
    this.state = this.getTime();
  }
  // ...
  setTimer() {
    clearTimeout(this.timeout);
    this.timeout = setTimeout(this.updateClock.bind(this), 1000);
  }
  // ...
  updateClock() {
    this.setState(this.getTime, this.setTimer);
  }
  // ...
}

我們將在下一節中介紹生命週期中的鉤子,但是為了簡單起見,我們暫時將其簡稱為constructor()

在該 updateClock() 函式中,我們將要在新時間內更新狀態。我們現在可以在 updateClock() 函式中更新狀態:

class Clock extends React.Component {
  // ...
  updateClock() {
    this.setState(this.getTime, this.setTimer);
  }
  // ...
}

該元件將安裝在頁面上,並在(大約)一秒鐘(1000毫秒)內更新當前時間。但是,它不會再重新設定。我們可以在setTimer()函式結束時再次呼叫該函式:

class Clock extends React.Component {
  // ...
  updateClock() {
    const currentTime = new Date();
    this.setState({
      currentTime: currentTime
    })
    this.setTimer();
  }
  // ...
}

現在,元件本身可能會比超時功能再次呼叫慢,這將導致重新出現的瓶頸,並且不必要地在移動裝置上使用寶貴的電池。在呼叫setTimer() 函式之後this.setState(),我們可以將第二個引數傳遞給this.setState()函式,該函式將在狀態更新_後_保證被呼叫。

class Clock extends React.Component {
  // ...
  updateClock() {
    const currentTime = new Date();
    this.setState({
      currentTime: currentTime
    }, this.setTimer);
  }
  // ...
}

更新我們的活動列表

我們可以Header 在上一節中我們一直在研究的活動列表中更新我們的元件。當使用者點選search 圖示,我們將要顯示<input>元件。

嘗試一下!點選下面的搜尋圖示:(想要有效果還是去原文體驗吧?)

知道我們現在知道的是,現在 this.state 我們可以更新檢視來新增條件呈現<input>

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <MenuButton />

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className={searchInputClasses.join(` `)}
          placeholder="Search ..." />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

有些事情要記住

  • 當我們呼叫this.setState() 一個物件引數時,它將執行一個資料的_淺合併_到可用的物件中this.setState() ,然後重新渲染元件。
  • 我們通常只想在我們的狀態中保持我們將在該render() 函式中使用的值。從上面我們的時鐘的例子,請注意,我們的儲存hours,minutes,以及seconds 在我們的狀態。在我們不打算在render功能中使用的狀態下儲存物件或計算通常是一個壞主意,因為它可能導致不必要的渲染和浪費的CPU週期。

正如我們在本節頂部指出的那樣,props不僅出於效能原因,最好使用,但是因為有狀態的元件更難測試。

今天,我們更新了我們的元件以使其處於狀態狀態,現在有必要處理如何使元件成為狀態。明天我們將進入元件的生命週期,何時/如何與頁面進行互動。

MenuButton

上面提到的元件在程式碼庫中,只是為選單按鈕提供了一個很好的顯示。

const MenuButton = (props) => (
  <div className="menuIcon">
    <div className="dashTop"></div>
    <div className="dashBottom"></div>
    <div className="circle"></div>
  </div>
) 

相關文章