前言
组件化开发是现代前端框架的重要特点,一个完整的React项目不可能只具备一个组件。当组件数量较多时,数据必然会在组件之间进行必要的传递。我们将数据在组件之间的传递过程称为“组件之间的通信”。归纳起来,从通信方向上来讲包括以下两种:
- 父子组件之间的通信。
- 兄弟组件之间的通信。
本篇文章为大家详细讲解父子组件之间的通信,并结合实际案例对父子组件通信方式进行强化理解。下一篇文章会为大家讲解兄弟组件之间的通信。
约定
本文中涉及到的代码会大量用到React.Component和React.Fragment,这里先将这两个属性进行解构,文章中的代码部分就不在进行逐个书写了。
const {Component,Fragment} = React;
一、父组件向子组件传递数据
父组件通过调用子组件时设置自定义属性来将数据传递给子组件。此时,子组件通过this.props来接收父组件传递过来的数据。
我们通过一个简单地数据传递的例子来说明这个过程。
首先,我们先将这个案例中两个组件的基本结构书写出来,再往内部添加实现功能的细节代码。
父组件App的代码如下所示。
class App extends Component{
constructor(props){
super(props);
this.state={
book:{} /*暂时定义一个空对象*/
}
}
/*按钮的单击事件*/
btnClick(){
}
render(){
return (
<Fragment>
<h2>我是父组件</h2>
<button onClick={()=>this.btnClick()}>向子组件传递数据book</button>
<hr />
<Child></Child> {/*调用子组件*/}
</ Fragment>
)
}
}
上述代码中,父组件App已经准备好了名为book的state数据,同时也通过对<Child>标记对的书写调用了子组件Child。按钮的单击事件执行名为btnClick()的方法。父组件的准备工作已经就绪。
子组件Child的代码如下所示。
class Child extends Component{
render(){
return (
<Fragment>
<h2>我是子组件</h2>
<div className="bookData"></div> {/*准备接收并显示父组件传递过来的数据*/}
</ Fragment>
)
}
}
上述代码中,书写了子组件Child的简单格式。准备接收父组件传递过来的数据并显示在类名为bookData的<div>容器中。
其次,我们为父组件App的单击事件书写代码,让按钮单击时为state数据book赋值。
btnClick(){
let book={
name:'《哈利波特与魔法石》',
author:'J.K.罗琳',
price:54
}
this.setState({ book });
}
所有的准备工作都做好后,我们开始在App和Child之间传递数据。
(1)父组件通过调用子组件时设置自定义属性来将数据传递给子组件。在父组件中添加下列代码。
<Child bookInfo={this.state.book}></Child>
在父组件中调用Child组件时,为其设置了一个名为bookInfo的自定义属性,该属性的取值绑定了父组件state区的book数据。
(2)子组件通过this.props来接收父组件传递过来的数据。在子组件中添加下列代码。
render(){
const bookData=this.props.bookInfo
return (
<Fragment>
......
</Fragment>
)
}
在子组件中的任意位置都可以使用this.props.bookInfo来接收父组件传递过来的数据。上述代码中是在render()函数内接收数据的。接下来我们将子组件中接收到的数据展示在DOM结构中即可。
<div className=”bookData”>
<div>书名:{bookData.name}</div>
<div>作者:{bookData.author}</div>
<div>单价:{bookData.price}</div>
</div>
大家把上述代码组装好,单击按钮,就可以看到页面中显示的父组件中传递过来的数据了。
二、子组件向父组件传递数据
子组件通过调用父组件的自定义事件向父组件传递数据。父组件在调用子组件时设置一个自定义事件,事件代码中接收子组件传递过来的数据。
在例1的基础上,我们为子组件Child也设置一个按钮,在单击这个按钮时,子组件向父组件传递一个字符串,内容为“book数据收到了”,并显示在父组件中。
子组件JSX代码改为:
<Fragment>
<h2>我是子组件</h2>
<div> {/*准备接收并显示父组件传递过来的数据*/}
<div>书名:{bookData.name}</div>
<div>作者:{bookData.author}</div>
<div>单价:{bookData.price}</div>
</div>
<button onClick={()=>this.btnClick()}>通知父组件收到了</button>
</ Fragment>
在子组件的单击事件中如何将数据传递给父组件呢?
(1)子组件通过调用父组件的自定义事件向父组件传递数据。在子组件中添加下列代码。
btnClick(){
let reply="book数据收到了";
this.props.onCustom(reply);
}
在子组件按钮的单击事件中,通过this.props.onCustom()代码对父组件中设置的自定义事件进行了调用。
(2)父组件在调用子组件时设置一个自定义事件,事件代码中接收子组件传递过来的数据。在父组件中添加如下代码。
<Child bookInfo={this.state.book} onCustom={(data)=>{this.childCustom(data)}}></Child>
childCustom(data){
console.log(data);
}
在父组件中,自定义事件onCustom的事件代码为childCustom()方法,当子组件按钮的单击事件发生时,会将实参reply传递给data,最终在父组件的控制台中输出了data数据。
整个过程可以利用下图进行说明。
完整的案例代码如下所示:
class App extends Component{
constructor(props){
super(props);
this.state={
book:{} /*暂时定义一个空对象*/
}
}
/*按钮的单击事件*/
btnClick(){
let book={
name:'《哈利波特与魔法石》',
author:'J.K.罗琳',
price:54
}
this.setState({ book });
}
/*自定义事件*/
childCustom(data){
console.log(data);
}
render(){
return (
<Fragment>
<h2>我是父组件</h2>
<button onClick={()=>this.btnClick()}>向子组件传递数据book</button>
<hr />
<Child bookInfo={this.state.book} onCustom={(data)=>this.childCustom(data)}></Child>
</ Fragment>
)
}
}
class Child extends Component{
// 按钮的单击事件
btnClick(){
let reply="book数据收到了";
this.props.onCustom(reply);
}
render(){
const bookData=this.props.bookInfo;
return (
<Fragment>
<h2>我是子组件</h2>
<div> {/*准备接收并显示父组件传递过来的数据*/}
<div>书名:{bookData.name}</div>
<div>作者:{bookData.author}</div>
<div>单价:{bookData.price}</div>
</div>
<button onClick={()=>this.btnClick()}>通知父组件收到了</button>
</ Fragment>
)
}
}
ReactDOM.render(
<App></App>,
document.getElementById('app')
);
三、案例:ToDoList
如果上面的讲解大家能够理解,我们继续通过一个实际的案例来更加系统化的练习父子组件之间的通信方式。ToDoList是一个待办事项列表案例,用户可以通过在文本框中输入一天的安排,来记录整天的待办事项。当某一项事件完成后,可以单击左侧的关闭按钮将其删除,表示待办事项的结束。
要完成这样一个案例,首先需要对其进行组件划分。我们将整个项目划分为两个组件:父组件TodoList,用来显示文本框和按钮;子组件TodoItem,用来显示下面的待办事项,并可以将其删除。
整个案例我借助了Bootstrap框架实现样式布局,不会使用Bootstrap的小伙伴也没有关系,可以自行对类名进行编写,让其显示为一个基本的样式和外观。
下面对案例进行的细节分析,我将Bootstrap类名进行了删除,大家将学习重点放在React组件数据传递的方法上,在最后完整的项目代码中,我会将Bootstrap类名再为大家添加上。
1、父子组件的基本格式
我们首先先书写两个组件的基本格式,并让父组件TodoList调用子组件TodoItem。
父组件TodoList的代码格式。
class TodoList extends Component{
constructor(props){
super(props);
this.state={
list:['第一项','第二项']
};
}
//单击添加按钮
addTodo(){
}
render(){
return (
<Fragment>
<h2>ToDoList 项目(React版)</h2> <hr/>
<input type="text" placeholder="输入待办事宜..." />
<button onClick={()=>this.addTodo()}>添加待办事宜</button>
<div style={{marginTop:'30px'}}>
<ul>
{
this.state.list.map((item,index)=>{
return <TodoItem></TodoItem>
})
}
</ul>
</div>
</Fragment>
)
}
}
子组件TodoList的代码格式。
class TodoItem extends Component{
//单击关闭按钮
deleteItem(){
}
render(){
return (
<Fragment>
<li>
<span className="delete" onClick={()=>this.deleteItem()}>×</span>
{}
</li>
</Fragment>
)
}
}
2、在父组件中处理文本框
表单元素在React中可以使用受控组件和非受控组件来实现,我们在后面的文章中会系统讲解。本案例要用到文本框,我们采用受控组件的方式为其进行处理。
受控文本框需要使用value属性来设置文本框的初始值绑定,同时还要为文本框绑定onChange事件来保证当文本框内容发生变化时,可以将文本框的内容实时保存在state数据中。这就需要我们在state数据区为文本框设置一个指定初始值同时还可以接受文本框内容变化的变量。该变量我们起名为inputValue。
此时父组件的state区数据如下所示。
this.state={
list:['第一项','第二项'],
inputValue:'',
};
父组件的文本框也要添加 value属性和onChange事件。父组件代码如下所示。
<input type="text" placeholder="输入待办事宜..." value={this.state.inputValue} onChange={()=>this.inputChange(event)} />
当文本框内容发生变化时,要及时地将文本框变化的内容反馈给state区的inputValue,这里在onChange事件中,我们采用event.target.value来获取文本框的值。则onChange事件的inputChange()方法代码如下所示。
//文本框内容发生变化
inputChange(event){
this.setState({
inputValue:event.target.value
})
}
3、父组件“添加待办事件”按钮的单击事件
单击父组件的“添加待办事件”按钮,我们应该将文本框中的内容加入到state数据区的list数据中,同时将文本框中的内容清空。父组件代码如下所示。
//单击添加按钮
addTodo(){
let list=[...this.state.list];
list.push(this.state.inputValue);
this.setState({
list,
inputValue:'' {/*清空文本框中的内容*/}
})
}
4、父组件向子组件传递数据
父组件应该将list数组中的数组元素传递给子组件TodoItem,这就要求父组件在调用子组件时为子组件设置自定义属性。父组件代码如下所示。
<ul>
{
this.state.list.map((item,index)=>{
return <TodoItem text={item} index={index} key={index}> </TodoItem>
})
}
</ul>
上述代码除了key属性是在遍历过程中用来指定唯一关键字的之外,父组件TodoList为子组件TodoItem传递了两个自定义属性:text和index。text的值为state区list数据的数组元素,即用户输入的待办事项;index的值为state区list数据中每一个数组元素的索引值,以便在子组件中使用。
子组件接收到父组件传递过来的数据后,子组件代码如下所示。
render(){
const {text,index}=this.props;
return (
<Fragment>
<li>
<span className="delete"}>×</span> {text}
</li>
</Fragment>
)
}
5、删除待办事项
删除待办事项时,用户要单击子组件TodoItem中任意一项前面的关闭按钮,单击后需要从父组件中将这一项从state区的list数组中删除。这就涉及到子组件向父组件的传递,子组件要将单击的那一项的index索引值传递给父组件,以保证父组件知道删除list数组中的哪一项数组元素。
(1)子组件调用父组件的自定义事件。
在子组件中为关闭按钮绑定onClick事件,并指定事件发生时执行deleteItem()方法。
<Fragment>
<li>
<span className="delete" onClick={()=>this.deleteItem()}>×</span>
{text}
</li>
</Fragment>
deleteItem()方法如下所示。
//单击关闭按钮
deleteItem(){
const {index,onCustom}=this.props;
onCustom(index);
}
上述代码中,onCustom是父组件在调用子组件时设置的自定义事件,并使用this.props解构出来。
(2)父组件接收子组件传递的数据并实现删除功能。
父组件在调用子组件时,设置了一个名为onCustom的自定义事件用来接收子组件传递过来的数据,并实现对list数组中指定数组元素的删除。
<ul>
{
this.state.list.map((item,index)=>{
return <TodoItem text={item} index={index} key={index} onCustom={()=>this.closeItem(index)}></TodoItem>
})
}
</ul>
自定义事件onCustom发生时执行closeItem()方法,该方法的代码如下所示。
//删除待办事宜
closeItem(index){
let list=[...this.state.list];
list.splice(index,1);
this.setState({
list
})
}
至此,整个项目的各个组成部分分析完毕,大家要重点理解父子组件数据传递的方式,这在将来的项目开发中是非常常用的。
四、ToDoList案例的完整代码
完整代码中加上了表示样式的Bootstrap类名,完整的案例代码如下所示。
class TodoItem extends Component{
//单击关闭按钮
deleteItem(){
const {index,onCustom}=this.props;
onCustom(index);
}
render(){
const {text,index}=this.props;
return (
<Fragment>
<li key={index}>
<span className="delete" onClick={()=>this.deleteItem()}>×</span>
{text}
</li>
</Fragment>
)
}
}
class TodoList extends Component{
constructor(props){
super(props);
this.state={
list:['第一项','第二项'],
inputValue:'',
};
}
//文本框内容发生变化
inputChange(event){
this.setState({
inputValue:event.target.value
})
}
//单击添加按钮
addTodo(){
let list=[...this.state.list];
list.push(this.state.inputValue);
this.setState({
list,
inputValue:''
})
}
//删除待办事宜
closeItem(index){
let list=[...this.state.list];
list.splice(index,1);
this.setState({
list
})
}
render(){
return (
<Fragment>
<div className="container">
<div className="row">
<h2>ToDoList 项目(React版)</h2> <hr/>
</div>
<div className="row">
<div className="input-group">
<input type="text" className="form-control" placeholder="输入待办事宜..."
value={this.state.inputValue}
onChange={()=>this.inputChange(event)}
/>
<div className="input-group-btn">
<button className="btn btn-success" onClick={()=>this.addTodo()}>添加待办事宜</button>
</div>
</div>
</div>
<div className="row" style={{marginTop:'30px'}}>
<ul>
{
this.state.list.map((item,index)=>{
return (
<TodoItem text={item} index={index} key={index} onCustom={()=>this.closeItem(index)}>
</TodoItem>
)
})
}
</ul>
</div>
</div>
</Fragment>
)
}
}
ReactDOM.render(
<TodoList></TodoList>,
document.getElementById('app')
);
总结
本文是React系列教程的第四篇文章,主要为大家讲解了React父子组件的通信方式。总结如下。
父组件向子组件传递数据:
- 父组件通过调用子组件时设置自定义属性来将数据传递给子组件。
- 此时,子组件通过this.props来接收父组件传递过来的数据。
子组件向父组件传递数据:
- 子组件通过调用父组件的自定义事件向父组件传递数据。
- 父组件在调用子组件时设置一个自定义事件,事件代码中接收子组件传递过来的数据。
关于作者
小海前端,具有18年Web项目开发和前后台培训经验,在前端领域著有较为系统的培训教材,对Vue.js、微信小程序开发、uniApp、React等全栈开发领域都有较为深的造诣。入住Segmentfault,希望能够更多的结识Web开发领域的同仁,将Web开发大力的进行普及。同时也愿意与大家进行深入的技术研讨和商业合作。