当前位置: 首页 > 编程日记 > 正文

React-Todos

最近学完React的最基本概念,闲下来的时候就自己写了一个Todo-List的小应用。这里做个简略的说明,给想好好学React的新手看。

React-Todo


学习前提

这里我用了webpackb做了babel和JSX预处理和模块打包。所以对React和一些ES2015(ES6)的语法要有一定的了解。我相信学习ES2015绝对是划算的,因为它是Js的规范。这里给出学习的地方,阮一峰老师的ECMAScript 6 入门或者babel的相关文档Learn ES2015。

功能需求

最后的实际效果:

效果图

我们需要做到的功能有:

  • 可以在最上面的input里,使用回车来添加任务。
  • 在中间的任务列表里,由checkbox来控制任务的状态。
  • 已完成的任务有一个line-through的样式。
  • 当鼠标移到每一个任务时,都会出现删除按钮提供删除。
  • 在底部有一个全选按钮,用于控制所有的任务状态。
  • 还有已完成与总数的显示。
  • 可以清空已完成的任务。

项目下载

上面就是一个Todo-List最基本的功能,而我们这次就是用React实现上述功能。例子在我的github上可以download下来,可以用作参考:React-Todos

正式开始


加载npm模块

重要要开始我们的React-Todo的项目了,首先我们就要新建项目,通过npm我们可以很轻松的创建项目,并加载我们所需要的各个组件。大家可以在自己的项目里,用我的package.json去加载所需要的模块。通过命令行进行安装。

$ npm install

这里提一下,因为我们这里仅仅是前端静态的,并不涉及到数据库。所以我自己写了一个非常简单的用于操作localStorage的小模块localDb。所以涉及到数据存储的时候,都是用localStorage来代替数据库。它的原理就是,通过将数据格式化成JSON字符串进行存储,使用的时候就解析JSON字符串。这个模块在我的github的例子里有,需要从那里复制一份来,放在node_modules的文件夹内。

配置webpack

经过一轮漫长的等待,我们终于安装好所需要的各个模块了。我们最开始我们的react的编码前,需要对webpack进行配置。关于webpack的学习,我这里就不赘述了,在前一篇刚讲完。下面直接看一看webpack.config.js

// webpack.config.js
var path = require('path');module.exports = {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 React from "react";
import LocalDb from "localDb";import TodoHeader from "./TodoHeader.js";
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"><TodoHeader addTodo={this.addTodo.bind(this)}/><TodoMain deleteTodo={this.deleteTodo.bind(this)} todos={this.state.todos} changeTodoState={this.changeTodoState.bind(this)}/><TodoFooter isAllChecked={this.state.isAllChecked} clearDone={this.clearDone.bind(this)} {...props} changeTodoState={this.changeTodoState.bind(this)}/></div>)}
}
React.render(<App/>, document.getElementById("app"));

用ES6写React最大的不同就是,组件可以通过继承React.Components来得到,并且初始化state也不需要冗长的getInitalialState,直接在构造函数里操作this.state即可。更优秀的便是...spread扩展操作符,可以让我们省下一堆不必要的代码,这个接下来再说。

App状态state

我们知道React的主流思想就是,所有的state状态和方法都是由父组件控制,然后通过props传递给子组件,形成一个单方向的数据链路,保持各组件的状态一致。所以我们在这个父组件App上,看的东西稍微有点多。一点点来看:

constructor(){super();this.db = new LocalDb('React-Todos');this.state = {todos: this.db.get("todos") || [],isAllChecked: false};
}

在App组件的constructor内,我们先是初始化了我们的localStorage的数据库,放在了this.db上。然后便是初始化了state,分别有两个,一个是todos的列表,一个是所有的todos是否全选的状态。

App方法

