在上篇文章中,我們使用React實現了一個TodoList,可以顯示基本的待辦事項的列表,今天我們繼續添加一些功能,比如選中一個TodoItem的checkbox進而可以改變它的完成狀態,添加一個搜尋方塊,在搜尋方塊中輸入關鍵字可以對多條資料進行過濾。
我們還是在原來的基礎上做改動,下面是最新的TodoList模組:
var TodoList = React.createClass({ getInitialState: function() { return { data: [] }; }, componentDidMount: function() { var mockData = [ {id: 1, name: "report the updates to Boss", time: "9:30"}, {id: 2, name: "Stand-up meeting", time: "10:00"}, {id: 3, name: "Draw up a plan for next step", time: "11:00"} ]; this.setState({ data: mockData }); }, render: function() { var todoItems = this.state.data.map(function(todo) { return ( //passing the whole todo object as a property <TodoItem key={todo.id} todo={todo}/> ); }); return ( <div className="todoList">{todoItems}</div> ); }});
在上面的代碼中,我們改進了TodoItem屬性的傳遞方式,直接把一個todo資料對象作為屬性提供給TodoItem,這樣更利於開發過程中的資料處理。接下來,我們也要對TodoItem模組進行改進,代碼如下:
var TodoItem = React.createClass({ //will be called after clicking the checkbox handleClick: function(event) { var todoData = this.props.todo; todoData.hasDone = !todoData.hasDone; //re-render the view this.setState({ hasDone: todoData.hasDone }); //Ajax handling maybe //updateTodo(this.props.todo); }, getInitialState: function() { return { hasDone: false }; }, componentDidMount: function() { this.setState({ hasDone: this.props.todo.hasDone }); }, render: function() { var classList = ['todoItem']; this.state.hasDone && classList.push('hasDone'); //add a 'hasDone' class after checkbox is checked var classNames = classList.join(' '); return ( //adding 'onClick' property on checkbox to bind a click handler <div className={classNames}> <input type="checkbox" onClick={this.handleClick} checked={this.props.todo.hasDone}/> <div className="name">{this.props.todo.name}</div> <div className="time">{this.props.todo.time}</div> </div> ); }});
這一部分改動的較多,我們仔細來分析每個部分的功能。
首先是getInitialState部分,我們添加了初始狀態,hasDone表示一個待辦事項是否已經完成,初始值為false,然後在componentDidMount函數中,我們根據上層傳遞進來的資料重新設定狀態,這個狀態最終會在render方法中起作用。
在render方法中我們會首先判斷這個Todo是否已經完成,如果已完成,就為其添加一個hasDone的className,最終會在頁面中顯示不同的樣式。然後我們為?checkbox聲明了checked屬性,它會根據資料中的hasDone來顯示選中狀態,這就意味著如果一個Todo是已完成的,頁面初始化時這個checkbox是被選中的。
最後,我們為checkbox綁定一個點擊事件,當它被點擊時,handleClick函數就會被觸發,在這個函數中,我們會擷取最新的hasDone值,然後重設狀態更新視圖,實際開發中我們還需要做一些Ajax操作請求伺服器更改資料庫中的值。
最後,我們添加一個hasDone對應的樣式:
.todoItem.hasDone > div { text-decoration: line-through;}
現在我們來看一下最終的頁面效果,如圖所示,當我們點擊第一個TodoItem後是這個樣子的:
我們已經成功地添加了點擊事件,下面我們還需要為這個TodoList添加一個過濾的功能,先建立一個SearchBox模組:
var SearchBox = React.createClass({ render: function() { return ( <div className="searchBox"> <input type="text" placeholder="type in keywords to search"/> </div> ); }});
然後聲明對應的CSS樣式:
.searchBox { width: 400px; height: 100%; margin: 0 auto;}.searchBox input { width: 100%; height: 30px; border: none; padding: 5px 15px; border-radius: 2px; font-size: 14px;}
最後還需在TodoList模組的render方法中把SearchBox添加進去:
render: function() { var todoItems = this.state.data.map(function(todo) { return ( <TodoItem key={todo.id} todo={todo}/> ); }); return ( <div className="todoList"> <SearchBox/> {todoItems} </div> ); }
現在可以預覽一下頁面效果:
下一步就要為SearchBox添加事件處理了,我們需要好好思考一下如何將文字框中的輸入與TodoItems關聯起來。首先,要想對資料集進行過濾,TodoList模組中必須定義一個表示搜尋索引鍵的變數,進而對資料集進行操作,然後,這裡的關鍵字是SearchBox提供的,所以SearchBox模組中的文字框資料變化必須通知TodoList模組,做下一步的處理。
現在我們來修改一下TodoList的代碼:
var TodoList = React.createClass({ handleSearchTextUpdate: function(searchText) { this.state.searchText = searchText; this.setState(this.state); }, getInitialState: function() { return { data: [], searchText: '' //adding a searchText, it will be used in render method }; }, componentDidMount: function() { var mockData = [ {id: 1, name: "report the updates to Boss", time: "9:30"}, {id: 2, name: "Stand-up meeting", time: "10:00"}, {id: 3, name: "Draw up a plan for next step", time: "11:00"} ]; this.state.data = mockData; this.setState(this.state); }, render: function() { var state = this.state; //filter the data first and then call map function var todoItems = state.data.filter(function(todo) { return todo.name.toLowerCase().indexOf(state.searchText.toLowerCase()) > -1; }).map(function(todo) { return ( <TodoItem key={todo.id} todo={todo}/> ); }); return ( //adding a 'onSearchTextUpdate' callback, it will be called when text changes in search input <div className="todoList"> <SearchBox onSearchTextUpdate={this.handleSearchTextUpdate}/> {todoItems} </div> ); }});
可以看到,在getInitialState方法中,我們添加了一個searchText的值,用於表示過濾的關鍵字,然後在render方法中,我們會使用filter函數對資料集進行過濾,searchText也就是在這個時候發揮它的作用的。接著在SearchBox聲明時我們添加了onSearchTextUpdate屬性作為搜尋文本變化後的回呼函數,它實際上會執行handleSearchTextUpdate函數,進而設定新的searchText,然後重設state重新整理視圖。
那麼在SearchBox模組,我們需要做的就是監聽文字框中輸入的變化,然後調用上面提到的回呼函數,把最新的輸入傳遞上去,來看一下修改過的代碼:
var SearchBox = React.createClass({ handleChange: function(event) { var newInput = event.target.value; //call the onSearchTextUpdate in props this.props.onSearchTextUpdate(newInput); }, render: function() { return ( //adding a 'onChange' to monitor the value changes <div className="searchBox"> <input type="text" onChange={this.handleChange} placeholder="type in keywords to search"/> </div> ); }});
過濾功能只是在TodoList和SearchBox之間的互動,TodoItem是不需要任何改動的,所以只需弄清楚SearchBox和TodoList之間的互動,就可以很快實現一個過濾功能,下面是我們最終的頁面效果:
如果你是一個Angular的愛好者,可能覺得React使用起來會比較繞,那麼你需要仔細揣摩整個實現過程,或許你還會稍微有點喜歡它呢~