1. 程式人生 > >React 快速入門

React 快速入門

安裝

首先你需要點選安裝 nodejs(npm)。然後執行:

npm install -g create-react-app

如果上述命令執行失敗可以執行以下命令:

npm install -g create-react-app --registry=https://registry.npm.taobao.org

然後建立一個 react 並執行:

create-react-app myApp
cd myApp
npm start

這樣你就簡單的完成了一個 react app 建立,其目錄結構如下( 圖中不包括 node_modules 目錄,下同 ):

Hello World

我們刪除一些不必要的東西,然後修改目錄結構如下(不能刪 node_modules 目錄,如果刪了就在專案目錄下執行 npm i 就好了):

其中 components 是個目錄。

修改 index.js 如下:

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <h1> hello world! </h1>,
  document.getElementById('root')
);

然後命令列執行:

npm start

你就可以看到熟悉的 ‘hello world’ 了

JSX

JSX 是 react 中允許 js 和 html 混寫的語法格式,需要依賴 babel 編譯。這裡我就只研究它的語法:

const element = <h1>Hello, world!</h1>;

可以通過花括號在其中插入表示式:

function formatName(user){
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root'
) );

可以將 HTML 語句寫為多行以增加可讀性,用小括號括起來可以防止自動插入分號導致的錯誤。

JSX 也是個表示式,所以可以用在 for 和 if 中:

  function getGreeting(user){
    if (user){
      return <h1>Hello, {formatName(user)}!</h1>;
    }
    return <h1>Hello, Stranger.</h1>;
  }

我們可以正常使用引號給 HTML 標籤新增屬性,也可以使用 js 表示式

const element = <div tabIndex="0"></div>;

const element = <img src={user.avatarUrl} />;   //注意空標籤以 /> 結尾,像 XML 一樣

注意 html 屬性名請使用小駝峰(camelCase)寫法

React 會在渲染之前 escape 所有在 JSX 嵌入的值,可以有效的防止 XSS 攻擊。

babel 會編譯 JSX 成 React.createElement() 的引數呼叫:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
// 編譯為以下形式
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

而 React.createElement() 會生成這樣一個物件(React 元素):

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world'
  }
};

元素渲染

./public/index.html 中有一個 id 為 root 的 div。我們將這個 div 作為 react 渲染的容器。

回看 hello world 程式,通過 ReactDOM.render() 方法很輕鬆的把內容渲染到了目標容器上:

ReactDOM.render(
  <h1> hello world! </h1>,
  document.getElementById('root')
);

當然也可以這樣寫:

let content = <h1> hello world! </h1>;
ReactDOM.render(
  content,
  document.getElementById('root')
);

下面我們寫一個複雜的,這是個實時更新的時鐘,通過 setInerval 每隔 1s 呼叫 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);

重寫上面時鐘元件的程式碼如下,使其元件化程度更高:

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

元件

React 給我們提供了更好的管理我的程式碼——元件。這裡我們還是首先我們先了解一下自定義標籤:

const element = <Welcome name="Sara" />;

對這個標籤的理解也不難,它實際上呼叫了 Welcome 函式,並且將所有的屬性(這裡只有name)打包為一個物件傳給 Welcome 函式。所以下面這個程式碼輸出 ”Hello Sara”

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

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

元件幫助我事先一些重複的工作,比如這樣:

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

