React实现Todolist

React实现Todolist
一个todolist基本的功能点包括:

  1. 点击复选框标记完成或者未完成,且相应的文字出现删除线
  2. 从列表中移除item
  3. 添加item

首先它是由todolist title 、todolist、和addtodo三部分组成。由于todolist title 和addtodo比较简单,在这里只将todolist拆分成组件。
创建一个ListItem组件。含有一个render方法

class ListItem extends Component {
  render() {
    return (
      <ul>
        {
          this.props.data.map(element => {
            return (
              <li className="listItem" key={element.name}>
                <input type="checkbox"/>
                <span>{element.name}</span>
                <button className="delete">删除</button>
              </li>
             )
        })
      }
    </ul>
   )
  }
 }
 export default ListItem

this.props.data是父组件传下来的一个list。遍历该list的每一项渲染出来。(注:上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。)
此时一个todo的list组件就封装好了,export default出去,让需要的去组件使用。

在父组件TodoList中使用该ListItem组件。

import ListItem from './ListItem'
class TodoList extends Component {
  render() {
    return (
      <div className="reactTodoList">
      	<header className="header">React todo list</header>
      	<ListItem data={this.state.list} />
      	<footer>
        	<input type="text"  placeholder="添加todo"></input>
        	<button className="addTodo" >添加</button>
      	</footer>
   	 </div>
    )
  }
}
ReactDOM.render( <TodoList />,  document.getElementById('root'))

由上基本的框架已经搭建出来了,接下来就是list中需要填充数据。
在ListItem组件中有一个接受到的this.props.data的数组,其来自于父组件上的<ListItem data={this.state.list} />。父组件中的state需要声明一下:

constructor() {
    super()
    this.state = {
      list: [{
        name: 'learn english', status: 0
      },{
        name: 'Learn guitar', status: 0
      }, {
        name: 'weight less than 100', status: 0
      }, {
        name: 'have 100,000 deposit', status: 0
      }]
    }
  }

在render函数上插入这一段代码,添加一个类构造函数来初始化状态 this.state。React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
接下来父组件通过<ListItem data={this.state.list} />将其list传入到了子组件,子组件中使用map进行渲染。至此页面渲染结束。

1. complete todo
现在要添加交互了。首先实现勾选checkbox出现✅,并且对应的文字出现删除线。注意父组件传入的data的数组中status表示该todo是否完成,所以status就是一个重要的信息。0表示未完成,点击checkbox后,status变为1,表示完成,且文字出现删除线。

<li className="listItem" key={element.name}>
    <input type="checkbox"
       checked={element.status === 1}
       onChange={this.completeTask.bind(this, element.name)}/>
    <span style={{textDecorationLine: element.status === 0 ? 'none' : 'line-through'}}>{element.name}</span>
    <button className="delete">删除</button>
 </li>

这段代码来自于ListItem的组件(可以向上找),现在我们添加在input元素上一个checked属性,和一个onChange事件。
checked表示当status为1时表示完成,当checkbox发生变化的时候就要去更新父组件中的this.status.list相应的status。所以需要一个触发事件completeTask。将此item的name传给父组件,父组件根据name查找到相应的listItem,更新其status。(子组件需要向父组件传值使用函数)。此时在该子组件中定义一个completeTask函数(在render函数上方)。

completeTask(name) {
    this.props.completeTask(name)
 }

学过vue的应该知道,这有点像vue的子组件emit事件到父组件,父组件通过该事件来进行局部处理。

 <span style={{textDecorationLine: element.status === 0 ? 'none' : 'line-through'}}>{element.name}</span>

在上面这段代码中,通过判断: element.status来添加style,是否出现删除线。

在父组件中应该这样:

<ListItem data={this.state.list} completeTask={this.completeTask1.bind(this)}/>

此段代码来自于父组件TodoList中(向上查找)。代码中的completeTask函数来自于子组件中,completeTask1是需要在父组件中进行处理的父组件函数。这个completeTask1需要完成的事情就是根据子组件传上来的name值,在this.state.list中找到之后更新它的status。若为1更新为0,反之亦然。

completeTask1(name) {
    const TodoList = []
    this.state.list.forEach((element, index) => {
      if (element.name === name) {
        const item = this.state.list[index]
        TodoList.push(Object.assign({}, item, {status: item.status === 0 ? 1 : 0}))
        this.setState({
          list: TodoList
        })
      } else {
        TodoList.push(element)
      }
    })
  }

父组件中中的completeTask1函数,遍历this.state.list根据name查找到对应的item,通过Object.assign({}, item, {status: item.status === 0 ? 1 : 0}))来更改status。注:state中的所有数据不允许直接更改。

this.setState({
    list: TodoList
 })

用来更新state中的数据。

至此就实现了点击checkbox复选框出现对号并且相应文字出现删除线。(完成item的功能)

2. delete todo
点击子组件ListItem中的delete按钮,同样通过函数向父组件传递一个name,父组件根据这个name从this.state.list中移除该item。
子组件中

   <button className="delete" onClick={this.deleteTask.bind(this, element.name)}>删除</button>

render函数上方

deleteTask(name) {
    this.props.deleteItem(name)
}

父组件中

<ListItem data={this.state.list} deleteItem={this.deleteItem1.bind(this)}
        completeTask={this.completeTask.bind(this)}/>

多了一个deleteItem的函数。
deleteItem1函数中通过name查找item,删除

deleteItem1(name) {
    const data = this.state.list.filter(element => element.name !== name)
    this.setState({
      list: data
    })
  }

此时一个删除todolist的功能也已经完成。接下来就是添加todo了。

3. add todo
添加todo只需要在父组件中进行操作。
首先需要获得文本框中的输入内容,接下来通过点击添加按钮来给this.state.list中push一项就可以实现啦。

<footer>
        <input type="text" value={this.state.inputVal} onChange={this.handleChange.bind(this)} placeholder="添加todo"></input>
        <button className="addTodo" onClick={this.addTask.bind(this)}>添加</button>
  </footer>

该部分就是添加todo的部分啦。首先input绑定了this.state.inputVal, 并且当输入内容变化的时候更新state中的inputVal。
首先

constructor() {
    super()
    this.state = {
      list: [{
        name: 'learn english', status: 0
      },{
        name: 'Learn guitar', status: 0
      }, {
        name: 'weight less than 100', status: 0
      }, {
        name: 'have 100,000 deposit', status: 0
      }],
      inputVal: ''
    }
  }

注意添加了一个inputval,用来存储输入框中的内容。实现handleChange函数,并更新state.inputVal

handleChange(e) {
    this.setState({
      inputVal: e.target.value
    })
  }

实现了输入框的功能,接下来点击addTask的按钮需要实现添加todo的功能。并且添加成功后输入框中的内容为空。即inputVal为空字符串。

addTask() {
    if (!this.state.inputVal) return
    this.setState({
      list: [...this.state.list, {
        name: this.state.inputVal,
        status: 0
      }],
      inputVal: ''
    })
  }

这个函数中若输入为空不执行任何操作(你也可以添加提示)。添加成功后更新list和inpuVal。
至此一个简单的todolist功能就完成啦。

完整代码见: https://github.com/zhangsundf/React-todoList