![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=1739204726-MZ43Sp2RzQH21GeeqahDiF7thCdbO7lw-0-96e1ab3ca8a1e766b44bfdfa257e8b05)
在使用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=1739204726-sGS1M32npqo0vU3LS4HKhtPKZu4vZrZz-0-bf7c8513c47a0e456051ad07ddf5616b)
当然,还有为了做优化提供的另一种类组件,关于该组件的原理与用法,我们将在实战优化部分进行详细介绍,代码示例如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_47_2.jpg?sign=1739204726-3osCFA8Qw6eNW2CUt5XPfsHOWtno2CZw-0-c90d8045501bc62e554e7e3485e85fdc)
2.函数组件
函数组件相较于类组件,定义更便捷,使用更灵活,尤其搭配Hooks使用能够在某些场景下替代类组件。函数组件的定义如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_1.jpg?sign=1739204726-ty1LPxxq2tpX8KhtGCX0u0dzfGLp82PS-0-68b4b383698fc4ec237467c9fc42de98)
或者使用箭头函数,写法如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_2.jpg?sign=1739204726-zXwgZ21LRl5KUfaHgfCRrq373f9xavBe-0-182b5a4ea1f2efef30e2cf10a723661a)
注:在组件中,无论是否使用Taro这个对象,都应该将@tarojs/taro包引入。无论组件返回值多么简单,都尽量使用@tarojs/components提供的组件包裹,而不应该直接返回数字或字符串等。
定义好组件后,最终需要将最上层组件也就是根组件挂载到DOM节点上:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_3.jpg?sign=1739204726-VovUG4j4hj4z6x7qIUisVvZLDEuvYeHN-0-9afbce778ad2089199596df9aa20fa2e)
为了方便讲解,后续章节将统一使用类组件,当然我们也会在Hooks章节详细介绍函数组件的知识。
2.2.3 props
很多时候,组件中使用的某些数据可能需要外部提供,就像我们使用HTML中的图片标签时,需要设置src属性才能显示对应图片。假如现在定义了一个名叫Timg的类似图片img的组件,组件内部应该怎样获取外部传入的属性数据并使用呢?答案是使用props,代码示例如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_49_1.jpg?sign=1739204726-lC85XF87Yg5bekPIJJp0Z4sCmMc6EbIM-0-aab0b06e5fbdaa9b274696c328af25fa)
效果如下图所示。
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_49_2.jpg?sign=1739204726-46dOBYZEHs35AdYjKp2clq3MDXTkbjO4-0-6821184961c59e789d8db4f2ed52b58e)
通过props,可以将数据传递给组件,组件内部通过this.props获取对应的属性数据,渲染即可。
有时,某些属性数据并不一定是外部必须传入的,因此我们在定义组件时,可以设置默认属性数据,如上例:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_50_1.jpg?sign=1739204726-RBG0W60iKy6tKdGtDGRhfeKOxZHrmcWE-0-22fdf15cda1f926efa3dedebb43c8a41)
若在使用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=1739204726-HRdT1xwdTH3BEvUyz8IWcadYjRsi4JGo-0-09557dda34bc428dadb2feeb0e6aaabc)
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_51_1.jpg?sign=1739204726-eAZi6V5aSsEV7WVwmOY4os7KpU7lAA90-0-f5cff879a510d3d6a3e262c8a3cf0d7d)
通过这个示例,我们可以总结出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=1739204726-TCxfrQQ7HfjgcnrPBD9x4ueKUuDHqsTz-0-85ac669375b4fd20c372fd4b71c2d824)
使用内联样式需要注意以下几点:
· 如果不指定尺寸单位,则会默认解析为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=1739204726-begh1UMdKkFkjaDk93t3lQi1jgqzptYg-0-d8670ea75417d717f90f4605d9326180)
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_53_1.jpg?sign=1739204726-WsWkbx9OgMozlmJmnAQhZb09Ozex4daF-0-4ff12bec737130e76f32a7bce9dc9145)
注:我们前面就有提到,因为class为JavaScript关键字,不能出现在JSX中,所以需要使用className替代class。