一构建开发环境
todolist的开发工具包为webpack+es6+react,首先需要构建开发环境,我在上一篇文章webpack构建ant-design中已经构建了ant-design的开发环境,这里也是用到这边文章中所用的环境,在这个实例中需要用到箭头函数,所以还需要安装一下 stage-0,安装语句如下:
npm install --save-dev babel-preset-stage-0
修改.babelrc文件如下
{ "presets": [ "es2015", "react", "stage-0" ], "sourceMaps": true }
二分析todolist结构
完成版的todolist如上图所示,按照react组件化编程的风格,我大致将todolist分为三个组件,分别是TodoHeader,用来输入任务名称,TodoMain,用来展示任务,TodoFooter,用来全选和清楚选中的任务,我创建的文件,及问价目录如下
三 输入,存储任务
index.js作为webpack打包的入口文件,App作为主要的组件来引用上面三个组件。
index.js
require("./style/index.less"); import ReactDom from 'react-dom'; import React from 'react'; import App from '../src/component/App'; ReactDom.render(, document.getElementById('root'));
App.jsimport React from 'react';import TodoHeader from './TodoHeader';
import TodoMain from './TodoMain'; import TodoFooter from './TodoFooter'; class App extends React.Component{ constructor(props){ super(props); this.db = localStorage; //定义一个localStorage容易 let todos = this.db.getItem('todos') || []; //通过键todos,来获取localStorage容器中的值 this.state = { todos: eval('(' + todos + ')'), isAllChecked: false //用来表示是否全选 }; } componentDidMount(){ // let todos = eval('(' + this.state.todos + ')'); // if(todos.length > 0){ // this.setState({todos: todos}); // } } allChecked() {
//全选函数,供子组件调用 let isAllChecked = false;
// every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
if(this.state.todos.every((todo) => todo.isDone)){ isAllChecked = true; } this.setState({ todos: this.state.todos, isAllChecked: isAllChecked }) } clearDOne= () =>{
//清除选中的项目 let todos = this.state.todos.filter(todo => !todo.isDone); //过滤没有选中的项目 this.setState({ todos: todos, isAllChecked: false, }); this.db.setItem('todos', JSON.stringify(todos)); //修改localStorage中键todos的值,以json数组的形式存储,方便后续取出操作 } addTodo =(todoItem) =>{
//添加项目操作 this.state.todos.push(todoItem);//todo列表 ,向todos数组中插入一条记录 this.db.setItem('todos', JSON.stringify(this.state.todos)); //将所有的记录再次保存如localStorage中 this.allChecked(); } deleteTodo = (index) =>{
//删除一个项目 this.state.todos.splice(index, 1);//通过传过来的索引值,删除数组中的一条记录 this.setState({todos: this.state.todos}); // this.db.setItem('todos', JSON.stringify(this.state.todos));//将更新的todos数组,插入到localStorage中 } changeTodoState = (index, isDone, isChangeAll = false) => { // 改变任务状态,传递给TodoItem和Footer组件的方法 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.setItem('todos', JSON.stringify(this.state.todos)); } render(){ let info = { isAllChecked: this.state.isAllChecked, todoCount: this.state.todos.length || 0, todoDoneCount: (this.state.todos && this.state.todos.filter((todo) => todo.isDone)).length || 0 }; return(); } } export default App;
在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了COOKIE存储空间不足的问题(COOKIE中每条COOKIE的存储空间为4k),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。
三组件TodoHeader
,组件TodoHeader的代码如下,用户在输入框中输入,点击回车按钮,保存用户的输入到localStorage中去。
import React from 'react'; class TodoHeader extends React.Component{ constructor(props){ super(props); this.state = { }; this.handlerKeyUp = this.handlerKeyUp.bind(this); //绑定键盘事件,也可以使用箭头函数 } componentDidMount(){ } handlerKeyUp(e){ //鼠标回车事件 if(e.keyCode == 13){ //键盘敲击回车时间 let value = e.target.value; //获取输入框中输入的值 if(!value) return false; //如果输入框没有输入,则返回false let newTodoItem = { text: value, isDone: false };//将输入的值保存在一个对象中 e.target.value = '';//将输入框的值置空 this.props.addTodo(newTodoItem) //调用父组件的方法,通过this.props调用父控件的方法,将输入的值传递给父控件,在父控件中对state的值进行修改,从而重新渲染页面。 } } render() { return() } }; export default TodoHeader;React Todo
四 展示组件TodoMain
定义一个组件TodoItem,来处理具体某一条的记录,然后再TodoMain中来处理出所有的数据
TodoItem.js代码如下
import React from 'react'; export default class TodoItem extends React.Component{ constructor(props){ super(props); this.state = { isShow: false, }; } componentDidMount(){ } handlerChange =()=>{
//多选框处理事件,选中则表示已处理 let isDOne= !this.props.isDone; this.props.changeTodoState(this.props.index, isDone); } handlerDelete =()=>{
//删除事件 this.props.deleteTodo(this.props.index); } handlerMouseOver =()=>{
//鼠标移入事件 this.setState({isShow:true}); } handlerMouseOut =() =>{
//鼠标移出事件 this.setState({isShow:false}); } render(){ let className = this.props.isDone? 'task-done': ''; return(
TodoMain.js代码如下
import React from 'react'; import TodoItem from './TodoItem'; export default class TodoMain extends React.Component{ constructor(props){ super(props); this.state = { }; } componentDidMount(){ } render() { if(this.props.todos.length == 0){ return(恭喜您,目前没有待办事项!) }else{ return (
{..this.props}将App中的方法,通过props传递给组件TodoIten中
五 TodoFooter
TodoFooter代码如下
import React from 'react'; export default class TodoFooter extends React.Component{ constructor(props){ super(props); this.state = { }; } componentDidMount(){ } handlerSelectAll =(e) =>{
//全选事件,调用父组件的全选事件 this.props.changeTodoState(null, e.target.checked, true); } handlerDeleteDOne= () => {
//清除已选任务操作,调用父组件的事件 this.props.clearDone(); } render(){ let count = this.props.todoCount; if(count > 0){ return() }else{ return ( ) } } }
六项目中用到的样式
样式文件放在style/index.less文件中
body { font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; font-size: 14px; line-height: 1.42857143; color: #333; background-color: #fff; } #root{ width: 1170px; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } .todo-header{ .form-horizontal .form-group { margin-right: -15px; margin-left: -15px; } .form-group { margin-bottom: 15px; } .form-horizontal .control-label { padding-top: 7px; margin-bottom: 0; text-align: right; } .col-md-2 { float: left; width: 16.66666667%; } .col-md-10{ width: 83.33333333%; } .form-control { display: block; width: 100%; height: 34px; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; color: #555; background-color: #fff; background-image: none; border: 1px solid #ccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); box-shadow: inset 0 1px 1px rgba(0,0,0,.075); -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; } } .todo-main{ padding-left: 0; margin-bottom: 20px; .item{ position: relative; display: block; padding: 10px 15px; margin-bottom: -1px; background-color: #fff; border: 1px solid #ddd; .pull-left{ float: left!important; } } .list-group-item-success{ color: #3c763d; background-color: #dff0d8; } } .todo-main:first-child{ border-top-left-radius: 4px; border-top-right-radius: 4px; } .todo-footer{ margin-bottom: 40px; .clearTask{ display: inline-block; margin-left: 20px; .btn{ color: #fff; background-color: #337ab7; border-color: #2e6da4; } .btn:hover { color: #fff; background-color: #286090; border-color: #204d74; } } } .todo-empty{ } input[type=checkbox], input[type=radio] { margin: 4px 0 0; margin-top: 1px\9; line-height: normal; } input[type="checkbox"] { margin-right: 10px; } button.close { display: none; font-size: 12px; height: 18px; width: 18px; } button.close { -webkit-appearance: none; padding: 0; cursor: pointer; background: 0 0; border: 0; } .close { float: right; font-size: 21px; font-weight: 700; line-height: 1; color: #000; text-shadow: 0 1px 0 #fff; filter: alpha(opacity=20); opacity: .2; } .btn-group-xs>.btn, .btn-xs { padding: 1px 5px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-image: none; border: 1px solid transparent; border-radius: 4px; } .todo-wrap{ min-height: 20px; padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.05); box-shadow: inset 0 1px 1px rgba(0,0,0,.05); margin-left: 25%; } .todo-empty{ text-align: center; } .text-center { text-align: center; } .h1, h1 { font-size: 36px; }