ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 效果 ## 分析 在这里面我们是在输入框中输入数据,按下enter键,对评论列表中添加一条新的数据。那么表单的数据和评论列表的数据都应该存放在state中。 ![](https://box.kancloud.cn/046b2076e55ddd4de97d3c7a53413734_510x450.png) # 新建组件 ## 1.在index.html中引入bootstrap样式 ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> </head> <body style="background: skyblue"> <div id="root"></div> </body> </html> ~~~ ## 2.新建Commet.js组件 新建`src/component/Commet.js`并书写基本结构 ~~~ import React, {Component} from 'react'; export default class Commet extends Component { constructor(props) { super(props); this.state = {} } render() { return ( <div className={'container'} style={{marginTop:100}}> <div className="form-group"> <input type="text" className="form-control"/> </div> <div className="list-group"> <li className="list-group-item"> 111 <span className="close">&times;</span> </li> <li className="list-group-item"> 111 <span className="close">&times;</span> </li> </div> <div className="text-center bg-success">共有条评论</div> </div> ); } } ~~~ # 双向数据绑定 这里面给表单添加数据目的是为了将来给评论列表的数组中添加一些数据 ~~~ changeUser(){ this.setState((state,props)=>{ return { user:this.refs.my.value } }) } …… <input ref="my" value={this.state.user} onChange={this.changeUser.bind(this)} type="text" className="form-control"/> ~~~ 给评论列表的数组中添加一些新的数据 ~~~ import React, {Component} from 'react'; export default class Commet extends Component { constructor(props) { super(props); this.state = { user: '', list: [] } } changeUser() { this.setState((state, props) => { return { user: this.refs.my.value } }) } addList(e) { if (e.keyCode === 13) { this.setState((state, props) => { console.log(state, props) var newarr = JSON.parse(JSON.stringify(state.list)) newarr.unshift(state.user) return { list: newarr, user: '' } }) } } removeItem(index) { console.log(index) this.setState((state,props)=>{ var newarr = JSON.parse(JSON.stringify(state.list)) newarr.splice(index,1) return { list:newarr } }) } renderList() { return this.state.list.map((item, index) => { return (<li key={index} className="list-group-item"> {item} <span className="close" onClick={this.removeItem.bind(this,index)}>&times;</span> </li>) }) } render() { return ( <div className={'container'} style={{marginTop: 100}}> <div className="form-group"> <input ref="my" value={this.state.user} onChange={this.changeUser.bind(this)} onKeyDown={this.addList.bind(this)} type="text" className="form-control"/> </div> <div className="list-group"> {this.renderList()} </div> <div className="text-center bg-success">共有条评论</div> </div> ); } } ~~~ ## 注意事项 千万不要对原数组进行直接操作, 直接操作只会影响数据的改变,并不会触发视图进行更新,如果期望数据和视图同时更新必须使用this.setState()这个方法。 # 拆分组件 新建`src/components/Item.js` ~~~ import React, {Component} from 'react'; export default class Item extends Component { constructor(props) { super(props); this.state = props; } removeItem(index) { this.state.rem(index) } render() { return ( <li className="list-group-item"> {this.state.item} <span className="close" onClick={this.removeItem.bind(this, this.props.index)}>&times;</span> </li> ); } } ~~~ 修改Commet.js组件 ~~~ removeItem(index) { console.log(index) this.setState((state, props) => { var newlist = JSON.parse(JSON.stringify(state.list)) newlist.splice(index, 1) return { list: newlist } }) } renderList() { return this.state.list.map((item, index) => { return ( <Item rem={this.removeItem.bind(this)} key={index} item={item} index={index}/> ) }) } ~~~ 在这里我们用子组件向父组件传递数据,不是用的自定义事件触发,而是父组件将父组件对应的功能通过props传递到子组件中。子组件书写功能触发这个功能即可。 # 总结 **父子组件通信在React中是通过 props来进行传递信息和功能的。** # 拔高 如果对自己有更高要求的可以看一下,如果自己感觉有点难,也是正常,那么可以先听一下有一点点印象,不急于立即掌握。 ## 修改Item组件 在子组件中有的同学喜欢把props的数据放到state中。这样使用的话,就会统一了。不用一会props,一会state了。 Item组件修改如下: ~~~ import React, {Component} from 'react'; export default class Item extends Component { constructor(props) { super(props); this.state = props; } removeItem(index) { this.state.rem(index) } render() { return ( <li className="list-group-item"> {this.state.item} <span className="close" onClick={this.removeItem.bind(this, this.state.index)}>&times;</span> </li> ); } } ~~~ ![](https://box.kancloud.cn/cc2202e80dbd939b7908daca12ce5d4a_1732x188.png) 会直接报错,错误如下 ``` Warning: Item: It is not recommended to assign props directly to state because updates to props won't be reflected in state. In most cases, it is better to use props directly. ``` 直接props值赋给state是不可以的。还不如你直接使用props呢,而不是state。 修改: ~~~ constructor(props) { super(props); this.state = { ...props }; } ~~~ 通过属性扩散将props的属性扩展到state里面。终于不再报错了。 ## 再次尝试效果 ![](https://box.kancloud.cn/68083e116a1713b6fee29a2107a997f5_674x255.gif) 去们发现我们明明输入的是01 02 03 但是显示的却全是01,这是为什么呀? 这里面如果我们数据添加使用的是push不会产生上面的问题,如果一旦用了unshift就会出现上面的问题。 首先constructor这个生命周期,默认只会执行一次。props中的数据也是扩展到state中的。数据都是拷贝过去的。 当父组件的props数据发生改变的时候子组件并未发生修改。那怎么办呢?在子组件中有专门的父组件发生改变的生命周期`componentWillReceiveProps`在这个生命周期中让state和最新props同步一下,这样就可以了。 ~~~ componentWillReceiveProps(nextProps, nextContext) { this.setState((state,props)=>{ return nextProps }) } ~~~ ![](https://box.kancloud.cn/325635c4e8db1813ce148cd6c6e29431_1100x319.gif) ## 思考题 请问为什么显示的都是01???拔高 这里面如果我们数据添加使用的是push不会产生上面的问题,如果一旦用了unshift就会出现上面的问题。看下图。 ![](https://box.kancloud.cn/7a127290e8b07bb149bdea465ba4a7e7_1872x961.png) 看完图相信大家有一定的理解了,其实说的更白一点不论父级属性改不改,子组件默认都不会变,除非`componentWillReceiveProps`生命周期调用了`this.setState()`这个方法才会引起下次的`render`生命周期执行,这样的话组件才会重新渲染成最新的数据。 # 总结 工作中到底和props好还是state好呢?这个还是看个人需求,有的人认为一会props一会state,不太好理解,那么就按照拔高里面的知识去做,通过扩散属性将props的值拷贝到state中。不过也要注意一点如果props发生改变的情况,需要在`componentWillReceiveProps`生命周期中,执行`this.setState((state,props)=>{ return nextprops})`否则父组件传递的数据子组件不会重新渲染。