1. 程式人生 > >React 初學者教程13:用 React 建立一個簡單的 Todo List

React 初學者教程13:用 React 建立一個簡單的 Todo List

本文轉載自:眾成翻譯 譯者:網路埋伏紀事 連結:http://www.zcfy.cc/article/1554 原文:https://www.kirupa.com/react/simple_todo_app_react.htm

概述:通過學習如何建立經典的 Todo List 應用,將所有學過的 React 技巧投入到實戰中。

如果說建立 “Hello, World!” 示例是慶祝你開始涉足 React,那麼建立一個經典的 Todo List 應用是慶祝你接近掌握 React!在本教程中,我們要把已經學習過的很多概念和技術綜合在一起,建立一個如下的應用:

這個 Todo List 應用的工作方式很簡單。在文字框中鍵入任務,按下 Add(或者回車)。在提交了任務之後,你會看到它作為一個條目出現。你可以繼續新增任務到附加條目,並讓它們都顯示出來:

很簡單,對吧?在如下的小節中,我們將從頭開始建立這個應用,詳細學習在此過程中一切是如何發生的。

開始

首先建立一個新 HTML 文件。在其中新增如下內容:

<!DOCTYPE html>
<html>

<head>
  <title>React! React! React!</title>
  <script src="https://fb.me/react-15.1.0.js"></script>
  <script src="https://fb.me/react-dom-15.1.0.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>

  <style>

  </style>
</head>

<body>

  <div id="container">

  </div>

  <script type="text/babel">
    var destination = document.querySelector("#container");

    ReactDOM.render(
      <div>
        Hello!
      </div>,
      destination
    );
  </script>
</body>

</html>

如果在瀏覽器中預覽,會看到 “Hello!” 出現。下面,我們開始建立 Todo List 應用。

建立 UI

首先讓 UI 元素跑起來。這個應用的 UI 並不複雜。我們打算先讓輸入框和按鈕顯示出來。用 divformbutton 元素就可以搞定。

所有元素都會放在一個 TodoList 元件中。現在在 ReactDOM.render 方法中新增如下程式碼:

var TodoList = React.createClass({
  render: function() {
      return (
        <div className="todoListMain">
          <div className="header">
            <form>
              <input placeholder="enter task">
              </input>
              <button type="submit">add</button>
            </form>
          </div>
        </div>
      );
    }
});

在 ReactDOM.render 方法內,我們需要呼叫新新增的 TodoList 元件以渲染它。繼續,將已有的 JSX 用如下程式碼替換:

ReactDOM.render(
  <div>
    <TodoList/>
  </div>,
  destination
);

存檔,然後在瀏覽器中預覽。你會看到如下的介面:

如果你對你看到的感到驚訝,花幾分鐘來看看我們在 TodoList 元件中定義的 JSX。這裡應該沒有什麼令人吃驚的東西。我們只是定義了幾個看起來很無聊的 HTML 元素。說到這個,我們來引入一些 CSS,讓 HTML 元素看起來沒那麼無聊。

在 style 元素內,新增如下 CSS 程式碼:

body {
  padding: 50px;
  background-color: #66CCFF;
  font-family: sans-serif;
}
.todoListMain .header input {
  padding: 10px;
  font-size: 16px;
  border: 2px solid #FFF;
}
.todoListMain .header button {
  padding: 10px;
  font-size: 16px;
  margin: 10px;
  background-color: #0066FF;
  color: #FFF;
  border: 2px solid #0066FF;
}

.todoListMain .header button:hover {
  background-color: #003399;
  border: 2px solid #003399;
  cursor: pointer;
}

新增完上述程式碼後,預覽一下應用。因為我們的 HTML 元素設定有對應的 className 值,所以 CSS 就起作用了,我們的示例在瀏覽器中就是如下的樣子:

此時,我們的應用看起來很不錯,但是還不能做什麼。下一節我們開始讓應用實際做點事情。

建立功能

Todo List 應用功能的實際實現並不難。我們先對它如何工作做個概述。最重要的資料是在文字框中輸入的文字。每次輸入一些文字,然後提交表單時,該文字就會顯示在列表中以前你提交的文字之下。

