最近学完React的最基本概念,闲下来的时候就自己写了一个Todo-List的小应用。这里做个简略的说明,给想好好学React的新手看。
开始之前
这里我用了webpackb做了babel和JSX预处理和模块打包。所以对React和一些ES2015(ES6)的语法要有一定的了解。我相信学习ES2015绝对是划算的,因为它是Js的规范。这里给出学习的地方,阮一峰老师的ECMAScript 6 入门或者babel的相关文档Learn ES2015。
最后的实际效果:
我们需要做到的功能有:
- 可以在最上面的input里,使用回车来添加任务。
- 在中间的任务列表里,由checkBox来控制任务的状态。
- 已完成的任务有一个line-through的样式。
- 当鼠标移到每一个任务时,都会出现删除按钮提供删除。
- 在底部有一个全选按钮,用于控制所有的任务状态。
- 还有已完成与总数的显示。
- 可以清空已完成的任务。
上面就是一个Todo-List最基本的功能,而我们这次就是用React实现上述功能。例子在我的github上可以download下来,可以用作参考:
加载npm模块
终于要开始我们的React-Todo的项目了,首先我们就要新建项目,通过npm我们可以很轻松的创建项目,并加载我们所需要的各个组件。大家可以在自己的项目里,用我的package.json去加载所需要的模块。通过命令行进行安装。
这里提一下,因为我们这里仅仅是前端静态的,并不涉及到数据库。所以我自己写了一个非常简单的用于操作localStorage的小模块localDb。所以涉及到数据存储的时候,都是用localStorage来代替数据库。它的原理就是,通过将数据格式化成JSON字符串进行存储,使用的时候就解析JSON字符串。这个模块在我的github的例子里有,需要从那里复制一份来,放在node_modules的文件夹内。
配置webpack
经过一轮漫长的等待,我们终于安装好所需要的各个模块了。我们在开始我们的react的编码前,需要对webpack进行配置。关于webpack的学习,我这里就不赘述了,在前一篇刚讲完。下面直接看一看webpack.config.js。
entry: "./src/entry.js",output: {
path: path.join(__dirname,'out'),publicPath: './out/',filename: "bundle.js"
},externals: {
'react': 'React'
},module: {
loaders: [
{ test: /.js$/,loader: "jsx!babel",include: /src/},{ test: /.css$/,loader: "style!css"},{ test: /.scss$/,loader: "style!css!sass"},{ test: /.(jpg|png)$/,loader: "url?limit=8192"}
]
}
};
这里一切从简,可以看到入口文件是在src文件夹里的entry.js,然后输出文件放在out文件夹的bundle.js里。
配置一下模块的loaders,先用babel-loader再用jsx-loader。这样子我们就可以让ES6配合JSX编写我们的React组件了。其它的加载器也没什么好说的了,如果不清楚可以翻我上一篇关于webpack的文章。
这里提一下externals属性,这个属性是告诉webpack当遇到require('react')的时候,不去处理并且默认为全局的React变量。这样子,我们就需要在index.html单独用src去加载js。
分析各个组件
App组件
我这里并不会教大家手把手将这个React-Todo做出来,但是可以结合例子进行分析理解。先来看看总的组件,也就是App。
import TodoMain from "./TodoMain.js";
import TodoFooter from "./TodoFooter.js";
class App extends React.Component {
constructor(){
super();
this.db = new LocalDb('React-Todos');
this.state = {
todos: this.db.get("todos") || [],isAllChecked: false
};
}
// 判断是否所有任务的状态都完成,同步底部的全选框
allChecked(){
let isAllChecked = false;
if(this.state.todos.every((todo)=> todo.isDone)){
isAllChecked = true;
}
this.setState({todos: this.state.todos,isAllChecked});
}
// 添加任务,是传递给Header组件的方法
addTodo(todoItem){
this.state.todos.push(todoItem);
this.allChecked();
this.db.set('todos',this.state.todos);
}
// 改变任务状态,传递给TodoItem和Footer组件的方法
changeTodoState(index,isDone,isChangeAll=false){
if(isChangeAll){
this.setState({
todos: this.state.todos.map((todo) => {
todo.isDone = isDone;
return todo;
}),isAllChecked: isDone
})
}else{
this.state.todos[index].isDone = isDone;
this.allChecked();
}
this.db.set('todos',this.state.todos);
}
// 清除已完成的任务,传递给Footer组件的方法
clearDone(){
let todos = this.state.todos.filter(todo => !todo.isDone);
this.setState({
todos: todos,isAllChecked: false
});
this.db.set('todos',todos);
}
// 删除当前的任务,传递给TodoItem的方法
deleteTodo(index){
this.state.todos.splice(index,1);
this.setState({todos: this.state.todos});
this.db.set('todos',this.state.todos);
}
render(){
var props = {
todoCount: this.state.todos.length || 0,todoDoneCount: (this.state.todos && this.state.todos.filter((todo)=>todo.isDone)).length || 0
};
return (
<div className="panel">