React.js 官方範例教學中文完整翻譯 (2016.02.26)

本篇為翻譯文 來源為 Facebook react.js 官方網站

內容為透過一個Facebook的留言框範例來學習 Reactjs 的教學。

作者的話:因為這篇文章需要先使用git將程式碼clone到您的伺服器
若是您想了解如何將程式碼git clone到電腦中,可詳閱此篇教學

另外程式實作中的原註解不會刪除 會另添加中文註解

[2016.01.22] – 這篇文章有些翻的語句不是很順(因為我想不太到有些該怎麼翻成比較技術性的字眼,等到全部翻完以後會有一個比較完整校正流程再更新。)

[2016.02.01] – 快翻完了 …抱歉最近有點忙 …

[2016.02.26] – 翻譯完 日後有時間會做一些必較口譯式的翻譯

將會協助你建立一個簡單、實在的留言框,您可以將此程式放置到你的部落格中,像DisqusLiveFyre,或者 Facebook comments。
將會提供您:

  • 留言框的顯示。
  • 使用表單送出一則留言
  • 後端程式提供一個 Hooks ,

同時也有下列的特點:

  • 留言優化:儲存留言至伺服器之前先顯示在列表中,讓使用者感覺更即時。
  • 及時更新:不用重新整理頁面即可即時顯示在使用此頁面的其他使用者上。
  • Markdown 格式:使用者可以用 Markdown 格式來留言。 意旨可以更加整齊

臉書官方提供的原始碼(GitHub)

開始

在這一篇教學中,我們會盡可能地讓程式碼變得易懂,將所有程式碼封裝在一個HTML檔中,使用您最喜愛的編輯器開啟一個新的HTML檔( public/index.html ),內容就像這樣:

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Tutorial</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel" src="scripts/example.js"></script>
    <script type="text/babel">
      // To get started with this tutorial running your own code, simply remove
      // the script tag loading scripts/example.js and start writing code here.
    </script>
  </body>
</html>

這教學中剩下的部分我們將寫在script tag中,我們還不需要任何即時重整(live reloading),所以您需要在將程式碼儲存後再瀏覽器重新整理方能看到更新。在您的瀏覽器中打開 http://localhost:3000(在您確定開啟了伺服器)。
當您沒有”變更上述任何程式碼”的情況下,您將會看到程式碼的完成執行的樣子。

先刪除<script type=”text/babel” src=”scripts/example.js”></script>接著讓我們開始這篇教學。

第一個元件

整個 React 為模組化與設計可組合元件,在我們留言框範例中,我們將會遵循這個架構。

- CommentBox
  - CommentList
    - Comment
  - CommentForm

建立 CommentBox 這個元件,它只需要一個簡單的 <div>

// tutorial1.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);

注意 html 元素取名開頭字母一定要為小寫,而自定義的 react class 開頭取名一定為大寫。

JSX 語法

首先你會注意到在 XML 的語法在您的 Javascript,我們會使用簡單的預先編譯器的語法糖負責去轉換這些原生的 Javascript。

語法糖:意思是讓程式碼更容易閱讀或表達一種編程語言的語法。使程式碼表現更清晰,更簡潔,或以另類的風格。  –  Wiki

// tutorial1-raw.js
var CommentBox = React.createClass({displayName: 'CommentBox',
  render: function() {
    return (
      React.createElement('div', {className: "commentBox"},
        "Hello, world! I am a CommentBox."
      )
    );
  }
});
ReactDOM.render(
  React.createElement(CommentBox, null),
  document.getElementById('content')
);

並不只是 JSX 可以被選擇,但我們發現 JSX 語法在原生 Javascript 更容易使用
更多請看: JSX Syntax article.

上一段程式碼中

我們傳入透過一個 包含一些方法的 Javascript 物件給  React.createClass() 去創建一個React 元件。最重要的方法是 render(),他回傳一個 react 元件樹狀圖,最後以 HTML 方式呈現出來。

<div> 標籤並不是一個真正的 DOM 節點。這是一個 React 知道該操作哪一個區塊。
您可以把它想成是標記或是片段資料,為的是讓 React 知道如何操作。

React 是安全的,所以預設不會產生 html 字串,因此就可以預防 XSS 攻擊。

您並不需要回傳標準的 HTML。您可以回傳您所建立或是其他人所建的元件樹。
這也就是 React 的可組合性:前端的維護性關鍵之一。

ReactDOM.render() 方法使用 – 注入想修改的DOM元素在第二個參數中。

React在不同的平台下做出不同的反應( React Native).

最重要的是在此教學中 ReactDOM.render 必須保持在script的最底部。
ReactDOM.render 必須只能在 composite components 已經被定義後才能呼叫它。

撰寫元件

