React 入門教學與 Hello World 基礎範例

State 與生命週期

在前面介紹過的技巧中,我們只能靠著 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 組件,將相關的 UI 獨立出來:

// 建立 Clock 組件
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 之後,我們希望它可以獨立運作,不要讓我們還要定時去更新它,也就是說它最好可以這樣使用:

// 獨立運作的 Clock 組件
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

若要達到這樣的功能,我們須在 Clock 中加入一個 state(意指狀態),這個 state 就類似 props 一樣,可包含一些資料,但不同的地方是 state 存在於組件內部(私有的),只受組件本身的控制。

將函數轉為類別

接下來我們要使用的功能比較複雜一些,所以要以 ES6 class 的方式來定義組件。我們先把前面的 Clock 以 ES6 class 的方式改寫:

// 以 ES6 class 定義的 Clock 組件
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

在類別中加入 State

將原本的 this.props.datethis.state.date 取代:

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

加入類別的建構子(constructor),在建構子中自己設定目前時間,而 props 的值則從父類別繼承:

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

接著就可以使用這個自動的 Clock 了:

// 使用自動的 Clock
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

生命週期

在較大型的應用程式中,我們必須注意資源的配給與回收問題,在資源不再被使用時,就需要將其回收。

目前我們的 Clock 只會自己設置一開始的時間,後續並不會更新。接下來我們要在 Clock 顯示時,加上一個 timer,讓它可以自動持續更新時間,然後在 Clock 被移除時,自動清除 timer,這些動作可以透過類別的掛載(mount)與卸載(unmount)功能來達成。

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

這裡我們靠著掛載函數(componentDidMount)讓 Clock 在顯示後,自動啟動 timer,定時呼叫 tick() 更新 state 的時間,然後藉由卸載函數(componentWillUnmount)在 Clock 要移除前,註銷 timer。

State 注意事項

關於 state 的使用,有三個重點。

不可直接更改 State

若要更改 state 裡面的資料,不可以直接更改:

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

要改用 setState 來設定新的值:

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

在程式中,唯一可以直接更改 state 的地方就是在建構子之中,除此之外都必須呼叫 setState

State 的更新是非同步的

由於 this.propsthis.state 的更新可能是非同步的,所以不可以直接根據其值來計算:

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

若需要根據舊的 state 來計算,要改成這樣:

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

這裡我們使用箭頭函數來簡化程式碼,若用傳統的語法則會像這樣:

// 正確用法
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

更新的 State 值會合併

state 中可以儲存多種資料,我們可以只更新其中一部份,它會自動跟原有的值合併。假設我們的 state 有兩個值:

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

我們可以個別更新這兩個值:

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

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

資料向下流動原則

組件的 state 資料只有組件本身可以存取,其餘不管是父組件或是子組件都無法直接得知其內容,因此 state 有區域性以及封裝的特性。

組件可以將自己的 state 傳遞給子組件:

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

自行定義的組件亦可:

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

FormattedDate 在收到 date 之後,並不會知道該資料是來自於何處。

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

組件的 state 資料可以傳遞給其下方的子組件,但是不可以朝其他方向傳遞,React 中的資料都遵守這種向下流動原則。

文章撰寫中…

參考資料:React從零開始學 ReactJSPeter Chang

網頁開發

1 Comment

  1. NTUDog

    寫得真好 期待下篇

Leave a Reply