系列文章目录
第一章:React从入门到进阶之初识React
第一章:React从入门到进阶之JSX简介
第三章:React从入门到进阶之元素渲染
第四章:React从入门到进阶之JSX虚拟DOM渲染为真实DOM的原理和步骤
第五章:React从入门到进阶之组件化开发及Props属性传值
第六章:React从入门到进阶之state及组件的生命周期
第七章:React从入门到进阶之React事件处理
一、React组件的定义及分类
- 组件允许我们可以将UI拆分成一个个独立的可复用的代码片段,并且可以对每个片段进行独立构思和管理。
- 从概念上讲,组件类似于JavaScript中的函数,它接收任意的输入参数(props),并返回用于描述页面展示内容的React元素
- React 组件可以定义为类(class)组件或函数(function)组件的形式
二、函数式组件
- 函数式组件其实就是写一个JavaScript中的函数,接收一个props对象作为参数,并返回用于描述页面内容的React元素(JSX语法),这就构成了一个函数式组件。
- 需要注意的是:组件名称必须以大写字母开头,因为React 会将以小写字母开头的组件视为原生 DOM 标签。例如,< div /> 代表 HTML 的 div 标签,而 < Clock/> 则代表一个组件,并且需在作用域内使用 Clock。
- 函数式组件属于静态组件,调取组件渲染出的结果,除非重新渲染组件,否则第一次渲染的内容不会改变(但可以通过React HOOKS解决)
下面我们以一个时钟组件为例编写一个函数式组件
function Clock(props){ return <div>{new Date().toLocaleString()}</div> }
上面代码就是一个有效的React组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素(JSX语法),这类组件就被称为“函数组件”,因为它本质上就是一个JavaScript函数。
三、类组件
- 上面我们已经说过,定义一个React组件可以有两种方式,除了定义函数组件外,我们还可以使用ES6中的类(class)来定义React组件
- 类组件顾名思义:就是通过声明一个类来实现定义一个React组件;定义类组件有如下几个特点:
- 1.定义类组件必须要继承自React的Component类或React.PureComponent
- 2.类中必须要定义一个名为render的函数,函数的返回值应该是React元素(JSX语法)
- 3.在类的内部默认会有个props属性(继承自Component)可以直接使用
- 4.与函数组件一样,组件名称必须以大写字母开头
- 5.在类的构造函数(constructor)中通过this.props访问属性值是获取不到的,因为这时props还没有挂载到this.props上,要等constructor执行完成之后才会挂载
- 6.如果非要在构造函数(constructor)中使用this.props来获取,则可以把外面传进来的props传递给constructor中的父类构造函数super从而实现this.props的挂载
- 7.类组件是动态组件,基于数据驱动视图渲染
下面我们还是以时钟组件为例:编写一个类组件
class Clock extends React.Component{ render(){ return <div>{new Date().toLocaleString()}</div> } }
四、组件的渲染
- 前面我们接触到的React元素都只是DOM标签,比如div、h1、span等等。那么我们自己定义的组件该如何渲染呢?
- 其实不管是函数组件还是类组件,渲染方式跟React元素是一样的,也是把函数名或类名当做普通标签直接使用即可
- 组件跟普通DOM标签一样,既可以是单闭合也可以是双闭合,如果标签内需要显示文本或者是其它子元素,这时就需要用双闭合标签包起来
- 在使用过程中,我们也可以给组件传递各种属性,这些属性(attributes)以及组件内部的子组件(children)会被转换为单个对象传递个组件,这个对象被称为“props”,就是在函数组件或类组件定义时我们提到的那个props
- ReactDOM在将组件转换为虚拟DOM时(转换为React.createElement(xxx)),会进行判断,如果生成的虚拟DOM对象的type是一个函数或者类(也就是说渲染的是一个函数组件或类组件),则首先会把函数执行并把解析出来的props传递给函数,如果是一个类,则会创建类的实例并把解析出来的props传递给这个类
- 然后在函数组件或类组件的内部我们就可以直接通过props.xxx或this.props.xxx来获取和使用组件渲染时传递过来的属性
- 传递进来的属性是只读的(只能获取不能修改里面的值),如果想要修改可以通过一个中间变量(状态state)来进行操作
function Welcome(props){ return <h1>Hello, {props.name}</h1> } ReactDOM.render( <Welcome name="Alvin" />,//单闭合标签 document.getElementById("root") );
上述代码运行结果,会在页面上显示 “Hello, Alvin”,Alvin就是我们通过name属性动态传递给Welcome组件的
- 上面的例子中,我们通过调用ReactDOM的render函数,并把< Welcome name=“Alvin” />作为参数传入
- React调用Welcome组件,并将{name:“Alvin”}作为参数传递给props,这个过程中组件Welcome会被作为普通函数执行
- 然后在Welcome组件中,将 < h1>Hello, Alvin</ h1>元素作为返回值返回
- 最后通过ReactDOM 将生成的虚拟DOM更新到真实的DOM中,将真实DOM更新为:< h1>Hello, Alvin</ h1>
五、组合组件
在日常项目开发中,我们自定义的组件中也可直接引入其它自定义组件作为子元素,这样我们就可以用同一组件来抽象出任意层次的细节,比如按钮、表单、对话框乃至整个屏幕等等,都可以以单独组件的形式来表示,然后再通过其它组件来使用。
例如,我们可以创建一个包含多个组件的App组件,在这个App组件中可以包含多次渲染的同一个Welcome组件,也可以是另外的Clock组件,还可以是原生的DOM元素标签。function Clock(props){ return <div>{new Date().toLocaleString()}</div> } function Welcome(props){ return <h1 style={{color:'red'}}>Hello, {props.name}</h1>; } function App(props){ return <div> <Clock /> <Welcom name="Alvin" /> <Welcom name="Yinnes" /> <Welcom name="Yaolu" /> </div> } ReactDOM.render( <App />, document.getElementById('root') );
六、提取组件
- 上面一节中我们将了组合组件,就是在一个组件中引入多个相同或不同的其它组件。
- 下面我们要介绍的是提取并封装组件,虽然说是拆分组件但其实跟上面的组合组件也类似,拆分后还是要进行组合使用。
- 有时候我们在写一些复杂的业务逻辑时,可能就需要些很多代码,那么过多的逻辑代码堆在一起,就很容易引起一些问题,并且后期也不利于维护,这时我们就需要把一些独立的业务逻辑拆分出来封装成一个独立的组件,然后再通过组合组件进行使用。
接下来我们以一个Comment组件为例进行提取
function Comment(props) { return ( <div className="Comment"> <div className="UserInfo"> <img className="Avatar" 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> ); }
- 该组件用于描述一个社交媒体网站上的评论功能,它接收 author(对象),text (字符串)以及 date(日期)作为 props。
- 该组件由于嵌套的关系,变得难以维护,且很难复用它的各个部分。因此,让我们从中提取一些组件出来。
首先,我们将提取 Avatar 组件:
function Avatar(props) { return ( <img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} /> ); }
- Avatar 不需知道它在 Comment 组件内部是如何渲染的。因此,我们给它的 props 起了一个更通用的名字:user,而不是 author。
- 我们建议从组件自身的角度命名 props,而不是依赖于调用组件的上下文命名。
我们现在针对 Comment 做些微小调整: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> ); }
接下来,我们将提取 UserInfo 组件,该组件在用户名旁渲染 Avatar 组件:
function UserInfo(props) { return ( <div className="UserInfo"> <Avatar user={props.user} /> <div className="UserInfo-name"> {props.user.name} </div> </div> ); }
进一步简化 Comment 组件
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> ); }
最初看上去,提取组件可能是一件繁重的工作,但是,在大型应用中,构建可复用组件库是完全值得的。根据经验来看,如果 UI 中有一部分被多次使用(Button,Panel,Avatar),或者组件本身就足够复杂(App,FeedStory,Comment),那么它就是一个可复用组件的候选项。
七、Props
- 上面在讲组件定义时,我们已经提到过props属性。当我们在渲染使用组件时无论是函数组件还是类组件,只要给标签元素添加了属性,那么这些属性都会被转换为props对象传递给函数或者类。
- 如果在渲染组件时使用的是双闭合标签,并且在标签内部又嵌套了其它组件或添加了文本内容,则这些子组件或文本内容同样会被转换成普通对象或者是字符串并放置在props下的children属性中
- 在组件中如果想获取传递的属性值或者子组件内容则可以直接通过props.[属性名]或props.children.xxx获取
function Welcome(props){ //通过props.name获取传递的属性值 return <h1>Hello, {props.name}</h1> } ReactDOM.render( <Welcome name="Alvin" />,//单闭合标签,传递了name属性 document.getElementById("root") );
下一章节中,我们将介绍一种新的概念,称之为 “state”。在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。