上节课我们介绍了如何给每条todo添加鼠标移入移出效果,我们在Item组件加了state并通过鼠标移入移出事件更新state来驱动页面样式的改变。既然我们在视觉效果上已经实现了那么我们来实现功能层面吧。这节课我们来讲一下如何来完成我们的todo

回顾

在我们开始这节课的内容之前,我们来对上节课的内容来做一个简单的回顾,上节课的内容没有什么新的知识点,都是之前一再强调的问题。

  • 事件绑定必须是个函数
  • 通过state驱动页面样式的修改

以上便是上节课我们需要注意的点,接下来我们开始这节课的内容。

todo状态

image-20220110095117816

我现在像图中那样有了todo,但是我们如何去处理todo完成的逻辑呢?或者说我们有些事其实没有完成,但是我不小心勾选上了,怎么取消呢?可能有人说这个简单啊,我们们点一下前面的checkbox不就行了嘛。

但是这样处理是不是只是我们页面上变了?我们可别忘了底部是有一个组件在统计的。而且我们还有个功能要一键清除掉我们已完成的todo的,如果只是在页面上做了勾选或者取消勾选我们怎么能实现底部组件的功能呢?

那么我们这样的话,我们是不是要从组件state方面来考虑啊?不然我们之前App组件的state中每条todo对象都加一个done属性干吗呢?

功能分析

那么既然我们知道要从state层面来实现,那么首先我们要先来思考两个问题。

  • todo是否完成的这个标识存放在哪个组件中?
  • todo勾选的操作发生在哪个组件中?

那么好,我们之前是不是都说了我们把所有的todo信息都存放在了App组件的state里?App组件的state中存放了多条todo对象,每个todo对象都有一个done属性来标识这条todo是否完成。所以说todo是否完成的标识存放在App组件中。

其次我如果要完成某个todo,从宏观角度上来说是不是要点击列表中我想要完成的todo对应的checkbox?那么这个checkbox在哪?是不是在Item组件中?所以,我们完成todo的勾选操作是不是就在Item组件中发生?

那么好,如果我们要完成一个todo,是不是就需要点击这个todo对应的checkbox?是不是相当于利用Item组件中的checkbox然后来触发App组件中state的更新?

既然这样就好办了,我们这个功能分为两步

  1. 勾选todo对应的checkbox触发事件
  2. 通过事件修改App组件的state

这么来看的话是不是就清晰了许多?既然我们勾选就触发事件,那么我们是不是要给这个checkbox绑定事件?那么绑定什么事件呢?有听过有勾选事件吗?没听过,那么我们来想想当我们勾选了这个checkbox那是不是一种改变?所以我们使用onChange事件就可以了,我们绑定了事件不行啊,我们事件的回调方法得自己定义啊,通过回调来修改App组件的state这个操作我们是不是在添加todo那一节就已经介绍过了。既然如此我们便来实现吧

功能实现

上文已经说明了实现这个功能的思路了,那么接下来我们先看一下代码吧:

render() {
    const { todo } = this.props;
    const { mouse } = this.state;
    return (
      ...
        <label>
          <input 
            onChange={this.handleCheck(todo.id)} type="checkbox" 
            defaultChecked={todo.done} />
         ...
    )
  }
  handleCheck = id => {
    return e => {
      console.log(e.target.value);
    }
  }
  handleMouse = flag => {...};
}

多余的代码我就先省略了,我们先来看这个checkbox对应的input标签,我们既然要绑定事件,是不是要加上onChange事件啊?设置回调为handelCheck。我们的目的是什么?是不是勾选了之后马上将App组件中state里的该条todo的对象的done属性改掉?那么我们通过什么来识别是这条todo呢?是不是通过id属性?那么我们就将id传给handleCheck方法。

既然绑定好了事件,那么我们要定义handleCheck方法,这个方法接收id参数,但是我们看<input>中直接调用了这个方法对不对?那么事件绑定必须要是函数,所以我们handleCheck方法是不是要返回一个函数?这个函数中要去修改App组件的state,我们是不是要根据checkbox的勾选状态来判断?所以说是不是要获取<input>的值?这个事件是不是也发生在<input>上?那么我们返回的这个函数是不是就可以通过e参数来接收event再通过e.target.value来获取到勾选状态?好,我们来看一下结果是个什么样子:

