react实现todolist 增删改查

react实现todolist 增删改查

实现需求:todolist实现增删改查;javascript

实现思路:react组件划分。由于用的是localstorage封装了数据库各个组件之间数据库不能同步,因此设计组件的思路是在父组件里进行增删改查后的渲染操做(setstate),父组件app下有增删改查操做,所以有四个子层级组件,考虑到刚开始作删除功能的时候对li有操做,因此把对li的操做(删除),ui样式的变更和渲染统一放在了删除todoitem组件里,所以该组件的功能就是对li执行删除操做,和一些事件处理,组件内容应该返回li和和删除按钮,可是li是一个数组,因此须要作一个循环去不停的渲染todoitem,因此设计一个父组件todomain,todomain的功能就是对数据执行map,map里循环渲染todoitem组件,同事把数组传给子组件todoitem,因此todomain应该是从app(app组件有增长组件传递的数据,并把该数组存到了数据库(localDb),因此要获取这个数组进行渲染)继承数据,所以设计思路如图:前端

环境搭建工具:node.js,webpack,webpack-dev-server,babel编译jsx,es6

使用框架:react.js,localDb(利用localstorage机制封装的本地数据库),ant-design。(ps:未使用jquery)

安装 gitclone git@github.com:lernalot/react-todolist-.git ,npm install,注意node-modules里必定要有localDb文件夹。

