上节课我们介绍了如何实现静态组件,其实没有什么可说的,就是页面拆分,我们已经把页面实现了,但是呢我们的效果还远远没有达到,那么今天要进入下一步,动态组件,首先就要实现数据动态加载啊。所以我们要初始化这个动态列表。

回顾

在开始这街课之前,我们先对上一节课来做一个简单的回顾

  • 拆分组件要以功能点来作为单位进行拆分
  • 拆分组件并不用拆得那么碎。
  • 我们在拆分组件时尽可能将组件复用

以上就是上节课的一些主要要点,其实在案例编写的过程中反而真的是最简单的。接下来我们来开始我们这节课的内容。

如何动态展示

我们说要动态展示是什么意思呢?我们看现在的效果:

image-20220107130339019

虽然说有东西在但是是不是都是我们写死的?我现在总的列表里面是不是只有一条数据?因为我们在List组件中只用了一个Item组件,那么如果我们想要展示更多,是不是就要在List组件中不断地添加Item组件?但是生产环境中我们每一条数据都不一样,我不可能手动去给你添加组件进去的。所以说我们是不是要用props传入到Item组件中?这样就可以让这个列表能够动态去展示。那么怎么处理?

我们一直在说,state会驱动页面的动态更新,但是我们想一想我们这里有多少组件?一个App组件包含了三个子组件,其中一个子组件里面还嵌套子组件。我们每一个组件是不是都有自己的state?那么我们到底应该在那个组件的state中来保存和更新我们的数据?

组件state分析

那么我们肯定马上就会想到,那个组件负责展示数据,那么我就用哪个组件的state不就得了嘛,那这么一说肯定是用List组件的state啊。我们在Liststate中设置一个属性比如叫todos,这个属性是一个数组,这个数组里面包含了我们的todo。然后在List组件中来遍历,把state中的值传给Item组件,这样我们就可以动态展示了。

当然这样确实可以展示出来,可是我们考虑一下后续问题,我们的Add组件是干什么的?我们的Add组件是不是要把用户输入的东西更新到页面上?那么我们肯定要输入之后回车触发页面更新,那是不是代表着我要在按了回车之后要把输入的内容更新到state里面?我能在Add组件中更新Liststate吗?肯定不能啊。

但是有有人要问了,那么我把输入的东西当作props传给List,然后List里面来更新state就行了啊。当然这个思路没有问题,但是实现得了吗?以我们现在的知识面还是实现不了的。为什么?我们来看一下,我们最外层的容器组件是不是AppApp组件中是不是有AddList组件?App组件和Add还有List这俩组件分别是什么关系?是不是都是父子关系?如果App组件想给这俩组件传东西怎么传?是不是props

export default class App extends Component {
  render() {
    return (
      <div className="todo-container">
        <div className="todo-wrap">
          <Add a={1}/>
          <List b={2}/>
          <Footer />
        </div>
      </div>
    )
  }
}

想上面这么写我们是不是就把a传给了Add组件,把b传给了List组件?那么我们分别在这两个组件中打印一下呗:

// Add 组件
export default class Add extends Component {
  render() {
    console.log(this.props.a);
    return (
      <div className="todo-add">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" />
      </div>
    );
  }
}
// List 组件
export default class List extends Component {
  render() {
    console.log(this.props.b);
    return (
      <ul className="todo-list">
        <Item />
      </ul>
    )
  }
}

我这边就把两个组件一块展示了,但是这是在两个文件中,我们这么写只是为了方便。那么我们来看一下效果:

image-20220107133135884

控制台中打印出了我们传进去的值。这是不是意味着App是所有组件的父组件,所有App组件想给哪个组件传东西都行?但是Add组件和List组件呢?这两个组件不是父子关系。ListItem是父子关系,那么List可以给Itemprops。但是AddList是平级的啊。我们前面的课程中又说过如何在两个平级的组件之间传东西吗?没有吧?所以说我们现在的知识面来说是没法实现的。

那么这样的话是不是就代表着,如果我们用List组件的state来驱动页面更新的话,我们的Add组件将无法正常工作。那么怎么办?是不是应该用Add组件的state呢?那么大家想一想,如果我用Add组件的stateList展示什么呢?是不是还是刚才那个问题?我们的List组件拿不到Add组件里的东西啊。可能有人要说,那这样的话,这个需求我做不了了,您另请高明吧。

不至于,我们来分析一下。App组件中拥有AddFooterList这三个组件。而且App组件和这三个组件都分别是父子关系。那么这三个子组件能获取到App组件的state值吗?那必须能啊,直接App组件通过props传进去不就行了嘛。那么另外一个问题,这些子组件可以修改App组件的state吗?这个我们好像也从来没听说过。我们先不着急,先就当可以,我们来把state放在App组件中。因为我们已经分析了AddList组件中都不合适,而Footer组件一分析和另外俩没啥区别,因为都是平级的,我们没办法交互的。那只剩下App组件可以用了。那就先放App组件中呗

动态展示

既然我们已经知道可以在App组件中的state来存放和更新数据,那么我们来修改一下App.js

export default class App extends Component {
  state = {
    todos: [
      { id: "001", name: "Eat", done: false },
      { id: "002", name: "Sleep", done: false },
      { id: "003", name: "Code", done: false },
    ]
  };