function App(){
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

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

我們可以通過傳遞引數得到同一個元件構建的不同模組。

這裡我們需要補充一個重要的概念:純函式!!!

如果一個函式執行過程中不改變其引數,也不改變其外部作用於引數,當相同的輸入總能得到相同的值時,我們稱之這樣的函式為純函式。React 要求所有元件函式都必須是純函式。

其實之前的一段程式碼中 Tick, Welcome 函式就可以看做是一個元件,同時 React 建議元件名的首字母大寫。但是更多情況下我們會用到 es6 的語法構建元件。以之前時鐘程式碼為例,轉換過程分為五個步:

  1. 新建一個類,類名同組件函式名Clock,並繼承自 React.Component;
  2. 給該類新增一個方法 render(/無引數/);
  3. 將 Clock 的函式體作為該函式的函式體;
  4. 將 render 方法中的 props 換為 this.props;
  5. 刪除原有的 Clock 函式

結果如下:

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

但這樣計時的功能就不能用了,我們繼續往下看……

State 和 Lifecycle

解決上面這個問題,就需要用到 State 和 Lifecycle 的知識了

我們給 Clock 類新增一個建構函式,並且刪除 Clock 標籤中的引數:

class Clock extends React.Component {
  constructor(props){
    super(props);
    this.state = {date: new Date()};    //state 用來記錄狀態
  }

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

ReactDOM.render(
  <Clock />,               //刪除引數
  document.getElementById('root')
);

為了控制計時的生命週期,我們需要引入 2 個方法 componentDidMount() 和 componentWillUnmount(),前者在渲染(render方法)完成時立即執行,後者在該 render 的內容即將被移除前執行。

很明顯,前者適合註冊計時器,後者可以用來清除計時器(防止記憶體洩露)

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

下一步我們重寫 tick 函式,此時的 tick 函式只需要修改 this.state 就行了。注意 React 要求不能直接修改該屬性,而是使用 setState() 方法,所以 tick 函式如下:

tick(){
  this.setState({
    date: new Date()
  });
}

這裡需要注意的是,當 state 中有很多屬性的時候,比如:

this.state = {name:"Lily", age: 12};

執行 setState 方法修改其中的內容時並不會影響未修改的屬性:

this.setState({name: "Bob"});   //此後 this.state 為 {name:"Bob", age: 12};

此外 setState 可能是非同步的,所以不要在更新狀態時依賴前值:

// 這是個反例
this.setState({
  counter: this.state.counter + this.props.increment,
});

為例解決這個問題,你可以傳入函式引數:
// Correct
this.setState((prevState, props) => ({ //這裡 prevState 更新前的 state 物件,props 為新值構成的物件
counter: prevState.counter + props.increment
}));

此時,完整的程式碼為:

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

事件

React 事件註冊和原生 DOM 事件類似的,這裡需要理解一些不同點即可:

  • 事件名使用小駝峰寫法,而不是全小寫,例如:onclick 寫作 onClick
  • 註冊事件使用花括號表示式替代原有函式寫法
<button onClick={activateLasers}>
  Click Here
</button>
  • 無法通過事件函式 return false 的方式阻止預設事件,必須顯式的呼叫 preventDefault(),並且在使用時不用糾結瀏覽器相容問題,React 已經幫你處理好了
  • React 建議通常不需要通過 addEventListener 新增事件,只需要像上方程式碼那樣在 render 時繫結事件即可
  • 在 es6 語法的元件中註冊事件只需要將事件函式定義為該類的一個方法,然後在 render 時繫結即可:
render(){
    return (
      <button onClick={this.handleClick}>
        Click Here...
      </button>
    );
  }
  • 在 class 中,除了箭頭函式定義的方法中 this 符合預期,其餘方法中的 this 都是 undefined,應該手動繫結。因此以下三個按鈕中 click2 會報錯。
class Button extends React.Component {
  constructor(){
    super();
    this.name = "Bob";
    this.click3 = this.click2.bind(this);
    this.click1 = () => {
      console.log(`hello ${this.name}`);
    }
  }
  click2(){
    console.log(`hello ${this.name}`);
  }

  render(){
    return (
      <raw>
        <button onClick={this.click1}>Click1</button>
        <button onClick={this.click2}>Click2</button>
        <button onClick={this.click3}>Click3</button>
        <button onClick={(e) => this.click2(e)}>Click3</button>
      </raw>
    );
  }
}
  • 以上幾種方法,React 推薦使用 click3 的實現方法,重寫如下:
class Button extends React.Component {
  constructor(){
    super();
    this.name = "Bob";
    this.click = this.click.bind(this);
  }
  click(){
    console.log(`hello ${this.name}`);
  }

  render(){
    return (
      <raw>
        <button onClick={this.click}>Click me</button>
      </raw>
    );
  }
}
  • 傳遞引數給事件的時候,第一個引數為 id, 第二個引數為 event。實際呼叫可以去以下兩種方式:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id, e)}>Delete Row</button>
  • 以上兩種方法等效,後一個方法的引數 e 可以省略。

條件渲染

