react初探(二)之父子组件通信、封装公共组件
一、前言
在组件方面react和Vue一样的,核心思想玩的就是组件,下面举两个组件常用的情景。
场景一:假如我们现在有一个页面包含表格以及多个弹框,这种时候如果将这个页面的业务代码写在一个组件中,那么这一块的代码会看着非常恶心。如果这个时候我们将这个页面的表格以及弹框这些单独的模块分别写成组件的形式,然后再在这个页面中将这些组件引入进来,那样我们的代码会看着非常整洁。这样做会需要使用到父子组件之间的通信,下面会详细解释。
场景二:日常项目中我们会经常遇到某一个功能会在不同地方使用,但是每次使用的时候都会有一些小差别。比如在一个页面中我们有一个地方叫 “上传头像”,在另外一个地方叫 “上传证书” 。这种情况我们封装一个公共的上传图片的组件,但是这个组件在使用的时候在不同的地方展示的文案或者样式不同。react提供一种类似于Vue的插槽(slot)的功能,自定义的组件使用children属性将子元素传递到输出,具体用法下面会详细解释。
二、定义组件
目前有两种常用的方法定义组件:函数式组件、类组件。
(1) 函数式组件:这种组件表示每一个函数就是一个组件,组件具体的内容由return的值决定。这种组件主要用于纯展示性组件,不存在state的操作。
function Name() {
return (
<div>我是Name组件</div>
)
};
优点:结构简单、写法简单。
缺点:不能使用生命周期的钩子函数、不能进行复杂的操作、只能通过函数传参的形式传递props。
(2)类组件:一般一个jsx文件就是一个类组件,该写法为es6的写法(PS:es5也有一种React.createClass组件写法,但是官方现在主推es6这种写法,所以本文不讲es5的这种写法)。使用类组件可以在constructor钩子函数中初始化一个state,相当于Vue中的data,具体关于state的操作可以看我的Reacr初探(一)
class Age extends Component {
render() {
return (
<div>我是Age组件</div>
)
}
}
优点:可以进行复杂的操作、具有生命周期钩子函数、拥有state。
缺点:和函数式组件比起来写法要复杂一点。
PS:后面使用的组件均为类组件
三、父组件 => 子组件
这里我们将引入props的概念了,官方描述:
props
是React组件的输入内容。 它们是从父组件传递给子组件的数据。
说白了props就是一个对象,是由父子件的属性提供。父组件提供的属性值可以为变量、常量(变量和常量可以用于 父组件 => 子组件)、函数(可以用于 子组件 => 父组件)、react元素(可以用于封装公共组件),在子组件中使用this.props就可以访问到props对象。
父组件中:
<Age num="18"></Age>
子组件中:
render() {
return (
<div>我是Age组件,我今年{this.props.num}岁了</div>
)
}
注意:props为只读的,所以不能修改props的值,如果修改会报错
但是如果传入props的某一个属性的值是一个对象,在子组件中可以改变传入的该对象的属性或者方法是不会报错的。同时父组件的该对象的值也会改变,但是由于不是使用setState改变的状态,所以组件不会重新渲染。好像有点绕口,我们来举个例子看看。
父组件中:
constructor(props) { super(props) this.state = { userInfo: { name: 'yjj', age: 18 } } } render() { return ( <div className="App"> <Person info={this.state.userInfo}></Person> <div onClick={()=> console.log(this.state.userInfo)}>点我打印userinfo对象</div> </div> ); }
子组件中:
render() { this.props.info.age = 25 return ( <div> <div>姓名:{this.props.info.name}</div> <div>年龄:{this.props.info.age}</div> </div> ) }
父组件中定义了一个userInfo对象,并且将该对象的值作为info属性传入到子组件。子组件中对this.props.info对象的age属性重新赋值,由于子组件的this.props.info对象指向的指针和父组件中的userInfo对象的指针是一样的。所以父组件中的userInfo对象的age属性实际上也是改变了的,等于25。但是在react中state的值需要使用setState()方法修改,所以不会重新渲染对应的组件。
四、子组件 => 父组件
子组件 => 父组件的通信实际上就是通过父组件给子组件传递的属性值为函数,子组件在相应的情况下调用该函数。
父组件中:
constructor(props) { super(props) this.state = { age: 18 } } changeAge = () => { this.setState({ age: 20 }) } render() { return ( <div className="App"> <Person changeAgeEvent={this.changeAge}></Person> <div>当前年龄:{this.state.age}</div> </div> ); }
子组件中:
render() {return ( <div> <div onClick={() => {this.props.changeAgeEvent()}}>点我改变年龄的值</div> </div> ) }
父组件的changeAgeEvent的值为this.changeAge函数,在子组件中使用this.props.changeEvent()调用父组件的函数。
如果要子组件给父组件传参,只需要在调用函数的时候加上需要的参数即可。
父组件中: changeAge = (age) => { this.setState({ age }) } 子组件中: <div onClick={() => {this.props.changeAge(100)}}>点我改变年龄的值为100</div> // 子组件给父组件传参
五、封装公共组件
在前言中提到的场景二的问题,我们希望可以像使用普通的div标签一样使用自定义的组件。例如: <UploadImg><h4>上传头像</h4></UploadImg>。如果这样我们就不用考虑封装的UploadImg组件是在 “上传头像” 还是 “上传证书” 的情况下使用UploadImg组件的时候根据使用的场景传入不同的文案即可。例如:上传头像时使用:<UploadImg><h4>上传头像</h4></UploadImg>; 上传证书时使用:<UploadImg><h4>上传证书</h4></UploadImg>。
在子组件中使用this.props.children就可以获取父组件通过<UploadImg><h4>上传头像</h4></UploadImg>传递过来的 “<h4>上传头像</h4>” 。
父组件中: <UploadImg><h4>上传头像</h4>/UploadImg> 子组件中: <div>父组件传递过来的:{this.props.children}</div>
使用this.props.children时,父组件只有一个入口。有的时候使用父组件时有的时候我们需要有多个入口,这种情况我们就需要约定自己的属性而不是使用children。实际上children也是表示是父组件中的一个属性,父组件的属性值不仅限于常量、变量、函数,还可以为react元素。
父组件中: <UploadImg btnText={<h4>上传头像</h4>} btnNum={<p>1</p>}></UploadImg> 子组件中: <div> <div>父组件传递过来的btnText:{this.props.btnText}</div> <div>父组件传递过来的btnNum:{this.props.btnNum}</div> </div>
父组件中的 btnText属性和btnNum属性的值都是为react元素,在子组件中使用this.props.btnText和this.props.btnNum就可以取得对应的react元素。
六、总结
写了一些demo发现,一个项目说白了就是一个根组件组成,但是这个根组件又由很多子组件组成,这些子组件又是由这些子组件的子组件组成。就这样层层嵌套下去,最终形成的就是一个完整的项目。目前react初探差不多就到此为止,更多深入的知识需要后面在实际项目中使用react后再来总结。