β

【转载】React 初学者教程13:用 React 创建一个简单的 Todo List 应用

W3CPlus 58 阅读

本文转载自: 众成翻译 译者: 网络埋伏纪事 链接: 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 并不复杂。我们打算先让输入框和按钮显示出来。用 div form button 元素就可以搞定。

所有元素都会放在一个 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.key li 元素的可见内容只是 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初学者系列教程

发表评论