【转载】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 并不复杂。我们打算先让输入框和按钮显示出来。用
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初学者系列教程
- 第 1 篇: React 简介
- 第 2 篇: 创建第一个 React 应用
- 第 3 篇: React 中的组件
- 第 4 篇: 在 React 中设置样式
- 第 5 篇: 创建复杂的组件
- 第 6 篇: 传递属性
- 第 7 篇: 深入JSX
- 第 8 篇: 处理状态
- 第 9 篇: 从数据到 UI
- 第 10 篇: React 中的事件
- 第 11 篇: 组件生命周期
- 第 12 篇: 用 React Router 创建单页应用
- 第 13 篇: 用 React 常见一个简单的 Todo List 应用
- 第 14 篇: 在 React 中访问 DOM 元素
- 第 15 篇: 设置 React 开发环境