0%

【React基础-4】组件 & Props

本文是【React基础】系列的第四篇文章,这篇文章中我们介绍一下在react开发中经常提及的”组件”以及”props”到底是什么东西,以及它们之前的关系,并且简单介绍下组件的种类:函数组件和类组件。

概述

上文中我们简单介绍了”元素”的概念,并且了解到元素构成组件,组件构成页面这样一个规律。所以通过上文了解了元素之后,这篇文章我们来继续了解下组件的相关概念。

项目demo地址

1
https://github.com/xuqwCloud/reactbasic

组件简介

组件其实就是我们在页面中看到的某一部分页面元素或者某一个页面元素,比如一个按钮、一个输入框、一部分图片加文字的页面等等,这些都是我们通过观看浏览器页面对于”组件”这个词语的最直观的理解,所以它对应到我们写的代码中的话,组件其实就是一段HTML代码。

那既然组件就是一段HTML代码,为什么还要搞出来组件这个名称呢,我们如果描述指定的HTML片段代码的时候直接就说是id为什么什么的一段div包裹的HTML代码就行了呗。其实组件的出现是为了解决react开发过程中代码的复用,更加具体点的说是为了解决界面UI的复用,这里面包含单纯的UI复用与UI和逻辑复用,但是在这里大家不必细分,只需要知道组件可以解决界面的一部分页面元素复用问题即可。举个例子的话就是,我们有个介绍各个手机参数的系统,里面有三个页面:A页面是苹果手机的参数配置介绍;B页面是三星手机的参数配置介绍;C页面是OPPO手机的参数配置介绍。这三个页面的顶部和底部都是我们手机介绍系统统一的顶部和底部,三个页面仅仅是中间内容区域有所不同而已,那么我们在开发的时候,有了组件的概念以后就没必要重复开发三个相同的顶部区域和三个相同的底部区域,仅仅开发一个顶部区域和一个底部区域,然后在三个页面中复用顶部区域和底部区域即可,这样大大简化了代码量,并且降低了出错的可能性,这就是我们通常说的组件化,也仅仅是组件化的冰山一角。

回到react中的话,组件其实跟函数类似,它接受任意参数,这里的任意参数称为”props”,然后返回一个用于描述部分页面元素的react元素,这就是在react中对于组件的定义。结合一个场景来解释的话就是:一个页面上的按钮button就是一个组件,有时候我们需要动态改变按钮的背景色,所以这个按钮组件得要接收表示颜色的变量,然后在不同事件下通过接收到的表示不同颜色的变量来给它赋值不同的背景色,最后将其返回并渲染到页面上,然后我们看到的效果就是这个按钮的背景色是随着用户不同的操作事件来动态改变的。上述例子中的按钮就是一个组件,它接收表示颜色的变量,将它赋值给控制按钮背景色的css属性后,将一个含有最新css属性值的按钮元素返回,然后渲染到页面,就是这样一个流程。

组件定义

在react中定义一个组件的话有以下两种方式:

  • 通过编写JavaScript函数来定义一个组件,这种方式定义的组件被称之为函数组件
  • 通过ES6的class来定义一个组件,这种方式定义的组件被称之为类组件

关于函数组件和类组件有什么区别,具体在什么情况下使用函数组件、什么情况下使用类组件的相关问题我们后续介绍,目前大家只需要知道react中定义组件的两种方式即可。接下来我们结合代码看看这两种方式具体是怎么定义组件的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//函数组件定义
function FuncComponent(props) {
return <h1>这是一个函数组件,{props.name}是它的属性值</h1>;
}
ReactDOM.render(<FuncComponent name="xbeichenbei.com" />, document.getElementById('root'));


//类组件定义
class ClassInitComponent extends React.Component {
render() {
return <h1>这是一个类组件,{this.props.name}是它的属性值</h1>;
}
}
ReactDOM.render(<ClassInitComponent name="xbeichenbei.com" />, document.getElementById('root'));

上述的两段代码完全是等效的,只不过第一段是通过函数形式定义的组件,第二段是通过类的形式定义的组件而已。细心地同学可以发现我们在函数组件里传入了一个props的参数对象,然后在返回的元素里将这个参数对象的name属性值作为react元素的一部分返回,最终渲染到了页面上;类组件中同样有props的使用,但是没有看到接收这个参数对象的代码,这是因为我们省略了类组件定义的一部分代码,下方才是完整的类组件定义代码:

1
2
3
4
5
6
7
8
9
10
//类组件定义
class ClassInitComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1>这是一个类组件,{this.props.name}是它的属性值</h1>;
}
}
ReactDOM.render(<ClassInitComponent name="xbeichenbei.com" />, document.getElementById('root'));

通过上述代码可看到,比起之前的代码,新的代码中多了constructor()构造函数,这个构造函数接受一个props的参数对象,然后在构造函数中通过一个super()方法来调用props,这里大家目前不必知道这段代码的详细含义,仅仅记住这是一个固定写法即可。我们上述通过两段代码定义的函数组件和类组件最终渲染到页面的效果如下所示:

img

img

渲染组件

在组件定义部分我们仅仅介绍了props在函数组件和类组件中的使用,并没有介绍它是从哪传到函数组件或类组件中的,在这里我们就介绍下props从哪传过来。其实大家通过上述代码的话都已经大概知道props从哪来了,就是从下面这行代码中来的:

1
ReactDOM.render(<FuncComponent name="xbeichenbei.com" />, document.getElementById('root'));

更具体点说的话,这个props其实就是我们自定义组件在使用的时候,我们给自定义组件设置的一些组件属性,就像HTML的标签属性id、class、style……这些一样。我们用下方的代码来看一下具体的使用:

1
2
3
4
5
6
//组件渲染
function WelCome(props) {
return <h1>你好,{props.name}。</h1>;
}
const element = <WelCome name="X北辰北" />;
ReactDOM.render(element, document.getElementById('root'));

上述代码中我们定义了一个函数组件WelCome,然后将这个组件赋值给element变量,最后将这个变量传递到ReactDOM.render()方法中。以下是对这个过程的详细解释:

  1. 调用ReactDOM.render()方法,并传入WelCome组件作为参数
  2. React调用WelCome组件,并将{name:"X北辰北"}作为props传入
  3. WelCome组件将<h1>你好,X北辰北。</h1>元素作为返回值
  4. React DOM将DOM高效的更新为<h1>你好,X北辰北。</h1>

以上就是对这段代码执行过程的详细解释,到目前为止大家可以看到,组件定义时接收到的props参数对象的值,其实就是我们在后期使用这个组件时为组件自定义的一个标签属性。

在以上的代码中大家需要注意的一点就是:react中组件的名称必须要大写,如果组件名称是小写的话react会默认为它是一个原生的DOM标签。

组合组件与提取组件

组合组件的含义其实很简单,就是在我们平常的开发中,一个web页面是通过不同的组件组合起来的,最简单的就是上中下结构的页面,包含顶部组件、中间内容区域组件、底部组件组合而成,大致意思就是这样,同样的,我们开发的组件也可以在不同地方重复使用,例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function WelCome(props) {
return <h1>你好,{props.name}。</h1>;
}
function App() {
return (
<div>
<WelCome name="X北辰北" />
<WelCome name="xbeichenbei.com" />
<WelCome name="个人博客" />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));

img

提取组件的意思跟组合组件相比的话其实有点反向的意思,提取组件就是将一个组件拆分成更小的几个组件。我们直接拿一个例子来解释:

1
2
3
4
5
6
7
8
9
10
11
12
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img src={props.author.avatarUrl} alt={props.author.name} />
<div className="UserInfo-name">{props.author.name}</div>
</div>
<div className="Comment-text">{props.text}</div>
<div className="Comment-date">{formatDate(props.date)}</div>
</div>
);
}

上述是一个Comment组件的定义,大家有了之前的了解,对这个组件类别一定很清楚了,没错,它是一个函数组件,如果我们在使用它的时候给它指定author、text、date三个属性值就可以正常使用这个组件了。但是有个问题已经产生了——这个组件太臃肿了,它其实是一个社交网站的评论模块的组件,按理说它里面包含用户信息组件、评论文字组件和时间组件,但是在Comment组件里我们将这三块的东西全部写在了一块,如果在其他页面也需要类似的评论功能,但是不需要评论时间这部分元素的话,我们只能重写一个Comment组件,删除掉表示时间的部分代码,这样做起来太麻烦了,每一个组件都得不到很好地复用,而且如果Comment组件内部的某一个地方出现问题,我们就需要查阅全部的Comment组件代码,这对后期维护组件的人员来说有点恼火了,那我们就需要对Comment组件进行组件提取,其实也就是组件拆分工作。

1、首先我们拆分一个用户头像这部分内容,将img标签拆出来单独形成一个组件,这样在后期其他地方使用头像组件时直接可以复用,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Avatar(props) {
return <img src={props.user.avatarUrl} alt={props.user.name} />;
}
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} />
<div className="UserInfo-name">{props.author.name}</div>
</div>
<div className="Comment-text">{props.text}</div>
<div className="Comment-date">{formatDate(props.date)}</div>
</div>
);
}

在上述代码中我们将表示用户头像的img标签进行了组件提取,形成了一个新的组件Avatar,而且Avatar组件的props我们没有用author属性名称而是用了user这个名称,所以在这里大家也要注意的是:组件提取的时候,props的名称应该从组件自身的角度来取名,尽量不要跟调用组件的上下文有依赖。

2、接下来我们再将代表用户信息的UserInfo这部分页面元素拆出来提取成一个组件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Avatar(props) {
return <img src={props.user.avatarUrl} alt={props.user.name} />;
}
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">{props.user.name}</div>
</div>
);
}
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">{props.text}</div>
<div className="Comment-date">{formatDate(props.date)}</div>
</div>
);
}

经过上述两个过程的提取,最终Comment组件中已经形成了一个可复用的用户信息组件UserInfo,这个UserInfo组件内部还嵌套了一个可复用的头像组件Avatar,这样一来在后期其他地方需要到这两个组件的时候,就可以直接复用了。

对于上述代码中剩下的评论内容元素和评论时间元素,大家有兴趣的话也可以继续提取出来相应的组件,便于后期在其他地方进行组件复用,同时也便于Comment组件的后期维护。

在组合组件和提取组件的时候大家可能觉得这些步骤很多余,也很费力,但是在一个大型的web系统中构建一个可复用的组件库是完全有必要的,根据我们开发经验来讲,如果UI中有一部分被多次使用或者一个组件本身就已经很复杂,那么我们就该考虑将这个组件作为一个可复用的组件了。

Props的只读性

关于props的只读性这一块大家只需要知道以下两点即可:

  • 无论是函数组件还是类组件,都不允许修改组件自身的props
  • 所以react组件都必须像纯函数一样保护它们的props不被更改

但是在我们开发的时候,有些组件的UI渲染是随着用户操作或者网络请求来动态改变的,那这种需求应该怎么做呢,下一篇文章我们给大家介绍另一个名词”state”,state允许用户在不违反上述规则的情况下react组件可以随着用户操作、网络请求等事件动态的改变页面的输出内容。