组件分析

  • app.js,首先看底部render的子组件:java

    render(){
    	return (
    		<Card className="pannel">
    			<TodoHeader addTodo={this.addTodo.bind(this)} todos={this.state.todos} showAll={this.showAll.bind(this)} />
    			<TodoQuery ref="query" todos={this.state.todos} queryList={this.queryList.bind(this)} />
    			<TodoMain todos={this.state.todos}  changeTodoState={this.changeTodoState.bind(this)} deleteTodo={this.deleteTodo.bind(this)} reviseTodo={this.reviseTodo.bind(this)}/>
    			<TodoRevise ref="modal" todos={this.state.todos} closeDialog={this.closeDialog.bind(this)} reviseContent={this.reviseContent.bind(this)}/>
    		</Card>
    	)
    }
    复制代码
  1. 其中card是从antd引入的一个api,antd是一个view层的框架,提供了一些ui组件可使用。
  2. 引入的组件分别为增长组件,查询组件,渲染组件(子组件删除),修改组件。
  • 接下来看增长的渲染操做addTodo方法node

    addTodo(todoItem){
    	if(!this.db.get('todos')){
    	   this.db.set('todos',[]);
    	}        
    	this.state.todos.unshift(todoItem);
    	if(this.state.todos.length != this.db.get('todos').length){
    		this.db.get('todos').unshift(todoItem);
    	}
    	this.db.set('todos',this.db.get('todos'));
    	this.setState({
    		todos:this.db.get('todos')
    	});
    	message.config({
    		top:48,
    		duration:1
    	});
    	message.success('增长成功!');
    }
    复制代码
  1. 从代码能够看出this.db表明数据库的操做,set保存,get取值,todoItem是从增长组件传过来的input的值,这里负责入栈操做,并把最新的数组渲染出来。
  • 删除操做deleteTodoreact

    deleteTodo(timeId){
    	let i =0;
    	for(i=0;i<this.db.get('todos').length;i++){
    		if(this.db.get('todos')[i].timeId == timeId){
    			this.db.get('todos').splice(i,1);
    		}
    	}
    	this.state.todos.map((todo,index) => {
    		console.log(todo);
    		if(todo.timeId == timeId){
    			this.state.todos.splice(index,1);
    		}
    	});
    	this.setState({todos:this.state.todos});
    	this.db.set('todos',this.db.get('todos'));
    	message.config({
    		top:48,
    		duration:1
    	});
    	message.success('删除成功!');
    }
    复制代码
  1. timeId是每个li生成的时间戳,他是惟一的,删除组件传过来时间戳以后,遍历数据库,找到li,数组删除这个元素。
  2. 值得注意的,这里相比增长方法的setState,这里渲染的是this.state.todos,而不是对数据库的渲染,主要是考虑到查询以后当前list有可能为一部分的数组,用户但愿是在当前查询到的list删除和显示,因此就渲染了this.state.todos,但以后对数据库进行删除操做,才是真正的对数据作了修改。
  • 修改方法:
    复制代码
  • reviseContent(todo,index,timeId,lastReviseTime){ let reviseIndex = Number(index)-1; let reviseObject = {text:todo,index:reviseIndex,timeId:timeId}; let i; message.config({ top:48, duration:1 }); this.state.todos.splice(reviseIndex,1,reviseObject); this.setState({todos: this.state.todos}); for(i=0;i<this.db.get('todos').length;i++){ if(this.db.get('todos')[i].timeId == timeId){ this.db.get('todos')[i] = reviseObject; this.state.todos[i].lastReviseTime = lastReviseTime; } } this.db.set('todos', this.db.get('todos')); this.refs.modal.setState({ visible: false, }); // message.success('修改为功!'); document.getElementById('reviseinput').value = ''; }
    复制代码
  1. 修改方法也是从数据库找到指定的数据,数据库修改以后,显示当前this.state.todos。
  2. 这里的lastReviseTime是最后一次修改时间,经过修改组件传入。
  • 查询方法queryList:
- queryList(queryArr){
        message.config({
            top:48,
            duration:1
        });
        if(this.state.todos.length == this.db.get('todos').length && queryArr.length == 0){
            message.warning('查询内容不存在');
            return;
        }
        if(queryArr.length == 0){
            message.warning('当前list不存在查询内容,已为您转到所有list');
            this.setState({
                todos:this.db.get('todos')
            });
        }else{
                this.setState({
                todos:queryArr
            });
        }     
    }
复制代码
  1. queryArr是查询后获得的数组,经过查询组件传过来,查询的基本原理就是从当前list遍历(由于可能存在查询以后再查询操做,再查询的时候,app对数据库作了修改,但TodoQuery的数据库信息不会实时同步,可是this.state.todos会同步,因此根据当前list作查询,若是当前list查不到内容并且list长度和数据库长度不一样,就从新渲染一遍this.state.todos,而后让用户再查询),查询思想就是判断查询的str是否出如今数组的元素里,经过indexOf方法判断索引是否大于-1便可。

###TodoMain组件:jquery

- return (
        <ul className="todo-list">
            {this.props.todos.map((todo, index) => {
                //return <li style={listStyle}>{this.props.todos[index].text}</li>
                return <TodoItem key={index} text={todo.text} isDone={todo.isDone} lastReviseTime={todo.lastReviseTime} timeId={todo.timeId} index={index} {...this.props} />
                //return <TodoItem key={index} {...todo} index={index} {...this.props}/>
                //map对数组进行了遍历,todo表示每个数组元素text,index表明索引,因此让todoitem渲染的过程是一个循环的渲染过程,每次渲染不同,是动态组件渲染
                //做为动态组件,须要一个key,每次渲染的时候key不一样,才会显示不一样的渲染,是一个表示的渲染
            })}
        </ul>
    )
复制代码
  1. 使用map对数据进行遍历渲染,注意spread操做符,{…todo},{...props},spread操做符把props,todos的属性和方法传递到子组件TodoItem。
  2. 这里是一个循环,因此return的TodoItem组件是一个动态组件,根据react组件的动态机制,须要在todoItem组件里设置一个key,并要保证每次key的值都不同,也就是渲染的列表的,渲染是对this.state.todos的渲染,列表每一项都有不一样的index,因此取值key={index}。

###TodoItem组件webpack

- export default class TodoItem extends React.Component{
	    constructor(){
	        super();
	        this.state = {
	            checkAll:false
	        }
	    }
	
	    // 鼠标移入
	    handlerMouseOver(){
	        ReactDom.findDOMNode(this.refs.deleteBtn).style.display = "inline";
	        ReactDom.findDOMNode(this.refs.changeBtn).style.display = "inline";
	    }
	
	    // 鼠标移出
	    handlerMouseOut(){
	        ReactDom.findDOMNode(this.refs.deleteBtn).style.display = "none";
	        ReactDom.findDOMNode(this.refs.changeBtn).style.display = "none";
	    }
	
	    // 删除当前任务
	    handlerDelete(){
	        this.props.deleteTodo(this.props.timeId);
	        console.log(this.props.timeId);
	    }
	    reviseTodo(){
	        this.props.reviseTodo(this.props.text,this.props.index,this.props.timeId);
	    }
	
	    render(){
	        let doneStyle = this.props.isDone ? {color: 'red'} : {color: '#57c5f7'};
	
	        return (
	            <li onMouseOver={this.handlerMouseOver.bind(this)} onMouseOut={this.handlerMouseOut.bind(this)} ref='checkList' > <span style={doneStyle} className="listContent">{this.props.text}</span> <Button ref="deleteBtn" onClick={this.handlerDelete.bind(this)} style={{'display': 'none'}} className="fr libtn-height">删除</Button> <Button ref="changeBtn" style={{'display': 'none'}} className="change libtn-height" onClick={this.reviseTodo.bind(this)}>修改</Button> <span className='product-time'>建立时间:{this.props.timeId}</span><span className='revise-time'>最后修改时间:{this.props.lastReviseTime}</span> </li>
	        )
	    }
	}
复制代码
  1. React.findDOMNode(this)能够获取当前这个组件标签。
  2. 在元素中定义ref=xxx属性,就能够经过React.findDOMNode(this.refs.xxx)获取到这个元素。
  3. 给元素定义class类名的时候要使用className,这里最后修改时间是从修改组件传过来的,TodoItem和TodoRevise没有通讯关系,TodoRevise把数据传到app,而后下发到TodoItem显示。(是否有其余方式解决互不相干(兄弟节点或者其余)的组件之间的通讯关系?)

###TodoRevise组件git

- class TodoRevise extends React.Component {
	    constructor(){
	        super();
	        this.state={
	            visible: false
	        };
	    }
	    // 绑定键盘回车事件,添加新任务
	    showModal(todo,index,timeId) {
	        this.setState({
	          visible: true,
	          reviseList: todo,
	          reviseIndex:index*1+1,
	          reviseTimeId:timeId
	        });
	    }
	    handleOk() {
	        let newList = document.getElementById('reviseinput').value;
	        let lastReviseTime = new Date().getTime();
	        this.state.reviseList = newList;
	        this.props.reviseContent(this.state.reviseList,this.state.reviseIndex,this.state.reviseTimeId,lastReviseTime);
	    }
	    handleCancel() {
	        this.props.closeDialog();
	    }
	    copyList(){
	        document.getElementById('reviseinput').value = this.state.reviseList;
	    }
	    keyComplete(event){
	        let lastReviseTime = new Date().getTime();
	        if(event.keyCode == 13){
	            let newList = document.getElementById('reviseinput').value;
	            this.state.reviseList = newList;
	            this.props.reviseContent(this.state.reviseList,this.state.reviseIndex,this.state.reviseTimeId,lastReviseTime);
	        }
	    }
	
	    render() {
	        return (
	          <div> <Modal title="修改列表内容" visible={this.state.visible} onOk={this.handleOk.bind(this)} onCancel={this.handleCancel.bind(this)}> <div>您正在修改第{this.state.reviseIndex}条,修改内容为<p className="revise_content" onClick={this.copyList.bind(this)}>{this.state.reviseList}</p></div> <p>在下面输入框中对原内容进行修改(支持回车键保存)</p> <Input type="text" id="reviseinput" placeholder="点击蓝色字体将内容复制到输入框" onKeyUp={this.keyComplete.bind(this)}></Input> </Modal> </div>
	        )
	    }
	}
	export default TodoRevise;
复制代码
  1. 这里modal是ant-design的一个组件,自带了handOk,show modal,handleCancel方法,修改的显示隐藏就是经过更改this,state.visible(boolean)来决定的,监听肯定按钮点击事件和回车键输入事件来执行把修改的对象传给app进行数据库查询找到被修改的对象,并赋值新的修改的内容(由参数传递过来)。

###总结与思考es6

####react渲染性能优势:github

  • react不直接操做dom,将DOM结构存储在内存中,而后同render()的返回内容进行比较,计算出须要改动的地方,最后才反映到DOM中。
  • react使用虚拟节点,经过js建立dom节点对象。
  • 传统的dom树:传统dom节点是很是庞大的,拥有不少属性,对传统dom的操做如增长或者删除将会引发重排,重排是DOM元素的几何属性变化,DOM树的结构变化,渲染树须要从新计算。页面在下载好文档时会生成两个内部数据结构dom树和渲染树,每次对dom节点的操做引发了重排以后,dom树会发生从新排列,此时渲染树就要开始对页面从新的结构进行计算,从读取dom(更改属性就要对dom对象的属性进行便利查找)到修改属性,把属性从新入栈到dom对象,生成新的dom文档的时候,重排必然引发重绘,重绘就会须要渲染树进行从新计算获得样式。
  • 虚拟节点:基本算法过程分为:
    1. 经过js建立dom对象,存储在内存中,react会根据一个根节点(实际节点):
    var ulRoot = ul.render()
    	document.body.appendChild(ulRoot)
    也就是react中:
    ReactDOM.render(<App/>,document.getElementById('ulRoot'));
    复制代码
    来逐层计算子节点,而后把子节点抛到根节点(ulRoot)下。
    1. js内比较修改后新的虚拟dom树和原来的旧的虚拟dom树,diff算法: 两个树的彻底的 diff 算法时间复杂度为 O(n^3),在前端里不多跨级移动dom元素,因此通常只会在同一层级对新旧dom树的dom元素进行对比,时间复杂度减为O(n)。差别计算:
    • 深度优先遍历dom树,对比dom数组的差别,把差别存储在一个数组里。
    1. 把差别应用到dom树,dom节点的操做可能会有:
      • 替换掉原来的节点,例如把上面的div换成了section
      • 移动、删除、新增子节点,例如上面div的子节点,把p和ul顺序互换
      • 修改了节点的属性
      • 对于文本节点,文本内容可能会改变。 每种操做对应一个对象,不一样的差别把差别做为对象属性存储到数组里。
    2. 遍历dom树,从存储差别的数组里得到index,而后对dom树的节点数组进行差别操做,差别操做会根据差别数组的对象类型,执行不一样的操做,替换删除等等。
  • react对变化是一个存储而后批处理的过程,仅对变化的dom进行批处理。(批处理机制?)

####react使用

  1. 组件规划:明确需求功能,明确功能对应的组件之间的关系。
    • 对于兄弟节点又没有相互引用,因为react单向数据传输机制,项目里使用的是使用this.props.deleteTodo(para1,para2),将参数从子组件传到父组件,而后由父组件记录到this.state.todos,再传递到其余组件。
  2. 子组件进行事件操做,将用户的行为记录下来,而后经过参数传到app作数据处理和setState。
相关文章
相关标签/搜索