  render() {
    const {todos} = this.state;
    return (
      <div className="todo-container">
        <div className="todo-wrap">
          <Add />
          <List todos={todos} />
          <Footer />
        </div>
      </div>
    )
  }
}

我们来看代码,前面的那些依赖导入我就不写出来了,我们看一下组件的定义部分,先是初始化了state。我们有一个属性todos是一个数组,数组里面是一个个的todo对象。我们看一下这些todo对象,第一个属性是id这是我们todo的唯一性标识,用来做遍历时的key的。然后name属性,就是我们的具体todo。然后一分个done属性,这个属性用来标识我们的todo是否完成了,毕竟我们有一个要一件清除已完成的todo的功能呢。

那么接下来我们要把这个属性传给List组件,那么既然传了,我们是不是就得接啊?那么我们就要在List组件中来获取到我传入的值。

export default class List extends Component {
  render() {
    const {todos}= this.props;
    return (
      <ul className="todo-list">
        { todos.map(todo => <Item />) }
      </ul>
    )
  }
}

我们来看一下,我们既然传了todos,那么我们是不是在List里面把解构赋值也加上来取到我们传入的todos,然后在<ul>中遍历数组来返回我们的Item组件,但是上面这样写对吗?我们来看一下效果:

image-20220107141839262

首先页面内容不对,虽然是三条数据没错,但是内容没展示出来,而且控制台有错误提示。而且告诉我们了我们没有加key,那么我们改一下呗,key用什么啊?index吗?我们之前的课程中已经说了遍历尽量不要用index,而且我们已经给了id了,那么肯定就用id啊,这样的话我们直接从todo取值作为参数传给Item组件就行了?那么传给了Item,那么Item组件也要来接收然后把里面的值展示到页面上。

// List 组件
export default class List extends Component {
  render() {
    const {todos}= this.props;
    return (
      <ul className="todo-list">
        { todos.map(todo => <Item key={todo.id} todo={todo}/>) }
      </ul>
    )
  }
}
// Item 组件
export default class Item extends Component {
  render() {
    const {todo} = this.props;
    return (
      <li>
        <label>
          <input type="checkbox" />
          <span>{todo.name}</span>
        </label>
        <button className="btn btn-danger" style={{ display: 'none' }}>删除</button>
      </li>
    )
  }
}

我们来看一下代码,先是List组件。我们在map方法中将todoid属性作为Itemkey,然后在将todoname属性传给了Item组件。然后再来看Item组件,Item组件中解构赋值拿到传入的name,然后渲染到页面上。那么我们再来看一下效果吧:

image-20220107144705314

这一次我们看到我们的实际内容都已经展示出来了,而且空盒子太中也没有报错。但是我们还有一个问题,什么问题呢?假如我们的state中有一项todo本来就已经是完成的todo了,我们这边展示会什么样子呢?我们来改一下看看

export default class App extends Component {
  state = {
    todos: [
      { id: "001", name: "Eat", done: true },
      { id: "002", name: "Sleep", done: true },
      { id: "003", name: "Code", done: false },
    ]
  };

  render() {...}
}

我们来看,我们把其中两项的done属性已经改成了true,那么页面上是什么效果?是不是应该前两个选中了?我们来看一下

image-20220107145328204

展示的好像和我们的预期不太一样,依然还是未完成的状态。这是为什么呢?以为我们没有去修改checkbox,所以一直默认都是未勾选,那么我们应该怎么处理呢?我们的checkbox是不是有一个属性叫checked?那么我们是不是可以通过这个属性来配合我们的done属性来做相应的处理呢?

export default class Item extends Component {
  render() {
    const {todo} = this.props;
    return (
      <li>
        <label>
          <input type="checkbox" checked={todo.done} />
          <span>{todo.name}</span>
        </label>
        <button className="btn btn-danger" style={{ display: 'none' }}>删除</button>
      </li>
    )
  }
}

我们来看一下代码我们是不是把checked属性设置成了传进来的tododone属性了?那么我们再来看一下效果吧:

image-20220107145854983

我们想要的效果是完成了,但是随之而来的呢多了一个错误警告。为什么呢,看一下警告信息:说我们写了checked属性,但是却没有写onChange事件这样会导致我们的checkbox变成一个只读的,就无法再改变了。其实在这个时候我们的checkbox确实也就已经无法勾选或者取消勾选了。那么怎么办呢?

其实这个时候我们可以换一个属性,我们不用checked我们用defaultChecked,这两个什么区别呢?就是这个defaultChecked不是只读的。这个意思是默认是否勾选,但是既然有默认,那肯定就允许不默认啊,所以说这个defaultChecked是可以允许我们修改的,那么我们再看一下结果:

image-20220107150526825

我们看到我可以作出我自己的修改,而且控制台也不再有错误警告了。那么至此我们便完成了我们初始化列表的展示

总结

  • 目前我们无法在平级组件之间相互传递数据
  • 在我们需要动态展示数据时一定要却定数据应该存储在哪个组件中
  • 父组件可以给任意子组件传递数据
  • checkbox如果写了checked属性,如果不写onChange事件就会变成只读状态
  • 遍历渲染一定要加唯一性的key

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/init_todolist_list/