用React实现一个Todo Demo

目录

  1. 初始化项目
  2. 组件化
  3. 添加待办事项
  4. 删除待办事项
  5. 切换事项完成状态
  6. 重命名待办事项
  7. 统计数量

    初始化项目

    使用官方工具create-react-app创建一个新项目react-todo
    1
    2
    3
    create-react-app react-todo
    cd /react-todo
    npm start

若启动成功,浏览器会自动打开http://localhost:3000,可以看到一个默认生成的项目。

组件化

在着手开始写一个React项目时,我们先会将组件进行抽象化,以提高开发效率和可维护性。
/src文件夹下新建一个/Components文件夹,并新添加TodoItem.jsTodoList.js两个文件。

1
2
3
4
|____src
| |____components
| | |____TodoItem.js
| | |____TodoList.js

编辑TodoItem.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, { Component } from 'react';
class TodoList extends Component {
constructor(props) {
super(props);
this.handleRemove = this.handleRemove.bind(this); // 删除事项
this.toggleComplete = this.toggleComplete.bind(this); // 切换状态
this.handleChange = this.handleChange.bind(this); // 处理输入
this.handleEdit = this.handleEdit.bind(this); // 编辑事项
this.handleSure = this.handleSure.bind(this); // 确认修改
this.handleCancel = this.handleCancel.bind(this); // 取消修改
}

// 切换事项状态
toggleChange() {}

// 处理输入框的输入
handleChange() {}

// 编辑待办事项
handleEdit() {}

// 确认修改
handleSure() {}

// 取消修改
handleCancel() {}

// 删除事项
handleRemove() {}

render() {
return (
<li> </li>
);
}
}

export default TodoItem;

编辑TodoList.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react';
import TodoItem from './TodoItem.js';

class TodoList extends Component {
render() {
return (
<ul>
<TodoItem />
</ul>
);
}
}

export default TodoList;

修改App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import TodoList from './components/TodoList.js';
import './App.css';

class App extend Component {
constructor(props) {
super(props);
this.handleAdd = this.handleAdd.bind(this); // 添加
this.handleRemove = this.handleRemove.bind(this); // 删除
this.handleToggleComplete = this.handleToggleComplete.bind(this); // 切换状态
this.handleRename = this.handleRename.bind(this); // 重命名
}

// 切换待办事项完成状态
handleComplete() {}

// 添加待办事项
handleAdd() {}

// 删除待办事项
handleRemove() {}

// 重命名
handleRename() {}

render() {
return (
<div>
<h1>Todo List Demo</h1>
<header>
<input type="text" ref="taskname" />
&nbsp;&nbsp;
<button onClick={this.handleAdd}>
Add Todo
</button>
</header>
<TodoList />
<footer></footer>
</div>
);
}
}

export default App;

现在,项目基本框架已经搭建完成。

添加待办事项

修改App.js

首先规定待办事项的数据结构如下:

1
2
3
4
5
6
7
8
9
todos: [{
id: 001,
name: 'task 1',
isCompleted: false
}, {
id: 002,
name: 'task 2',
isCompted: true
}]

修改App.js构造函数:

1
2
3
4
5
6
7
8
9
10
11
constructor(props) {
super(props);
this.handleAdd = this.handleAdd.bind(this); // 添加
this.handleRemove = this.handleRemove.bind(this); // 删除
this.handleToggleComplete = this.handleToggleComplete.bind(this); // 切换状态
this.handleRename = this.handleRename.bind(this); // 重命名

set.state = {
todo: []
};
}

添加待办事项需要生成一个随机四位数作为ID,为App.js添加generateId()函数:

1
2
3
4
// 生成taskId
generateId() {
return Math.floor(Math.random() * 9000) + 1000;
}

完成App.jshandleAdd()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
handleAdd() {
var taskName = ReactDOM.findDOMNode(this.refs.taskname).value.trim();
if (!taskName) {
return '';
}
var taskId = generateId(); // 初始化taskID
var todos = this.state.todos;
todos.push({
id: taskId,
name: taskName,
isCompleted: false
});
this.setSate({ todos: todos });
}

接下来要显示待办事项,修改App.js

1
<TodoList todos={this.state.todo} />

修改TodoList.js

现在要在TodoList中显示所有TodoItem,并为TodoItem添加一些属性,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
render() {
return (
<ul>
{
this.props.todos.map((todo, index) => {
return (
<TodoItem
taskId={todo.id}
key={todo.id}
name={todo.name}
isCompleted={todo.isCompleted}
/>
);
})
}
</ul>
);
}

修改TodoItem.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
render() {
var { taskId, name, isCompleted } = this.props,
operation = '';
if (isCompleted) {
operation = <s>{name}</s>
} else {
<b>{name}</b>
}

return (
<li key={taskId}>
{operation}
</li>
);
}

删除待办事项

修改App.js

修改handleRemoveTask()如下:

1
2
3
4
5
6
7
handleRemoveTask(taskId) {
var todos = this.state.todos;
todos = todos.filter((task) => {
return task.id !== taskId;
});
this.setState({ todos: todos });
}

