Reactjs學習記錄(005)-組件&props

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

當React遇到的元素是用戶自定義的組件,它會將JSX屬性作爲單個對象傳遞給該組件,這個對象稱之爲“props”。

我們來回顧一下在這個例子中發生了什麼:

  1. 我們對<Welcome name="Sara" />元素調用了ReactDOM.render()方法。
  2. React將{name: 'Sara'}作爲props傳入並調用Welcome組件。
  3. Welcome組件將<h1>Hello, Sara</h1>元素作爲結果返回。
  4. React DOM將DOM更新爲<h1>Hello, Sara</h1>

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (                      {/*直接return();*/}
    <div>
      <Welcome name="Sara" />  {/*在組件中嵌套組件*/}
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

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

提取組件一開始看起來像是一項單調乏味的工作,但是在大型應用中,構建可複用的組件完全是值得的。當你的UI中有一部分重複使用了好幾次(比如,ButtonPanelAvatar),或者其自身就足夠複雜(比如,AppFeedStoryComment),類似這些都是抽象成一個可複用組件的絕佳選擇,這也是一個比較好的做法。

function formatDate(date) {  //這裏只是一個函數而非組件,首字母沒有大寫
  return date.toLocaleDateString();
}

function Avatar(props) {
  return (
    <img className="Avatar"
         src={props.user.avatarUrl}
         alt={props.user.name} />
  );
}

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

const comment = {  //這裏是一個對象嗎
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'http://placekitten.com/g/64/64'
  }
};
ReactDOM.render(
  <Comment
    date={comment.date}
    text={comment.text}
    author={comment.author} />,
  document.getElementById('root')
);

Props的只讀性

無論是使用函數或是類來聲明一個組件,它決不能修改它自己的props。來看這個sum函數:

function sum(a, b) {
  return a + b;
}

類似於上面的這種函數稱爲“純函數”,它沒有改變它自己的輸入值,當傳入的值相同時,總是會返回相同的結果。

與之相對的是非純函數,它會改變它自身的輸入值:

function withdraw(account, amount) {
  account.total -= amount;
}

React是非常靈活的,但它也有一個嚴格的規則:

所有的React組件必須像純函數那樣使用它們的props。

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); //注意這裏的調用tick沒有加()

下面封裝了一個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設置一個定時器並且每秒更新UI應該是Clock的實現細節。

理想情況下,我們寫一次 Clock 然後它能更新自身:

爲了實現這個需求,我們需要爲Clock組件添加狀態

狀態與屬性十分相似,但是狀態是私有的,完全受控於當前組件。

我們之前提到過,定義爲類的組件有一些特性。局部狀態就是如此:一個功能只適用於類。

class Clock extends React.Component {  //component{render()  {return ();}   }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>{/*沒有(props)而是變成了this.props*/}
      </div>
    );
  }
}

function tick() {
  ReactDOM.render(                //在方法中引入組件,渲染節點
    <Clock date={new Date()} />, //props從這裏傳入組件
    document.getElementById('root')
  );
}

setInterval(tick, 1000);


將函數轉換爲類

你可以通過5個步驟將函數組件 Clock 轉換爲類

  1. 創建一個名稱擴展爲 React.Component 的ES6 類

  2. 創建一個叫做render()的空方法

  3. 將函數體移動到 render() 方法中

  4. 在 render() 方法中,使用 this.props 替換 props

  5. 刪除剩餘的空函數聲明


class Clock extends React.Component {  //component{render()  {return ();}   }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>{/*沒有(props)而是變成了this.props*/}
      </div>
    );
  }
}

function tick() {
  ReactDOM.render(                //在方法中引入組件,渲染節點
    <Clock date={new Date()} />, //props從這裏傳入組件
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

爲一個類添加局部狀態

我們會通過3個步驟將 date 從屬性移動到狀態中:

class Clock extends React.Component {
  constructor(props) {
    super(props);  //注意我們如何傳遞 props 到基礎構造函數的:
    this.state = {date: new Date()};  //step-two添加一個類構造函數來初始化狀態 this.state
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
         <h2>It is {this.state.date.toLocaleTimeString()}.</h2>{/*step-one在 render() 方法中使用 this.state.date 替代 this.props.date*/}
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />, //step-three從 <Clock /> 元素移除 date 屬性:
  document.getElementById('root')
);

將生命週期方法添加到類中

在具有許多組件的應用程序中,在銷燬時釋放組件所佔用的資源非常重要。

每當Clock組件第一次加載到DOM中的時候,我們都想生成定時器,這在React中被稱爲掛載

同樣,每當Clock生成的這個DOM被移除的時候,我們也會想要清除定時器,這在React中被稱爲卸載

我們可以在組件類上聲明特殊的方法,當組件掛載或卸載時,來運行一些代碼:



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

  componentDidMount() {  //當組件輸出到 DOM 後會執行 componentDidMount() 鉤子,這是一個建立定時器的好地方
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {  
    clearInterval(this.timerID); //在 componentWillUnmount()生命週期鉤子中卸載計時器:
  }

  tick() {     //最後,我們實現了每秒鐘執行的 tick() 方法。它將使用 this.setState() 來更新組件局部狀態:
    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 。 我們稍後會更新此狀態。

  2. React 然後調用 Clock 組件的 render() 方法。這是 React 瞭解屏幕上應該顯示什麼內容,然後 React 更新 DOM 以匹配 Clock 的渲染輸出。

  3. 當 Clock 的輸出插入到 DOM 中時,React 調用 componentDidMount() 生命週期鉤子。 在其中,Clock 組件要求瀏覽器設置一個定時器,每秒鐘調用一次 tick()

  4. 瀏覽器每秒鐘調用 tick() 方法。 在其中,Clock 組件通過使用包含當前時間的對象調用setState() 來調度UI更新。 通過調用 setState() ,React 知道狀態已經改變,並再次調用render() 方法來確定屏幕上應當顯示什麼。 這一次,render() 方法中的this.state.date 將不同,所以渲染輸出將包含更新的時間,並相應地更新DOM。

  5. 一旦Clock組件被從DOM中移除,React會調用componentWillUnmount()這個鉤子函數,定時器也就會被清除。

數據自頂向下流動

父組件或子組件都不能知道某個組件是有狀態還是無狀態,並且它們不應該關心某組件是被定義爲一個函數還是一個類。

這就是爲什麼狀態通常被稱爲局部或封裝。 除了擁有並設置它的組件外,其它組件不可訪問。

function FormattedDate(props) {  //組件可以選擇將其狀態作爲屬性傳遞給其子組件
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;  //<FormattedDate date={this.state.date} />
}

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>
        <FormattedDate date={this.state.date} /> {/*子組件接收當前組件的state,FormattedDate 組件將在其屬性中接收到 date 值,並且不知道它是來自 Clock 狀態、還是來自 Clock 的屬性、亦或手工輸入*/}
      </div>
    );
  }
}

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

這通常被稱爲自頂向下單向數據流。 任何狀態始終由某些特定組件所有,並且從該狀態導出的任何數據或 UI 只能影響樹中下方的組件。

如果你想象一個組件樹作爲屬性的瀑布,每個組件的狀態就像一個額外的水源,它連接在一個任意點,但也流下來。


爲了表明所有組件都是真正隔離的,我們可以創建一個 App 組件,它渲染三個Clock

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

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>
        <FormattedDate date={this.state.date} />
      </div>
    );
  }
}

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章