// 判断是否所有任务的状态都完成,同步底部的全选框
allChecked()// 添加一个任务,参数是一个todoItem的object
addTodo(todoItem)// 改变任务的状态,index是第几个,isDone是状态,isChangeAll是控制全部状态的
changeTodoState(index, isDone, isChangeAll=false) // 参数默认位false// 清空已完成
clearDone()// 删除面板上第几个任务
deleteTodo(index)// react用于渲染的函数
render(){<div className="panel"><TodoHeader /><TodoMain /><TodoFooter /></div>
}

我们可以从render函数看到整个组件的结构,可以看到其实结构非常简单,就是上中下。上面的TodoHeader自然就是用来输入任务的地方,中间就是展示并操作todo-list的,而底部就是显示数据并提供特殊操作。这里还是要提醒一句,所有标签都必须闭合,即使是非结对的,也要用斜杠闭合上。

    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"><TodoHeader addTodo={this.addTodo.bind(this)}/><TodoMain deleteTodo={this.deleteTodo.bind(this)} todos={this.state.todos} changeTodoState={this.changeTodoState.bind(this)}/><TodoFooter isAllChecked={this.state.isAllChecked} clearDone={this.clearDone.bind(this)} {...props} changeTodoState={this.changeTodoState.bind(this)}/></div>)}

我们可以看到,其他的方法都是传到子组件上,就不一一详细说如何实现的了。总体的思想就是,方法在父组件定义,通过props传给需要的子组件进行调用传参,最后返回到父组件上执行函数,存储数据、改变state和重新render。方法需要bind(this),不然方法内部的this指向会不正确。

计算需要的数据后,通过props传递到子组件。如果细心的同学应该可以看到像这样的{...props},这就是我之前说过的spread操作符。如果我们没有用这个操作符,就要这样写:

<TodoFooter {...props} /> // spread操作符
<TodoFooter todoCount={props.todoCount} todoDoneCount={props.todoDoneCount} />

最佳的实践就是,当父组件传props给子组件,然后子组件要将props转发给孙子组件的时候,spread操作符简直让人愉悦!可以对一堆麻烦又丑又长的代码可以say goodbye了!

最后我们将整个App渲染到DOM上即可。

React.render(<App/>, document.getElementById("app"));

AppHeader组件

import React from "react";class TodoHeader extends React.Component {// 绑定键盘回车事件,添加新任务handlerKeyUp(event){if(event.keyCode === 13){let value = event.target.value;if(!value) return false;let newTodoItem = {text: value,isDone: false};event.target.value = "";this.props.addTodo(newTodoItem);}}render(){return (<div className="panel-header"><input onKeyUp={this.handlerKeyUp.bind(this)} type="text" placeholder="what's your task ?"/></div>)}
}export default TodoHeader;

到了子组件,方法就没那么多了,一般子组件就是绑定事件。可以看到在子组件绑定了keyUp事件,用来确定回车键并调用父组件传来的addTodo(),将新生成的todo任务作为参数传入。

AppFooter组件

import React from "react";
export default class TodoFooter extends React.Component{// 处理全选与全不选的状态handlerAllState(event){this.props.changeTodoState(null, event.target.checked, true);}// 绑定点击事件,清除已完成handlerClick(){this.props.clearDone();}render(){return (<div className="clearfix todo-footer"><input checked={this.props.isAllChecked} onChange={this.handlerAllState.bind(this)} type="checkbox" className="fl"/><span className="fl">{this.props.todoDoneCount}已完成 / {this.props.todoCount}总数</span><button onClick={this.handlerClick.bind(this)} className="fr">清除已完成</button></div>)}
}

我们先来看看这个footer上有哪些方法。第一个就是处理todo状态的,它通过底部的checkbox的change事件触发。然后就是清空已完成的按钮的点击事件的方法handlerClick()。然后下面的数据显示,就通过props的值进行显示。

TodoMain

import React from "react";
import TodoItem from "./TodoItem.js"export default class TodoMain extends React.Component{// 遍历显示任务,转发propsrender(){return (<ul className="todo-list">{this.props.todos.map((todo, index) => {return <TodoItem key={index} {...todo} index={index} {...this.props}/>})}</ul>)}
}

