![Taro多端开发权威指南:小程序、H5与App高效开发实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/719/38209719/b_38209719.jpg)
2.2 组件化
长期以来,前端开发者都在探索如何更好地管理项目模块,都在思考如何设计各模块中类似的UI及逻辑以达到高效复用的目的。早期我们通过定义通用代码文件,在项目中通过script标签引入方式完成复用,这种方式确实能在一定程度上实现通用代码复用、对应模块版本管理等需求,但在大型项目中,这种方式会显得很脆弱,模块之间的依赖管理能力欠缺。后来,有了Bower、Grunt、Gulp等,解决了模块文件或依赖间的控制问题。再后来,有了Webpack,有了各种模块化规范,如AMD、CMD、Commonjs、ES module等,前端开发才进入一个新的世纪。一路进化,最终组件化、MVC、面向对象编程、函数式编程等思想才得以迸发。
2.2.1 初识组件
首先来看一个例子,下图是京东商城首页,我们站在开发者的角度来分析一下这个网页的页面结构。
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_46_1.jpg?sign=1738844949-aqQgsrdzf9v1a0vPn6Vmd4p93uqQSFAJ-0-c261d071ee651107d933ef0561a2cc6b)
在使用Taro开发这个页面时,首先考虑将页面内容拆分为图中标注的6个模块,设计好模块间的数据与UI交互之后,便可以单独开发每个模块,最终组合各个模块,完成开发。这里拆解的6个部分,正是6个独立组件。
2.2.2 组件定义
Taro中的组件分为两种,一种是基于类创建的组件,被称为类组件;一种是基于函数创建的组件,被称为函数组件。
1.类组件
定义类组件是一件很容易的事情,你只需要定义一个类,这个类继承自Taro.Component,且在组件中定义render方法并返回值即可,代码示例如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_47_1.jpg?sign=1738844949-CJMBwgI2COQsug8On5zh3y22j1EmewOW-0-ca4ca7b9a72477ff7634124ce011b47f)
当然,还有为了做优化提供的另一种类组件,关于该组件的原理与用法,我们将在实战优化部分进行详细介绍,代码示例如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_47_2.jpg?sign=1738844949-kUWtakodlVhFba3tCXiJZNu2y7mXADrF-0-8e1ef2337812b11402a800189e64c185)
2.函数组件
函数组件相较于类组件,定义更便捷,使用更灵活,尤其搭配Hooks使用能够在某些场景下替代类组件。函数组件的定义如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_1.jpg?sign=1738844949-xNNMY6mlgZuRF0HS5UcKPrailazvXkfR-0-8ad78f7ae17967515cf64ef974ee0773)
或者使用箭头函数,写法如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_2.jpg?sign=1738844949-fm96udJ8WI3ejkbMPkaT3mEIyXZkSlUZ-0-0ec5f2d6d3d2e7725ccf1362a2d04fea)
注:在组件中,无论是否使用Taro这个对象,都应该将@tarojs/taro包引入。无论组件返回值多么简单,都尽量使用@tarojs/components提供的组件包裹,而不应该直接返回数字或字符串等。
定义好组件后,最终需要将最上层组件也就是根组件挂载到DOM节点上:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_3.jpg?sign=1738844949-eQFCEf48JYxYc9OlimvBzJPBwcrnEedz-0-58feb1526f94aa875b268eff4c6b52c2)
为了方便讲解,后续章节将统一使用类组件,当然我们也会在Hooks章节详细介绍函数组件的知识。
2.2.3 props
很多时候,组件中使用的某些数据可能需要外部提供,就像我们使用HTML中的图片标签时,需要设置src属性才能显示对应图片。假如现在定义了一个名叫Timg的类似图片img的组件,组件内部应该怎样获取外部传入的属性数据并使用呢?答案是使用props,代码示例如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_49_1.jpg?sign=1738844949-u44bsGntObdNaMVH4TVOI6ZwLAaiaWuw-0-ffcda8be7679c1f5e80488df316db133)
效果如下图所示。
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_49_2.jpg?sign=1738844949-cWKpuPdtcoeV5TA2se6U6AlFvkHcqSHU-0-73a873b385bcad2d78e200485f59f7c4)
通过props,可以将数据传递给组件,组件内部通过this.props获取对应的属性数据,渲染即可。
有时,某些属性数据并不一定是外部必须传入的,因此我们在定义组件时,可以设置默认属性数据,如上例:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_50_1.jpg?sign=1738844949-PZztDpq2dciyi5brO7ME8YmhMrNgUyCO-0-185c3ee1870a2e64bacc07a3a1f7a2a5)
若在使用Timg组件时不传入src属性,则Timg组件会使用我们通过defaultProps设置的src属性的默认数据渲染页面。反之,Timg组件会使用外部传入的src属性数据进行渲染。
2.2.4 state
组件中还有一类数据,它具备以下几个特征:
· 数据私有,仅供组件内部使用。
· 数据需要根据某些操作发生更改,并触发视图更新。
这些特征正是组件状态state期望具备的,满足这些特征的数据一般都要考虑放入组件状态state中。
我们现在想设计一个组件,组件中有一个状态count,该值每过一秒增加1,并在增加后显示在页面中。代码设计如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_50_2.jpg?sign=1738844949-IfEl4bcY9CeBvXWSA3I3iusaptzsrWot-0-4b5b720fb101ec1ac5f2eed2f7228fa0)
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_51_1.jpg?sign=1738844949-gxSQ7IsYELCk7L9b12cMrupG6J9vweL3-0-959ecdc24eab143c26bf1095b47c6dbb)
通过这个示例,我们可以总结出state的用法:
· 类组件中有一个名叫state的预定义属性,该属性为对象,对象中记录了关于该组件的所有状态。如上例中,状态count的初始值为1。
· 在需要更改这个状态时,调用组件的setState方法,这个方法继承自Taro.Component。
· 在JSX中,通过this.state获取对应状态值并使用。
注:任何时候都不要通过赋值的形式直接修改state,如上例中,this.state.count=this.state.count+1这种赋值方式是错误的,正确的操作应该是用this.setState更新指定状态。
2.2.5 样式
看了以上与组件相关的例子,对于组件,你是否有种似曾相识的感觉?其实从某种角度来看,组件类似HTML中的标签,这样类比后,关于组件的很多问题都能迎刃而解。组件中样式的使用方法和HTML中一致,也分为两种:内联样式和外部样式。
1.内联样式
组件的内联样式通过style属性指定。与HTML标签的style属性不同的是,组件的style属性接收一个对象:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_52_1.jpg?sign=1738844949-bxbKo2WaepnqPtiThpBiNKCY1H8CEtsi-0-5ae8e0af5bbe8093d025b1e90447a668)
使用内联样式需要注意以下几点:
· 如果不指定尺寸单位,则会默认解析为px,如前面代码中的width:100,会被解析为width:'100px'。
· 属性名改为驼峰式命名,如background-color改为backgroundColor。
2.外部样式
外部样式可以使用CSS、Less、Sass等文件定义样式,然后在对应的模块文件中引入。我们以Less为例:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_52_2.jpg?sign=1738844949-ZUHLMoVqx5fWj5giMSxBgILTJ5lM1zi6-0-82088a40f23ddbfcd6bad5dd0524b9e0)
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_53_1.jpg?sign=1738844949-4BlrQGBzZPZZ9hDkm1N5t00kJJU3OXmA-0-447babbd982bda4bcc7f7c86258d7d3e)
注:我们前面就有提到,因为class为JavaScript关键字,不能出现在JSX中,所以需要使用className替代class。