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。
相關文章
相關標籤/搜索