Main组件的作用就是,将props传过来的todos遍历显示出来。所以对每一个todo的细致操作都是放在TodoItem上。

TodoItem

import React from "react";
export default class TodoItem extends React.Component{// 处理任务是否完成状态handlerChange(){let isDone = !this.props.isDone;this.props.changeTodoState(this.props.index, isDone);}// 鼠标移入handlerMouseOver(){React.findDOMNode(this.refs.deleteBtn).style.display = "inline";}// 鼠标移出handlerMouseOut(){React.findDOMNode(this.refs.deleteBtn).style.display = "none";}// 删除当前任务handlerDelete(){this.props.deleteTodo(this.props.index);}render(){let doneStyle = this.props.isDone ? {textDecoration: 'line-through'} : {textDecoration: 'none'};return (<lionMouseOver={this.handlerMouseOver.bind(this)}onMouseOut={this.handlerMouseOut.bind(this)}><input type="checkbox" checked={this.props.isDone} onChange={this.handlerChange.bind(this)}/><span style={doneStyle}>{this.props.text}</span><button style={{'display': 'none'}} ref="deleteBtn" onClick={this.handlerDelete.bind(this)}  className="fr">删除</button></li>)}
}

在TodoItem主要处理多个交互,包括修改任务状态,删除任务。还有就是鼠标移到相应的任务上才显示删除按钮。

我们可以看到render()函数,是控制了任务的样式。标签内的style是需要接受一个对象的,所以所有的CSS属性名,都要变成驼峰形的。

总结


其实真正的回过头看React-Todos,会觉得React带给我们的组件化的思想用起来太舒服了。我们通过父组件来控制状态,并通过props传递,来保证组件内的状态一致。我们可以非常有效的维护我们的交互代码,因为我们一眼就知道,这个事件属于哪个组件管理。它的模型其实非常轻,只有View层,但是它带给我们全新的书写前端组件的方法是非常好的,我个人认为如果未来的站点交互性愈来愈多,React是很有可能代替JQuery成为必备的技能。

转载于:https://www.cnblogs.com/YikaJ/p/4586704.html

相关文章:

Ant Design 入门-引用自己命名的组件

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 自己创建的组件:代码 import { Table, Divider, Tag } from antd; import React, { Component } from react; export default class My_Table extends Component {render() {const columns = [{title: …

迷宫出路代码_如何在软件开发的迷宫中找到自己的出路

迷宫出路代码by Tim Kleier蒂姆克莱尔(Tim Kleier) 如何在软件开发的迷宫中找到自己的出路 (How to find your way through the corn maze of software development) The corn maze is one of my favorite challenges to tackle. It’s an unnerving experience, especially w…

打包 React 项目并在服务器运行。

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 1.找到项目根目录的package.json文件&#xff1a;如图&#xff1a; 2.打开cmd执行&#xff1a;npm run build 3.生成DIST文件夹。 4.把DIST文件放到服务器phpStudty根目录&#xff0c;访问index.html。…

一些有用的Python问题

1. 修改IDLE工作路径&#xff0c;在命令交互模式下输入如下指令&#xff1a; >>> import os >>> os.getcwd() #查看当前的工作路径 >>> os.chdir(E:\\Python\\Demo) #修改当前的工作路径 2.Python中 ImportError: cannot import name NUMPY_MKL 的…

Python核心编程笔记---- print

在仅用变量名时&#xff0c;输出的字符串是用单引号括起来的。这个是为了让非字符串对象也可能以字符的形式显示在屏幕上。 而print 函数打印出来的是变量的值。 print 调用的是str()方法。而仅用变量名时调用的是repr()方法。 证明&#xff1a;------------------------------…

latex 插图解释_大O符号-只需插图和视频即可解释

latex 插图解释Big O notation is used to communicate how fast an algorithm is. This can be important when evaluating other people’s algorithms, and when evaluating your own! In this article, I’ll explain what Big O notation is and give you a list of the m…

[YTU]_2002(C语言实验——单词统计)

Description 从键盘输入一行字符&#xff0c;统计其中单词的个数&#xff0c;各单词以空格分隔&#xff0c;且空格数可以是多个。Input 输入只有一行句子。仅有空格和英文字母构成。 Output 单词的个数。 Sample Input stable marriage problem Consists of Matching memb…

资本中国人物-金融

一、二、三、店、五、土地、七、八、玖、拾起、白、千、一万、一亿、元(圆)、角、支、零、整个。这是上图中我们经常要填写。问&#xff1a;什么是它用数词&#xff1f;想必很多人都不是很清楚&#xff01; 请看下面的两个相关的表数词&#xff1a; 1、数字化和大、小写数字对照…

Ant Design Pro 网络请求流程

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 在 Ant Design Pro 中&#xff0c;一个完整的前端 UI 交互到服务端处理流程是这样的&#xff1a; UI 组件交互操作&#xff1b; 调用 model 的 effect&#xff1b; 调用统一管理的 service 请求函数&a…

在Google Cloud Platform上持续部署Node.js

by Gautam Arora由Gautam Arora 在Google Cloud Platform上持续部署Node.js (Continuous Deployment for Node.js on the Google Cloud Platform) Google Cloud Platform (GCP) provides a host of options for Node developers to easily deploy our apps. Want a managed ho…

hdu-1108 最小公倍数

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid1108 题目类型&#xff1a; 数论 题意概括&#xff1a; 求两个数的最小公倍数 解题思路&#xff1a; 模拟 题目&#xff1a; 最小公倍数 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/O…

sql-schema与catalog

schema&#xff1a; 指的是说当偶create database caiceclb时&#xff0c;caiceclb就是一个schema catalog&#xff1a; 指的是所有的database目录&#xff0c;就像上图显示的那样&#xff0c;将MySQL原来的&#xff08;mysql,infomation_schema&#xff09;及后来新建的的data…

这是如何更好地利用JavaScript数组的方法

by pacdiv由pacdiv 这是如何更好地利用JavaScript数组的方法 (Here’s how you can make better use of JavaScript arrays) Quick read, I promise. Over the last few months, I noticed that the exact same four mistakes kept coming back through the pull requests I c…

07、C语言——函数

函数 1、函数定义 函数返回值类型 函数名&#xff08;形式参数列表&#xff09;      {         函数体&#xff1b;      } 注意&#xff1a; 定义有参函数时&#xff0c;形参的定义可以采用传统方式或现代方式两种 1&#xff09;传统方式&#xff1a; int …

This is probably not a problem with npm. There is likely additional logging output above

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 E:\weii_objct\invoice-manage-web-view>npm start > ant-design-pro@2.1.0 start E:\weii_objct\invoice-manage-web-view > cross-env APP_TYPE=site umi dev cross-env 不是内部或外部命令…

parcel react_如何使用Parcel捆绑React.js应用程序

parcel reactby Michael Ozoemena迈克尔奥索埃梅纳(Michael Ozoemena) 如何使用Parcel捆绑React.js应用程序 (How to use Parcel to bundle your React.js application) 什么是包裹&#xff1f; (What’s Parcel?) Parcel is a web application bundler which offers a blazi…

SparkSQL 与 Spark Core的关系

不多说&#xff0c;直接上干货&#xff01; SparkSQL 与 Spark Core的关系 Spark SQL构建在Spark Core之上&#xff0c;专门用来处理结构化数据(不仅仅是SQL)。 Spark SQL在Spark Core的基础上针对结构化数据处理进行很多优化和改进&#xff0c; 简单来讲&#xff1a; Spark SQ…

linux操作系统-设置静态ip

在使用linux虚拟机的时候因为经常有关机的需求,然后重新开机后可能面临这上一次获取的ip被改变,在这里我分享一下在linux 下设置静态ip的经验 1.查看路由状态 [rootlocalhost ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Met…

判断数组里面的下标是否等于一个字符串

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 使用includes() 1、判断字符串里面是否包含一个字符串 示例&#xff1a; var a aaaaaaaavconsole.log(a.includes(v)); //truevar a aaaaaaaavconsole.log(a.includes(c)); //falsevar a aa…

如何获得更多的自由开发者客户

by Jad Joubran通过贾德乔布兰(Jad Joubran) 如何获得更多的自由开发者客户 (How to get more clients as a freelance developer) 我希望几年前知道的实用技巧 (Practical tips I wish I knew a few years ago) Whenever a conversation about freelancing kicks off with fe…

2017.6.4 入门组 NO.2——睡眠

其实这题就是将第二个时间-第一个时间&#xff0c;小于0的补全就A了代码如下&#xff1a; var x,y,k:string;l1,l2,x1,x2,x3,y1,y2,y3:longint; beginreadln(x);readln(y);l1:pos(:,x);l2:pos(:,y);k:copy(x,1,2); val(k,x1);k:copy(x,l11,2); val(k,y1);k:copy(y,1,2); val(k…

微信小程序获取用户收货地址 完整代码

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 获取用户收货地址需要用户点击授权&#xff0c;所以有两种情况&#xff0c;确认授权、取消授权。 情况一&#xff0c;用户第一次访问用户地址授权&#xff0c;并且点击确定授权。 情况二&#xff0c;用…

easyui的combobox下拉框初始化默认值以及保持该值一直显示的方法

easyui的combobox下拉框默认初始值是空&#xff0c;下面是实现从远程加载数据之后初始化默认值,以及让该值一直排在下拉框的最顶部的方式。 目前的需求是需要在初始化的时候添加"全部数据库"字段,并且在下拉的时候,"全部数据库"一直排在最顶部。 初始化效果…

关系数据库非关系数据库_如何与关系数据库最佳配合

关系数据库非关系数据库Relational databases handle data smoothly, whether working with small volumes or processing millions of rows. We will be looking at how we can use relational databases according to our needs, and get the most out of them.关系数据库无论…

看过的bootstrap书籍(附下载地址)

http://yun.baidu.com/share/link?shareid3820784617&uk1008683945 以下书籍下载地址。 《BootStrap入门教程》 就是网上的常规教程&#xff0c;过了一遍&#xff0c;不是很重要。 《Bootstrap实战_第一章》 没找到其余的章节&#xff0c;不过这本书不如直接看网上的boots…

Sql的连接表补充

连接条件可在FROM或WHERE子句中指定&#xff0c;建议在FROM子句中指定连接条件。WHERE和HAVING子句也可以包含搜索条件&#xff0c;以进一步筛选连接条件所选的行。 连接可分为以下几类&#xff1a; 内连接。&#xff08;典型的连接运算&#xff0c;使用像 或 <…

js正则验证身份证号是否正确

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 封装js公共方法 //验证身份证格式 const IdentityCodeValid sId > {const aCity { 11: "北京", 12: "天津", 13: "河北", 14: "山西", 15: "内蒙古…

jupyter笔记本_如何为Jupyter笔记本电脑设置PySpark

jupyter笔记本by Tirthajyoti Sarkar由Tirthajyoti Sarkar 如何为Jupyter笔记本电脑设置PySpark (How to set up PySpark for your Jupyter notebook) Apache Spark is one of the hottest frameworks in data science. It realizes the potential of bringing together both …

php 判断是否有相同的ID,如果有就修改数据库字段,没有就插入数据库字段

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 php代码 <?phpheader("Content-Type:text/html;charsetutf8"); header("Access-Control-Allow-Origin: *"); //解决跨域header(Access-Control-Allow-Methods:POST);// 响应类…

MySql存储引擎特性对比

下表显示了各种存储引擎的特性&#xff1a; 其中最常见的两种存储引擎是MyISAM和InnoDB 刚接触MySQL的时候可能会有些惊讶&#xff0c;竟然有不支持事务的存储引擎&#xff0c;学过关系型数据库理论的人都知道&#xff0c;事务是关系型数据库的核心。但是在现实应用中&#xff…