這一切只需要利用 React 的狀態功能就可以實現。在 state 物件內,我們有一個數組負責儲存輸入的文字:

每次這個 items 陣列用你提交的新文字更新時,我們就用新提交的文字更新你看到的介面。剩下的工作只是圍繞著設定事件和事件處理器,以確保提交表單,知道什麼文字要新增到 items 陣列。在如下的小節中,我們打算將你在這裡看到的所有文字轉換為 React 喜歡的 JavaScript 和 JSX。

初始化 state 物件

我們要做的第一件事情,是用負責儲存所有提交了的文字的陣列來初始化 state 物件。在 TodoList 元件中,新增如下高亮度行:

var TodoList = React.createClass({
  getInitialState: function() {
    return {
      items: []
    };
  },
  render: function() {
      return (
        <div className="todoListMain">
          <div className="header">
            <form>
              <input placeholder="enter task">
              </input>
              <button type="submit">add</button>
            </form>
          </div>
        </div>
      );
    }
});

這裡我們所做的是,指定在元件渲染前要被呼叫的 getInitialState 生命週期方法。在該方法內,我們建立了一個 items 空陣列。此後,在該元件內的任何地方,我們都可以通過 this.state.items 訪問這個陣列。

處理表單提交

當按下 Add 按鈕或者在鍵盤上敲回車鍵後,我們將新條目新增到 “todo” 列表。這種行為通常是內建給 HTML,瀏覽器知道如何處理。我們不必為處理回車鍵盤或者監聽 Add 按鈕按下來寫任何特殊程式碼,只需要考慮一件事情,即處理表單提交後發生了什麼。

要這樣做,我們監聽表單元素上的 onSubmit 事件即可。該事件是在表單提交時觸發,包括敲回車鍵或者搗鼓帶有 type 屬性為 submit 的任何元素。當表單被提交,並且該事件被監聽到時,我們會需要呼叫一個事件處理器。我們給這個事件處理器命名為 addItem

把這些放在一起,在 TodoList 元件的 render 函式內,作出如下高亮度修改:

render: function() {
  return (
    <div className="todoListMain">
      <div className="header">
        <form onSubmit={this.addItem}>
          <input placeholder="enter task">
          </input>
          <button type="submit">add</button>
        </form>
      </div>
    </div>
  );
}

就像我們希望做的那樣,我們只是把 form 元素的 onSubmit 事件連結到 addItem 事件處理器。這個事件處理器還不存在,所以我們新增如下高亮度行:

var TodoList = React.createClass({
  getInitialState: function() {
    return {
      items: []
    };
  },
  addItem: function(e) {

  },
  render: function() {
    return (
      <div className="todoListMain">
        <div className="header">
          <form onSubmit={this.addItem}>
            <input placeholder="enter task">
            </input>
            <button type="submit">add</button>
          </form>
        </div>
      </div>
    );
  }
});

現在 addItem 事件處理器/函式 還不能做什麼,但是重要的是它已經存在了。下一步,我們就就讓它能做點事情。

填充 state 物件

現在,TodoLilst 元件的 state 物件包含了 items 陣列。我們要做的是用你在文字框中輸入的文字填充這個陣列。這意味著我們需要一種在 React 內訪問 input 元素的方法。我們打算要做的方法是,在 input 元素上設定一個 ref 屬性,並且將該引用儲存到要生成的 HTML 元素上。

在 TodoList 元件的 render 方法內,新增如下行:

render: function() {
    return (
      <div className="todoListMain">
        <div className="header">
          <form onSubmit={this.addItem}>
            <input ref={(a) => this._inputElement = a} placeholder="enter task">
            </input>
            <button type="submit">add</button>
          </form>
        </div>
      </div>
    );
  }

在該元件掛載之後,這段高亮度程式碼立即執行,_inputElement 屬性就會儲存一個對生成的 input 元素的引用。這樣,我們就可以將這個元素看作是在非 React 世界中用 querySelector 或者相似的函式找到的任何 DOM 元素。下一步我們就要填充 items 陣列了。

繼續,修改 addItem 方法,新增如下程式碼:

addItem: function(e) {
  var itemArray = this.state.items;

  itemArray.push(
    {
      text: this._inputElement.value,
      key: Date.now()
    }
  );

  this.setState({
    items: itemArray
  });

  this._inputElement.value = "";

  e.preventDefault();
}

這看起來跟很多我們剛新增的程式碼一樣,但是我們這裡所做的是,把我們早前明確的目標變成 JavaScript,即用輸入框的文字填充 items 陣列。我們更詳細地來看看這段程式碼。

我們做的第一件事情是建立一個名為 itemArray 的陣列,用來儲存一個對 state 物件的 items 屬性的引用:

var itemArray = this.state.items;

有了這個陣列後,就把來自輸入框的最近提交的文字新增到該陣列:

itemArray.push(
  {
    text: this._inputElement.value,
    key: Date.now()
  }
);

注意,我們並非新增來自 input 元素的文字條目,而是新增一個由 text 和 key 屬性組成的物件。text 屬性儲存 input 元素的文字值。key 屬性儲存當前時間。這聽起來像要做古怪的事情,但是回憶一下在 《在 React 中從資料到 UI 的旅行》這一章,目標是讓這個 key 值對於每個被提交的條目是唯一的。這是很重要的,因為我們將在這個陣列中使用資料,最終生成一些 UI 元素。這個 key 值會被 React 用來唯一識別每個生成的 UI 元素,所以,通過用 Date.now() 生成 key,我們就可以確保某種程度的唯一性。因為這是一個很重要的細節,所以我們將會在稍後會重訪所有這些。

不管怎樣,回到正軌,處理了 itemArray 後,剩下的就是設定 state 物件的 items 屬性為該陣列:

this.setState({
  items: itemArray
});

到這裡差不多完了!在這個方法中我們最後要做的一件事情如下:

e.preventDefault();

preventDefault 方法確保不會傳播 onSubmit 事件超過此界限。我們這樣做的原因有點難以理解,但是它確保:提交表單時所有想要做的是呼叫 addItem 方法。如果不中止事件傳播,我們的應用將會在提交表單時會根據需要正確呼叫 addItem它還會觸發瀏覽器的預設 POST 行為 - 這顯然是我們不想要的。通過阻止 onSubmit 事件跑到此界限以外,我們就得到了我們想要的行為,即呼叫 addItem 方法,而沒有不想要的副作用,比如不需要的會重新整理頁面的 POST 動作。

顯示任務

到這裡我們就快結束了。我們要做的最後一件事情是視覺化當前 state 物件的 items 陣列中的任務。包括建立一個全新的 TodoItems 元件,傳遞一些 props,使用 map 函式,和其它一些讓人興奮的事情:

不管怎樣,我們要做的第一件事情就是定義 TodoItems 元件。在 TodoList 元件定義的程式碼上面,繼續新增如下程式碼:

var TodoItems = React.createClass({
  render: function() {

  }
});

下一步,是在 TodoList 元件的 render 方法中呼叫該元件。不僅如此,我們還要指定一個 prop,將 TodoList 元件中包含 items 陣列的 state 物件傳遞進來。這些事情做起來很簡單,繼續在 TodoList 元件的 render 方法中新增如下高亮度行:

render: function() {
  return (
    <div className="todoListMain">
      <div className="header">
        <form onSubmit={this.addItem}>
          <input ref={(a) => this._inputElement = a}
                 placeholder="enter task">
          </input>
          <button type="submit">add</button>
        </form>
      </div>
      <TodoItems entries={this.state.items}/>
    </div>
  );
}

這裡我們例項化了 TodoItems 元件,並將 state 物件的 items 屬性傳遞給該元件的 entries 屬性。此時如果在瀏覽器中預覽,什麼都還看不到。TodoItems 元件準備要渲染,並且它可以訪問提交的所有任務。唯一的問題是,它實際上什麼事都沒做,下面我們再校正。

回到 TodoItems 元件,我們要做的第一件事情就是建立一個新變數,來儲存傳遞進來的任務陣列。

var TodoItems = React.createClass({
  render: function() {
    var todoEntries = this.props.entries; 
  }
});