根據不同的條件(通常指state)渲染不同的內容, 比如下面段程式碼可以根據 isLoggenIn 渲染不同的問候語:

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {              // 根據 isLoggenIn 渲染不同的問候語
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // 你可以嘗試設定 isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

下面用 class 實現一個複雜一點的,帶有登入/登出按鈕的:

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      登入
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      登出
    </button>
  );
}
class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoggedIn: false
    };

    // 修正 this 繫結
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const { isLoggedIn } = this.state;

    let button = null;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        {/* Greeting 取自上一個示例 (注意這裡的註釋寫法)*/}
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

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

當然,對於這樣一個簡單的示例,使用 if 可能你會覺的太複雜了,我們也可以使用 && ?: 這些運算子來代替 if 語句,就像寫 javascript 程式碼一樣。我們極力的化簡一下上面的程式碼:

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoggedIn: false
    };
  }

  render() {
    const { isLoggedIn } = this.state;
    const button = isLoggedIn ?
          <button onClick={() => { this.setState({isLoggedIn: false}); }}>登出</button>
          : <button onClick={() => { this.setState({isLoggedIn: true}); }}>登入</button>;

    return (
      <div>
        <h1>
          {
            isLoggedIn ? 'Welcome back!' : 'Please sign up.'
          }
        </h1>
        {button}
      </div>
    );
  }
}

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

當然,如果你需要在某個條件下不進行渲染,那麼直接輸出 null 即可,比如下面這個元件,在 props.warnfalse 時不渲染任何內容:

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }
  return (
    <div className="warning">
      Warning!
    </div>
  );
}

需要注意的是,即便你輸出了 null, react 也會再渲染一次。同理,componentWillUpdatecomponentDidUpdate 也會被呼叫。

列表

在 React 中我們可以使用 map() 方法渲染列表,比如如下這個例子,將一組資料對映(map)為一組 dom:

const data = [1, 2, 3, 4, 5];
const listItems = data.map((item) =>
  <li key={number.toString()}>{item}</li>
);
ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

我們注意到這裡我們給 li (即列表的每個元素)標籤加了一個 key 屬性,這個 key 用來幫助 React 判斷哪個元素髮生了改變、新增或移除。關於這個 key 我們需要明白以下幾點:

  1. 最好保證 key 是一個字串,並且在該列表中唯一,如果你的資料中實在沒有唯一的 key 可以選擇,那麼就使用陣列的索引(index)吧(不推薦這樣)
  2. 值得注意的是,如果你不給每個元素指定一個 key, react 會預設使用索引(index)作為 key
  3. key 的值只是給 React 起到類似暗示的作用,不會真正的傳遞給 dom, 所以如果你需要使用 key 的值,應使用一個其它變數傳遞該值。

當然,上面程式碼我們也可以寫成 inline 的形式:

const data = [1, 2, 3, 4, 5];
ReactDOM.render(
  <ul>
    {
      data.map((item) =>
        <li key={number.toString()}>{item}</li>
      );
    }
  </ul>,
  document.getElementById('root')
);

表單

表單的處理會和原生的 html 有一些區別,因為 React 可以很好的幫助你使用 js 控制你的表單,這裡我們需要引入一個新的概念:受控元件。

受控元件說白了就是其值受 react 控制的元件。其中,表單的元素通常都會具有其自己的 state,該值會隨著使用者的輸入改變。比如下面這個例子,會在使用者提交時輸出使用者的名字:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

不難發現,這裡使用了,onchange 事件不斷的將使用者的輸入繫結到 this.state.value 上,然後通過和使用者輸入同步的重繪實現資料的顯示。這樣可以很好的控制使用者輸入,比如同步的將使用者輸入轉化為大寫:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}

理解了上面的內容我們可以知道,單純給一個 input 賦值一個值使用者是不能修改的,比如下面這行程式碼:

ReactDOM.render(<input value="hi" />, mountNode);

但如果你不小心他的值設為 null 或 undefined(等同於沒有 value 屬性),這個 input 就可以被更改了:

ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

在 React 中 textarea 也是通過 value 屬性實現其內容變化的,而非其子節點:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

在 React 中,對於 select 也會顯得很方便,你不需要在 option 中通過 selected 改變其值了,而是在 select 標籤上通過 value 屬性實現:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

上面程式碼預設選中 Coconut。 這裡值得注意的是,對於多選框,你可以傳入一個數組作為值:

<select multiple={true} value={['B', 'C']}>

當你控制很多個表單元件的時候要是為每個元件寫一個 handler 方法作為 onChange 事件那就太麻煩了。所以 React 可以通過表單元素的 name 配合 event.target.name 來控制表單:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const { target } = event;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const { name } = target;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"s
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