绑定handleRemoveTask

1
2
3
4
<TodoList
todos={this.state.todos}
removeTask={this.handleRemoveTask}
/>

修改TodoList.js

1
2
3
4
5
6
7
8
9
return (
<TodoItem
taskId={todo.id}
key={todo.id}
name={todo.name}
isCompleted={todo.isCompleted}
removeTask={this.props.removeTask}
/>
);

修改TodoItem.js

修改handleRemove()

1
2
3
handleRemove() {
this.props.removeTask(this.props.taskId);
}

添加Remove Button

1
2
3
4
5
6
7
8
return (
<li key={taskId}>
{operation}
<button onClick={this.handleRemove}>
Remove
</button>
</li>
);

切换事项完成状态

修改App.js

修改handleToggleComplete()

1
2
3
4
5
6
7
8
9
10
handleToggleComplete(taskId) {
var todos = this.state.todos;
for (var i in todos) {
if (todos[i].id === taskId) {
todos[i].isCompleted = !todos[i].isCompleted;
break;
}
}
this.setState({ todos: todos });
}

添加toggleComplete属性:

1
2
3
4
5
<TodoList 
todos={this.state.todos}
removeTask={this.handleRemoveTask}
toggleComplete={this.handleToggleComplete}
/>

修改TodoList.js

1
2
3
4
5
6
7
8
9
10
return (
<TodoItem
taskId={todo.id}
key={todo.id}
name={todo.name}
isCompleted={todo.isCompleted}
removeTask={this.props.removeTask}
toggleComplete={this.props.toggleComplete}
/>
);

修改TodoItem.js

修改toggleComplete()

1
2
3
toggleComplete() {
this.props.toggleComplete(this.props.taskId);
}

添加checkbox来表示是否完成:

1
2
3
4
5
6
7
8
9
10
11
return (
<li key={taskId}>
<input type="checkbox" checked={isCompleted} onChange={this.toggleComplete} />
&nbsp;&nbsp;
{operation}
&nbsp;&nbsp;
<button onClick={this.handleRemove}>
Remove
</button>
</li>
);

重命名待办事项

规定只有未完成的项目可以被重命名和删除,已完成的项目则不可以。

修改App.js

修改handleRename()

1
2
3
4
5
6
7
8
9
10
handleRename(taskId, name) {
var todos = this.state.todos;
for (var i in todos) {
if (todos[i].id === taskId) {
todos[i].name = name;
break;
}
}
this.setState({ todos: todos });
}

TodoList添加rename属性:

1
2
3
4
5
6
<TodoList 
todos={this.state.todos}
removeTask={this.handleRemoveTask}
toggleComplete={this.handleToggleComplete}
rename={this.handleRename}
/>

修改TodoList.js

1
2
3
4
5
6
7
8
9
10
return (
<TodoItem
taskId={todo.id}
key={todo.id}
name={todo.name}
isCompleted={todo.isCompleted}
removeTask={this.props.removeTask}
rename={this.props.rename}
/>
);

修改TodoItem.js

修改构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
constructor(props) {
super(props);
this.handleRemove = this.handleRemove.bind(this);
this.toggleComplete = this.toggleComplete.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleEdit = this.handleEdit.bind(this);
this.handleSure = this.handleSure.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.state = {
isEditing: false,
inputValue: this.props.name
};

修改handleEdit()handleSure()handleCancel()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
handleEdit() {
this.setState({
isEditing: true
});
}

handleSure() {
this.props.rename(this.props.taskId, this.state.inputValue);
this.setState({
isEditing: false
});
}

handleCancel() {
this.setState({
isEditing: false,
inputValue: this.props.name
});

render()添加Edit, Sure, Cancel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
render() {
var { taskId, name, isCompleted } = this.props,
operation = '';
if (isCompleted) {
operation = <s>{name}</s>;
} else {
if (this.state.isEditing) {
operation =
<span>
<input type="text" value={this.state.inputValue} onChange={this.handleChange} />
&nbsp;&nbsp;
<button onClick={this.handleSure}>
Sure
</button>
&nbsp;&nbsp;
<button onClick={this.handleCancel}>
Cancel
</button>
</span>
} else {
operation =
<span>
<b>{name}</b>
&nbsp;&nbsp;
<button onClick={this.handleEdit}>
Edit
</button>
</span>
}
}

return (
/*和前面一样,省略了*/
);
}

统计数量

修改App.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
render() {
var statistics = {
todoCount: this.state.todos.length || 0,
todoCompleteCount: this.state.todos.filter((todo) => {
return todo.isCompleted;
}).length
};

return (
<div>
<h1>Todo List Demo</h1>
<header>
<input type="text" ref="taskname" />&nbsp;&nbsp;
<button onClick{this.handleAdd}>Add Todo</button>
</header>
<TodoList
todos={this.state.todos}
removeTask={this.handleRemoveTask}
toggleComplete={this.handleToggleComplete}
rename={this.handleRename}
/>
<footer>
{statistics.todoCompleteCount}已完成/{statistics.todoCount}总数
</footer>
</div>
);
}