image-20220110104525007

我们发现当我们对todo进行勾选或者是取消勾选,控制台中打印出来的都是on,这是什么意思呢?我们来看代码。其实我们现在的checkbox是不是都用input标签然后来修改type属性来实现的?我们之前通过event.target.value能获取到值是因为之前input标签的type属性都是text,当我们改成checkbox之后就不能通过event.target.value来取值了,得改成event.target.checked。这是原生js中的一些基础知识,我们这里就不在赘述了。那么我们修改一下代码

render() {
    const { todo } = this.props;
    const { mouse } = this.state;
    return (...}
  handleCheck = id => {
    return e => {
      console.log(id, e.target.checked);
    }
  }
  handleMouse = flag => {...};
}

我们现在已经将value改成了checked了,为了方便观察,我们吧id也打印一下。那么再来看一下效果:

image-20220110105211991

这一次我们便能拿到我们的todoid以及todo是完成了还是取消完成。那么我们是不是就要通知App组件来更新state了?我们之前也说了,子组件要更新父组件的state是不是要父组件来传一个操作入口的函数给子组件?但是Item是不是App的子组件?当然不是啊。ItemList的子组件,而List才是App的子组件。那么我们是不是可以先将这个函数传给List然后通过List再传给Item组件啊?那么好我吗来看一下行不行:

// App.js App 组件
export default class App extends Component {

  state = { todos: [] };

  add = dataObj => {...};
  update = (id, done) => {
    const { todos } = this.state;
    const newTodos = todos.map(todo => id === todo.id ? { ...todo, done } : todo);
    this.setState({ todos: newTodos });
  }

  render() {
    const { todos } = this.state;
    return (
      ...
      <List todos={todos} update={this.update} />
      ...
  }
}

我把一些暂时不需要关注的代码就先省略了,我们先来看App组件我们添加了一个update方法用来作为给子组件修改父组件中state的入口函数。

那么来看这个方法,首先我们是不是要接收参数?我得先知道我们要更新哪条todo啊,而且我们得知道更新成什么样子啊。所以我们要接收iddone

然后我们是不是要先获取到当前我们所有的todos?但是现在这个todos是不是一个数组?我们需要将这个数组加工一下,是不是要遍历判断id是否相等?如果相等则修改done属性,否则不做改变。那么我们用map方法就可以了啊。

我们直接用了三元运算符,但是这里{...todo, done}是什么意思?这里可不是展开运算符啊。我们之前有一件专门讲过展开运算符的,如果大家忘了赶紧回去复习一下。我们{...obj}这一步是不是在复制一个对象啊?而且{...obj, xxx: "xxxx"}是不是可以在赋值obj对象的时候修改掉其中的xxx属性,那么我们这里也是,因为属性名和变量名一样,所以可以简写。

然后到了最后一步就是修改state,这个方法就算完成了。

紧接着我们把这个方法传给List组件,再由List组件传给Item组件,我们来看一下代码:

// List.jsx List 组件
export default class List extends Component {
  render() {
    const { todos, update } = this.props;
    return (
      <ul className="todo-list">
        {todos.map(todo => <Item key={todo.id} todo={todo} update={update} />)}
      </ul>
    )
  }
}
// Item.jsx Item 组件
export default class Item extends Component {
  state = { mouse: false };

  render() {...}
  handleCheck = id => {
    const {update} = this.props;
    return e => {
      update(id, e.target.checked);
    }
  }
  handleMouse = flag => {...};
}

我们在List组件中解构赋值获取到props中的update函数,然后传给Item组件,然后我们在Item组件中的handleCheck方法中来调用并将todoidcheckbox的勾选状态传给update函数来完成对App组件的state的更新,那么我们来看一下效果吧:

image-20220110112920262

当我们修改了各个todo的勾选状态之后,App组件的state中每条todo对象的done属性也作出了相应的改变。可见我们这个方法成功实现了todo是否完成的功能。

总结

  • checkbox不能通过event.target.value拿勾选状态,应该用event.target.checked
  • state在哪,修改state的方法就在哪
  • 事件绑定一定得是一个函数

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/finish_todo/