上节课我们介绍了如何实现静态组件,其实没有什么可说的,就是页面拆分,我们已经把页面实现了,但是呢我们的效果还远远没有达到,那么今天要进入下一步,动态组件,首先就要实现数据动态加载啊。所以我们要初始化这个动态列表。
回顾
在开始这街课之前,我们先对上一节课来做一个简单的回顾
- 拆分组件要以功能点来作为单位进行拆分
- 拆分组件并不用拆得那么碎。
- 我们在拆分组件时尽可能将组件复用
以上就是上节课的一些主要要点,其实在案例编写的过程中反而真的是最简单的。接下来我们来开始我们这节课的内容。
如何动态展示
我们说要动态展示是什么意思呢?我们看现在的效果:
虽然说有东西在但是是不是都是我们写死的?我现在总的列表里面是不是只有一条数据?因为我们在List
组件中只用了一个Item
组件,那么如果我们想要展示更多,是不是就要在List
组件中不断地添加Item
组件?但是生产环境中我们每一条数据都不一样,我不可能手动去给你添加组件进去的。所以说我们是不是要用props
传入到Item
组件中?这样就可以让这个列表能够动态去展示。那么怎么处理?
我们一直在说,state
会驱动页面的动态更新,但是我们想一想我们这里有多少组件?一个App
组件包含了三个子组件,其中一个子组件里面还嵌套子组件。我们每一个组件是不是都有自己的state
?那么我们到底应该在那个组件的state
中来保存和更新我们的数据?
组件state
分析
那么我们肯定马上就会想到,那个组件负责展示数据,那么我就用哪个组件的state
不就得了嘛,那这么一说肯定是用List
组件的state
啊。我们在List
的state
中设置一个属性比如叫todos
,这个属性是一个数组,这个数组里面包含了我们的todo
。然后在List
组件中来遍历,把state
中的值传给Item
组件,这样我们就可以动态展示了。
当然这样确实可以展示出来,可是我们考虑一下后续问题,我们的Add
组件是干什么的?我们的Add
组件是不是要把用户输入的东西更新到页面上?那么我们肯定要输入之后回车触发页面更新,那是不是代表着我要在按了回车之后要把输入的内容更新到state
里面?我能在Add
组件中更新List
的state
吗?肯定不能啊。
但是有有人要问了,那么我把输入的东西当作props
传给List
,然后List
里面来更新state
就行了啊。当然这个思路没有问题,但是实现得了吗?以我们现在的知识面还是实现不了的。为什么?我们来看一下,我们最外层的容器组件是不是App
?App
组件中是不是有Add
和List
组件?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> ) } }
我这边就把两个组件一块展示了,但是这是在两个文件中,我们这么写只是为了方便。那么我们来看一下效果:
控制台中打印出了我们传进去的值。这是不是意味着App
是所有组件的父组件,所有App
组件想给哪个组件传东西都行?但是Add
组件和List
组件呢?这两个组件不是父子关系。List
和Item
是父子关系,那么List
可以给Item
传props
。但是Add
和List
是平级的啊。我们前面的课程中又说过如何在两个平级的组件之间传东西吗?没有吧?所以说我们现在的知识面来说是没法实现的。
那么这样的话是不是就代表着,如果我们用List
组件的state
来驱动页面更新的话,我们的Add
组件将无法正常工作。那么怎么办?是不是应该用Add
组件的state
呢?那么大家想一想,如果我用Add
组件的state
,List
展示什么呢?是不是还是刚才那个问题?我们的List
组件拿不到Add
组件里的东西啊。可能有人要说,那这样的话,这个需求我做不了了,您另请高明吧。
不至于,我们来分析一下。App
组件中拥有Add
,Footer
,List
这三个组件。而且App
组件和这三个组件都分别是父子关系。那么这三个子组件能获取到App
组件的state
值吗?那必须能啊,直接App
组件通过props
传进去不就行了嘛。那么另外一个问题,这些子组件可以修改App
组件的state
吗?这个我们好像也从来没听说过。我们先不着急,先就当可以,我们来把state
放在App
组件中。因为我们已经分析了Add
和List
组件中都不合适,而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
组件,但是上面这样写对吗?我们来看一下效果:
首先页面内容不对,虽然是三条数据没错,但是内容没展示出来,而且控制台有错误提示。而且告诉我们了我们没有加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
方法中将todo
的id
属性作为Item
的key
,然后在将todo
的name
属性传给了Item
组件。然后再来看Item
组件,Item
组件中解构赋值拿到传入的name
,然后渲染到页面上。那么我们再来看一下效果吧:
这一次我们看到我们的实际内容都已经展示出来了,而且空盒子太中也没有报错。但是我们还有一个问题,什么问题呢?假如我们的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
,那么页面上是什么效果?是不是应该前两个选中了?我们来看一下
展示的好像和我们的预期不太一样,依然还是未完成的状态。这是为什么呢?以为我们没有去修改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
属性设置成了传进来的todo
的done
属性了?那么我们再来看一下效果吧:
我们想要的效果是完成了,但是随之而来的呢多了一个错误警告。为什么呢,看一下警告信息:说我们写了checked
属性,但是却没有写onChange
事件这样会导致我们的checkbox
变成一个只读的,就无法再改变了。其实在这个时候我们的checkbox
确实也就已经无法勾选或者取消勾选了。那么怎么办呢?
其实这个时候我们可以换一个属性,我们不用checked
我们用defaultChecked
,这两个什么区别呢?就是这个defaultChecked
不是只读的。这个意思是默认是否勾选,但是既然有默认,那肯定就允许不默认啊,所以说这个defaultChecked
是可以允许我们修改的,那么我们再看一下结果:
我们看到我可以作出我自己的修改,而且控制台也不再有错误警告了。那么至此我们便完成了我们初始化列表的展示
总结
- 目前我们无法在平级组件之间相互传递数据
- 在我们需要动态展示数据时一定要却定数据应该存储在哪个组件中
- 父组件可以给任意子组件传递数据
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/