讓我們來撰寫 CommentList and CommentForm 架構,再一次簡單的創建<div>。加入這兩個元件到您的程式碼中。

// tutorial2.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});

接著,更新他們到 CommentBox 中:

// tutorial3.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});

為了防止大家不知道到這裡目前為止程式碼應該長怎麼樣子
在此先貼上程式碼到這裡應該有的截圖

5
注意到我們混和HTML標籤與元件,HTML元件是正規的React元件,就像您定義的元件,這有一個不同處。 JSX 的編譯器會自動把 HTML 標簽重寫成 React.createElement(tagName) 表達法 ,這是為了防止全域命名空間的汙染。

使用屬性(Properties)

讓我們創建 Comment 元件,父元件透過'屬性'傳遞資料給子元件。

這些 '屬性' 是存取自 this.props. 使用屬性,我們可以透過 Comment from the CommentList 讀取資料:

// tutorial4.js
var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
});

元件屬性

現在我們定義了Comment元件,我們將會需要傳遞作者名稱與留言內容。
這個允許我們成重新使用一樣的程式碼在每一個不同的留言中。
現在讓我們增加一些留言在我們的 CommentList 中:

// tutorial5.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>
      </div>
    );
  }
});

這讓我們可以從父元件 CommentList 傳遞一些資料給子元件 Comment 。舉個例子,當我們傳送 Pete Hunt (屬性) , Comment元件將可以使用 this.props.author 、 this.props.children 這兩種方式去存取這些屬性(properties)資料。

加入 Markdown (Markdown - wiki)

Markdown 是一個種將存文字透過更簡單的方法轉換成Html,例如,用星號來括號。在這個教學中我們使用第三方的library來標記這些需要Markdown的文字並將他們轉換成HTML。我們在這個頁面已經準備了library與原本就已經被標記的,所以我們可以直接使用他,讓我們使用 Markdown 的功能轉換並些輸出這些 comment :

// tutorial6.js
var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {marked(this.props.children.toString())}
      </div>
    );
  }
});

在上述中,我們 Call library 去標記。我們必須從 React's wrapped text 轉換 this.props.children , 因此我們調用 toString()。

但這裡有個問題,我們給的 comments 在瀏覽器看起來像是這樣:                                    "<p>This is<em>another</em> comment</p>"。我們要將這字串中的 tags 可以轉換成Html tag。

但 React 為保護你免於 XSS attack
雖然這裡有個方法可以解決,但是框架會警告你別使用他,以免遭受 XSS attack

// tutorial7.js
var Comment = React.createClass({
  rawMarkup: function() {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return { __html: rawMarkup };
  },

  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
});

所以 React 提供了一個特別的 API 他可以讓你不容易去插入原始的HTML,但是需要標記他。

連結資料模型

目前為止我們已經完成在原始碼中加入回應。相反地,讓我們讀取json資料進 CommentList

// tutorial8.js
var data = [
  {id: 1, author: "Pete Hunt", text: "This is one comment"},
  {id: 2, author: "Jordan Walke", text: "This is *another* comment"}
];

我們使用模型化的方式傳遞資料進 CommentList 中。修改 CommentBox 與  ReactDOM.render() 並用 CommentBox 透過屬性傳遞資料進 CommentList 中:

// tutorial9.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox data={data} />,
  document.getElementById('content')
);

現在資料可以顯示在 CommentList , 讓我們把留言變成即時性的。