這裡我們添加了一個 todoEntries 變數,它儲存基於 TodoList 元件的 this.state.items 的值傳遞進來的 entries 屬性的值。現在,todoEntries 變數儲存了一個數組,該陣列包含了一堆物件,每個物件儲存了一個任務和一個 key。剩下的就是建立用來顯示資料的 HTML 元素了。

首先,新增如下高亮度程式碼行來建立 li 元素:

var TodoItems = React.createClass({
  render: function() {
    var todoEntries = this.props.entries;

    function createTasks(item) {
      return <li key={item.key}>{item.text}</li>
    }

    var listItems = todoEntries.map(createTasks);
  }
});

這裡我們用 map 函式遍歷 todoEntries 中的每一個元素,並呼叫 createTasks 函式來為每個條目建立一個 li 元素:

function createTasks(item) {
  return <li key={item.key}>{item.text}</li>
}

重申我們早前的觀點,因為這些列表元素是動態建立的,我們需要通過指定 key 屬性並給每個條目一個唯一的值,來幫助 React 記錄它們。在最初儲存任務的時候,我們就已經解決了這部分問題:

itemArray.push(
  {
    text: this._inputElement.value,
    key: Date.now()
  }
);

因為前面的計劃,我們現在是將 key 屬性賦值為 todoEntries 陣列中已包含的每個條目的 item.keyli 元素的可見內容只是 item.text 儲存的文字值。如果使用它不需要額外的解釋。讓人耳目一新,對不對?

總體來說,li 元素的集合是全部由 listItems 變數/資料來處理和儲存。此時剩下的就是把陣列中的列表元素渲染在螢幕上。新增如下高亮度程式碼行:

var TodoItems = React.createClass({
  render: function() {
    var todoEntries = this.props.entries;

    function createTasks(item) {
      return <li key={item.key}>{item.text}</li>
    }

    var listItems = todoEntries.map(createTasks);

    return (
      <ul className="theList">
        {listItems}
      </ul>
    );
  }
});

現在我們正在做的是返回一個 ul 元素,該元素的內容是由 listItems 儲存的 li 元素。在新增上述程式碼後,儲存文件並預覽。鍵入幾個任務後,你會看到如下介面:

我們的應用跑起來了。每個提交的任務顯示在列表條目上。深呼吸,放鬆一下。這是個很棒的進展,我們只剩下一點小事情。

新增收尾工作

到這裡,我們已近完成了!首先,我們現在所有的與最開始的示例看起來並不是一樣的。任務列表看起來有點普通,但是我們可以加點 CSS 解決。在樣式塊中,在已有的樣式規則下新增如下樣式規則:

.todoListMain .theList {
  list-style: none;
  padding-left: 0;
  width: 255px;
}

.todoListMain .theList li {
  color: #333;
  background-color: rgba(255,255,255,.5);
  padding: 15px;
  margin-bottom: 15px;
  border-radius: 5px;
}

如果現在預覽一下,會看到輸入的任務和我們想要的一模一樣:

現在,你注意到沒有:在輸入框中輸入的內容在表單提交後不會消失。每次在提交一個任務後,你必須手動清除輸入框。這是很煩人的,但是解決它很簡單。在 TodoList 元件的 addItem 方法中,新增如下高亮度行:

addItem: function(e) {
  var itemArray = this.state.items;

  itemArray.push(
    {
      text: this._inputElement.value,
      key: Date.now()
    }
  );

  this.setState({
    items: itemArray
  });

  this._inputElement.value = "";

  e.preventDefault();
}

這裡我們在表單被提交以及 addItem 方法被呼叫時,清除 input 元素的 value 屬性。

總結

Todo 應用的功能相當簡單,但是通過從頭開始建立它,我們幾乎涵蓋了 React 帶來的每個有意思的細節。更重要的是,我們建立了一個示例,來展示我們分別學習的不同概念是如何配合的。這才是重要的細節。現在,我有一個簡單的問題:本教程中我們已經做的一切都理解了嗎?

如果本教程中我們做過的一切都理解了,那麼你可以驕傲地告訴你的朋友和家人你差不多掌握 React 了!如果你發現還有困惑的地方,建議重讀前面的內容。

React初學者系列教程

React入門案例