state 提升

這個部分,想說的不是個語法問題,而是程式碼結構問題。我們重點理解一個例子:計算溫度的功能。

我們實現2個輸入框(攝氏溫度和華氏溫度)的同步資料顯示,和對資料的簡單操作(判斷是否達到標況下水的沸點100攝氏度)

我們先做點準備工作,比如溫度轉換函式:

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

別忘了,一個好的程式設計師要能夠很好的控制資料輸入,所以我們再寫一個函式用來處理溫度,引數是溫度和溫度轉換函式:

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input) || typeof convert !== 'function') {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return String(rounded);
}

我們先簡單實現這個功能:

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

然後我們寫一個元件用來讓使用者輸入溫度

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const { temperature } = this.state;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />

        <BoilingVerdict
          celsius={parseFloat(temperature)} />
      </fieldset>
    );
  }
}

此時我們可以輸入攝氏溫度了,再新增一個數據華氏溫度的地方。這裡我們從上面的 Calculator 中提出來輸入元件:

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const { temperature } = this.state;
    const { scale } = this.props;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

這樣 Calculator 就簡單了:

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

這樣2個輸入框就有了,但是它們還不能同步變化。而且 Calculator 元件不知道水溫是多少了,沒法判斷溫度了。這是我們應該吧溫度狀態放在他們最近的公共祖先元素上,這裡就是 Calculator 元件啦。

很明顯,首先要改的就是 TemperatureInput, 它不需要 state 了,我們應該從引數獲取溫度了:

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const { temperature, scale } = this.props;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

之後我們修改 Calculator 的 state, 將 temperature 和 scale 放入其中, 並新增狀態轉換函式:

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const { temperature, scale } = this.state;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />

        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />

        <BoilingVerdict
          celsius={parseFloat(celsius)} />

      </div>
    );
  }
}

到此所有的工作就完成了。我們總結一下,輸入資料時都發生了什麼

  • 當用戶輸入時,react 呼叫了已經申明的函式作為 onChange 事件。在這個例子中,就是 TemperatureInput 中的 handleChange 方法。
  • TemperatureInput 中的 handleChange 方法呼叫了 this.props.onTemperatureChange(), 並傳入了最新的值。而這個方法由父元件 Calculator 提供。
  • 在前一次渲染中,Calculator 已經將 Celsius TemperatureInput 的 onTemperatureChange 設定為 handleCelsiusChange,並將 Fahrenheit TemperatureInput 的 onTemperatureChange 設定為 handleFahrenheitChange。所以這兩個計算方法的呼叫取決於我們編輯哪一個 input。
  • 在這兩個方法中,Calculator 元件通過呼叫 this.setState() 方法讓 react 以最新的輸入和當前 scale 重繪該元件。
  • React 呼叫 Calculator 元件的 render 方法渲染頁面,兩個 input 中的值會基於當前值和 scale 重新計算, 溫度轉換函式就是在這裡被呼叫的。
  • React 通過 props 使用 Calculator 新傳入的資料,分別呼叫每個 TemperatureInput 模組中的 render 方法渲染 input 元件。
  • React DOM 更新 DOM 樹匹配新的資料,我們編輯的 input 得到我們剛剛輸入的值,而另一個 input 得到轉換後的值。

我們看看官方給出的效果:

組合與繼承

React 建議用元件組合的方式代替元件繼承。所以我們需要學習如何用組合代替繼承。

很多元件在事先是不知道自己的孩子(內部的元素)的。比如對話方塊這樣的盒子型元素。我們需要使用 children 屬性來解決這個問題

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

props.children 表示通過其他元件呼叫 FancyBorder 時的全部孩子元素,對應下面例子,children 就是 h1 和 p 的 react 物件陣列

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

但是當元件缺乏共同點的時候,我們需要在元件中開很多孔,就像下面這個例子,這些孔可以很好的幫我們組合使用很多元件,而且 react 並不限制我我們傳遞引數的型別

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

有時候我們想對元件做具體化分類的時候,邏輯上會很像繼承,比如 WelcomeDialog 是 Dialog 的一個具體化分類。但在 React 中我們依然用組合的方式實現這個功能:

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />

  );
}

當然我們也可以用 class 的定義方式:

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />

        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}