// tutorial10.js
var CommentList = React.createClass({
  render: function() {
    var commentNodes = this.props.data.map(function(comment) {
      return (
        <Comment author={comment.author} key={comment.id}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

從 Server 中取得資料

將json資料源的連結填入:

// tutorial11.js
ReactDOM.render(
  <CommentBox url="/api/comments" />,
  document.getElementById('content')
);

這個容器不同於先前的元件,因為他將會自動重新讀取。

注意:這段code將無法使用在下個步驟中

狀態反應

根據他的屬性, 每一個元件都會自行讀取. 屬性不可變動:他們通過父元件傳遞屬性。在實作互動上,我們引進可變動的狀態(state)到元間中。 this.state 是一個私有組件,可通過調用 this.setState() 來改變元件。當狀態更新的時候,他們也會更新。

當 server 抓取資料時,我們將可以改變我們現有的資料。
讓我們在以下範例中新增一個陣列的回應資料到 CommentBox 元件中。

// tutorial12.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

getInitialState() 執行一次恰好在元件的生命週期中和設置元件的初始狀態。

更新狀態

當元件第一次被建立時,我們想要從 server上 用 GET JSON 來更新最新狀態。我們使用jquery來對 server 做非同步請求。 將下面這段 json code 放進你的跟目錄下,當他開始抓取資料時, this.state.data 將會看起來像這樣:

[
  {"id": "1", "author": "Pete Hunt", "text": "This is one comment"},
  {"id": "2", "author": "Jordan Walke", "text": "This is *another* comment"}
]
// tutorial13.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

這裏, componentDidMount 是一個 React 自動化方法在元件第一次被讀取以後。動態更新的關鍵在於呼叫 this.setState()。當一個新訊息從 server 出現,他們取代了原本就的陣列留言並且 UI 自動更新自己的元素。 當然,你也可以使用 WebSockets 或是其他技術。

// tutorial14.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox url="/api/comments" pollInterval={2000} />,
  document.getElementById('content')
);

我們在這裡做的都是使用 ajax , 並指派他每兩秒更新一次。試著讓他在瀏覽器中執行並且更改 comments.json 檔案 ; 每過兩秒,修改將會被呈現。

添加新的回應

現在讓我們來製作一個表單。我們的 CommentForm 元件應該要有 使用者名稱 和 回應方塊 並且傳送需求給 server 來儲存留言。

// tutorial15.js
var CommentForm = React.createClass({
  render: function() {
    return (
      <form className="commentForm">
        <input type="text" placeholder="Your name" />
        <input type="text" placeholder="Say something..." />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

控制元素

傳統的 文件物件模型(DOM) ,input 元素 是 rendered 和瀏覽器管理狀態。根據結果,實際的 DOM 狀態與元件不同。因為該元件狀態與視圖的狀態不同,這是不理想的狀態,在 React 中, 元件應該經常被重新取代 視圖中最新的狀態和不只是在初始化而已。

因此,我們將使用 this.state 來儲存 user 輸入值,我們定義初始狀態和兩個 author 與 text 屬性並且設置他們為空字串。在我們 input 元素中,我們設置了 value 屬性來反應 元件狀態和附加 onChange 處理器。這些 input 元素設置值為呼叫控制元件。 點此瞭解更多控制元件 Forms article.

// tutorial16.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  render: function() {
    return (
      <form className="commentForm">
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

事件

React 將反應事件使用 駝峰是大小寫 到元件中。 將 onChange 處理器傳到兩個 <input> 元素之間。 現在,使用者將文字輸入到 <input> 元素欄,附加 onChange callbacks 被移除和 容器的狀態被修改,接著,輸入的元素值將會被更新,以反應當前的元件狀態。

傳送表單

讓我們表單變成有互動性的。當 user 送出表單,應該顯示他,送出需求到 server 和重新更新表單中的回應。開始,讓我們監聽表單送出事件並且顯示他。

// tutorial17.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var author = this.state.author.trim();
    var text = this.state.text.trim();
    if (!text || !author) {
      return;
    }
    // TODO: send request to the server
    this.setState({author: '', text: ''});
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

我們附加 onSubmit 處理器到表單中並且清除表單中欄位當表單送出當合法輸入時。
呼叫 preventDefault() 事件以防止瀏覽器的默認提交表單的動作。

Callbacks 如屬性

當 user 送出回應,我們將需要重新整理列表中的(包含最新)回應 。當  CommentBox 狀態更新列表中的回應(這使 CommentBox 更有邏輯 )。

我們需要 pass 資料從子元件回復到父元件。我們在我們的 render 方法中透過一個新的 callback (handleCommentSubmit)到子元件中。每當事件被觸發,則 callback 將被調用:

// tutorial18.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    // TODO: submit to the server and refresh the list
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

當 user 送出表單時,呼叫 callback 從 CommentForm:

// tutorial19.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var author = this.state.author.trim();
    var text = this.state.text.trim();
    if (!text || !author) {
      return;
    }
    this.props.onCommentSubmit({author: author, text: text});
    this.setState({author: '', text: ''});
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

我們要做的就是要送到 server 並且重新更新列表:

// tutorial20.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

優化: 優化更新

我們的應用程式雖功能完整但感覺還是在等待請求時還是太慢,所以我們可以先不等待回覆,直接添加留言到留言筐中,這樣可以讓他感覺更即時。

// tutorial21.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    var comments = this.state.data;
    // Optimistically set an id on the new comment. It will be replaced by an
    // id generated by the server. In a production application you would likely
    // not use Date.now() for this and would have a more robust system in place.
    comment.id = Date.now();
    var newComments = comments.concat([comment]);
    this.setState({data: newComments});
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        this.setState({data: comments});
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

恭喜!

您已經可以建立一個 留言框(comment box)。若想學習更多可以參考 why to use React,或是 API reference ! Good luck!

7 thoughts on “React.js 官方範例教學中文完整翻譯 (2016.02.26)

  1. 請問 commentNodes 排序問題
    var commentNodes = this.props.data.map(function(comment) {
    return (

    {comment.text}

    );
    });
    排序是否可以按照id DESC呢?
    謝謝

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *