![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=1738993465-pSmThixrNXkrexalAvKqGyYSd2YsLxOo-0-58acb13e5023b409d88ebd36c75b7043)
在使用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=1738993465-EZzVmjwFROY9HVhfwMnLxFL3zVQDldua-0-e55b3a5e424a055947a67f09ae91f2ee)
当然,还有为了做优化提供的另一种类组件,关于该组件的原理与用法,我们将在实战优化部分进行详细介绍,代码示例如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_47_2.jpg?sign=1738993465-ixg3dqCEaKupizMl0c8u8UVuO8koerqM-0-e7f4d5731dda71def291e2ae4f3a3b56)
2.函数组件
函数组件相较于类组件,定义更便捷,使用更灵活,尤其搭配Hooks使用能够在某些场景下替代类组件。函数组件的定义如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_1.jpg?sign=1738993465-M0v9hv2fwHY43AmIy6Ev5MQ52FyLguee-0-33a0d12f0f83b422ca9b2f35ebb42967)
或者使用箭头函数,写法如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_2.jpg?sign=1738993465-TuPN5z8rMXRppacqiKbRYfULZ0n82UOe-0-5f9022f677c536e656016412cbd636e1)
注:在组件中,无论是否使用Taro这个对象,都应该将@tarojs/taro包引入。无论组件返回值多么简单,都尽量使用@tarojs/components提供的组件包裹,而不应该直接返回数字或字符串等。
定义好组件后,最终需要将最上层组件也就是根组件挂载到DOM节点上:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_48_3.jpg?sign=1738993465-ImJIzTEqJb2DkNMUON1FCraVWsZmNp4J-0-7b899534bfa237e98f039098d4015fda)
为了方便讲解,后续章节将统一使用类组件,当然我们也会在Hooks章节详细介绍函数组件的知识。
2.2.3 props
很多时候,组件中使用的某些数据可能需要外部提供,就像我们使用HTML中的图片标签时,需要设置src属性才能显示对应图片。假如现在定义了一个名叫Timg的类似图片img的组件,组件内部应该怎样获取外部传入的属性数据并使用呢?答案是使用props,代码示例如下:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_49_1.jpg?sign=1738993465-JNljcVgH5ig5xkDiVrcQA1UPPiWU21uY-0-426725b37e7dc55df4fad3be4151b76f)
效果如下图所示。
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_49_2.jpg?sign=1738993465-000uFoLP5GMdudUFtL7u7acP5Vk0YAtJ-0-c5e45ddef2cd3505b0f0a291552cbde9)
通过props,可以将数据传递给组件,组件内部通过this.props获取对应的属性数据,渲染即可。
有时,某些属性数据并不一定是外部必须传入的,因此我们在定义组件时,可以设置默认属性数据,如上例:
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_50_1.jpg?sign=1738993465-mgRiG7SyAlmhg3G2xaZDiG4NWn2BXBEO-0-1c3605ff97aa2c84e8120b0429450269)
若在使用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=1738993465-VToKhAGLtqUUF4Kg8zWjnuMJKu9BnkdL-0-14233ba47cd6e0f45cd4004e4be156b4)
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_51_1.jpg?sign=1738993465-1QEuZLC0Q5fvxjqecNHBm1ofNoQALt9Q-0-2f51dd13b92f8d1fcc2fa907c1ca831a)
通过这个示例,我们可以总结出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=1738993465-C0VxkxrFGDHvDNHQMe834jrMI4eIMnYx-0-6dce9ed83ed7d0fc85440fe4e23899a3)
使用内联样式需要注意以下几点:
· 如果不指定尺寸单位,则会默认解析为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=1738993465-5nmMJp9Nzq5Kdv94NEBoxCIlNKzRyLc3-0-9aa12d1af1781989222919ba3d44c41d)
![](https://epubservercos.yuewen.com/D79B16/20118172101010406/epubprivate/OEBPS/Images/40906_53_1.jpg?sign=1738993465-t1CmbOTrn9Nyr6AyRen2susrEc68YtMV-0-3269e01f99c21e2d3facc5270c762b24)
注:我们前面就有提到,因为class为JavaScript关键字,不能出现在JSX中,所以需要使用className替代class。