【DoKit&北大专题】-读小程序源代码(二)
专题背景
近几年随着开源在国内的蓬勃发展,一些高校也开始探索让开源走进校园,让同学们在学生时期就感受到开源的魅力,这也是高校和国内的头部互联网企业共同尝试的全新教学模式。本专题会记录这段时间内学生们的学习成果。
更多专题背景参考:【DoKit&北大专题】缘起
系列文章
【DoKit&北大专题】缘起
【DoKit&北大专题】-读小程序源代码(一)
【DoKit&北大专题】-读小程序源代码(二)
原文
第一篇文章里我们将Dokit的模块引入到了自己的小程序项目中,那么接下来就是正式的阅读Dokit的源代码了。
一、微信小程序自定义组件概述
DoKit模块是由多个自定义组件构成的,因此在阅读源码之前,我们需要先简单的了解一下微信小程序自定义组件的基本知识。
微信小程序可以说是由多个page页面构成的,而每个page页面是由多个组件构成的,其中包括微信的原生组件,也包括了用户的自定义组件。
自定义组件的构成与Page页面的构成非常类似,都具有4个文件:js、json、wxml、wxss。
- js文件:页面/自定义组件的逻辑文件
- wxml文件:页面/自定义组件的结构文件,可以类比
HTML
,但没有HTML的div、p等标签,取而代之的是微信小程序的各种组件,如view、button等 - wxss文件:页面/自定义组件的样式文件,可以类比
CSS
,但自定义组件内的wxss文件不能使用ID选择器、属性选择器和标签名选择器 - json文件:页面/自定义组件的配置文件,对于页面,可以在这里设置是否使用自定义组件;对于自定义组件,设置是否使用自定义组件、更重要的是需要在这里声明该模块为自定义组件
引用自定义组件
在想要引用组件的page界面或自定义组件的json文件
里声明:(自定义组件里可以再引用其他的自定义组件)
{
"usingComponents": {
"dokit": "../../dist/index/index"
}
}
声明模块为自定义组件
在自定义组件的json文件里声明:
{
"component": true,
"usingComponents": {
"back": "../../components/back/back"
}
}
二、项目组织结构概述
如图所示,Dokit的项目主体在dist文件夹
我们先来了解一下该项目的目录分布,有助于我们对项目有个整体的把控:
-
assets
——里面有img文件夹,存储icon等图片资源 -
components
——Dokit最核心的部分,包括了八个自定义组件- apimock —— 数据模拟功能的自定义组件
- appinformation —— App信息功能的自定义组件
- back —— 用来返回的自定义组件,该组件并不是Dokit的功能组件,但它被除了index之外所有的自定义组件调用,通过该组件返回到小程序page界面
- debug —— 起到主菜单功能的自定义组件,罗列了Dokit的各种功能
- h5door —— h5任意门功能的自定义组件
- httpinjector ——请求注射功能的自定义组件
- positionsimulation —— 位置模拟功能的自定义组件
- storage —— 存储管理功能的自定义组件
-
index
—— Dokit入口的自定义组件,将Dokit引入自己项目中时,就是在目标page页面中引入这个component -
logs
—— 与新建微信小程序时的样例程序中的logspage页面相同,查看程序启动日志,目前不知道Dokit中这个部分有什么用 -
utils
—— 里面有两个文件,imgbase64.js是用来将Dokit各种图标转换成base64格式的;util.js内存储了一些常用接口函数,包括时间输出、跳转页面、深拷贝等
在这里简要解释一下什么是base64格式的图片:
base64格式是一种将二进制转换成字符的编码格式,而图片的base64编码就是将一副图片的数据编码成字符串;
这样前端(如HTML或WXML)可以直接利用编码后的字符串直接转换成图片。针对各种体积小的图片,使用base64格式可以减少向服务器下载图片的请求;
但要注意的是通过base64编码后字符串的体积大小往往会比图片本身要大,因此仅适用于体积小的,不经常更改的图片。
更详细的base64编码信息可以参考这篇文章
在简单的了解了项目的目录分布后,我们从Dokit的入口组件index开始,逐步阅读源码。
在阅读源代码之前先确定一下我们的阅读顺序,自定义组件有四个文件,我们先阅读.json
、.wxml
和.wxss
文件,将.js
文件穿插其中。
三、Dokit的入口组件
还记得我们最初给自己的小程序项目中引入Dokit模块的时候,在相应页面的page.json
文件中添加了以下语句:
"usingComponents": {
"dokit": "../../dist/index/index"
}
我们当时引入的自定义组件就是dist文件夹下的index组件,这个组件就是Dokit的入口组件。先来看看这个组件的json
文件,确定它引用了什么组件:
{
"component": true,
"navigationBarTitleText": "",
"usingComponents": {
"debug": "../components/debug/debug",
"appinformation": "../components/appinformation/appinformation",
"positionsimulation": "../components/positionsimulation/positionsimulation",
"storage": "../components/storage/storage",
"h5door": "../components/h5door/h5door",
"httpinjector": "../components/httpinjector/httpinjector",
"apimock": "../components/apimock/apimock"
}
}
可以看到这个组件引用了components文件夹中的所有Dokit的功能组件,接着看.wxml
文件确定它的结构/模版,是如何调用到了这么多组件的:
<block wx:if="{{ curCom!= 'dokit' }}">
<debug wx:if="{{ curCom === 'debug' }}" bindtoggle="tooggleComponent"></debug>
<appinformation wx:if="{{ curCom === 'appinformation' }}" bindtoggle="tooggleComponent"></appinformation>
<positionsimulation wx:if="{{ curCom === 'positionsimulation' }}" bindtoggle="tooggleComponent"></positionsimulation>
<storage wx:if="{{ curCom === 'storage' }}" bindtoggle="tooggleComponent"></storage>
<h5door wx:if="{{ curCom === 'h5door' }}" bindtoggle="tooggleComponent"></h5door>
<httpinjector wx:if="{{ curCom === 'httpinjector' }}" bindtoggle="tooggleComponent"></httpinjector>
<apimock wx:if="{{ curCom === 'apimock' }}" bindtoggle="tooggleComponent" projectId="{{ projectId }}"></apimock>
</block>
<block wx:else>
<cover-image
bindtap="tooggleComponent"
data-type="debug"
class="dokit-entrance"
src="//pt-starimg.didistatic.com/static/starimg/img/W8OeOO6Pue1561556055823.png"
></cover-image>
</block>
需要注意的部分有以下几点:
wx:if
条件渲染bindtoggle
组件间的通信{{curCom}}
数据绑定
我们先来看条件渲染,条件渲染是只有在wx:if
后面的条件成立时才会视图层才会渲染当前的组件,反之则是渲染wx:else
部分的组件(注意条件渲染是惰性的,默认初始为false,不会进行渲染)。
接着我们看到wx:if
中的条件是"{{ curCom!= 'dokit' }}"
,这是微信小程序WXML语法中的.
数据绑定:
被两个大括号括住的语句内可以只放一个变量名或者简单的运算,内部变量的具体的值是动态的,实际来自与该WXML文件同名的.js
文件中的data中同名属性的值。
查找index.js
中data
属性,可以看到该变量的值,初始化为curCom: 'dokit'
因此当组件第一次初始化时,会渲染<block wx:else>
内的组件,也就是<cover-image>
,通过修改data中的curCom
为debug/appinformation可以测试一下会数据绑定的效果。可以看到根据curCom
字符串的不同,index会展示不同的功能窗口。
简单的展示图如下:
我们通过修改curCom
的值可以改变index条件渲染的组件是什么,而组件自己工作的时候肯定要通过用户的点击动作来改变curCom
的值,这个过程实际上就是组件间发生了通信。
bindXXX=toggleComponent组件间的通信
查看之前的index.wxml
文件,我们看到了每个组件都有一个bindXXX的属性,且这个属性的值都是toggleComponent
,这其实就是该组件改变curCom值的方式:事件。
组件间的通信,除了父组件向子组件通过数据绑定传数据外,还有一种称为事件监听的方式从子组件向父组件传递数据:
- 事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件;事件是视图层到逻辑层的通讯方式, 可以将用户的行为反馈到逻辑层进行处理。
- 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数,事件对象可以携带额外信息,如 id, dataset, touches。
- 监听自定义组件事件的方法与监听基础组件事件的方法完全一致。
使用事件来实现组件通信是通过以下的方式:
1.父组件进行事件的监听
2. 子组件进行事件的触发
先来看父组件对子组件的事件是如何监听的:
在index组件中,为子组件添加bind
属性,具体语法为<组件名 bind事件名="事件处理函数"></组件名>
<debug bindtoggle="toggleComponent"></debug>
然后在.js
逻辑层添加事件处理函数,当父组件接收到子组件传递来的事件后应该执行这个方法来处理。
methods: {
toggleComponent(e) {
const componentType = e.currentTarget.dataset.type || e.detail.componentType
this.setData({
curCom: componentType
})
}
}
我们先将这个处理函数的阅读放在后面,先看子组件的触发事件方式:
自定义组件触发事件时,需要使用 triggerEvent
方法,指定事件名、detail对象和事件选项。我们先随便找一个功能组件,看看里面指定事件名为toggle
的方法,以debug
组件为例:
onGoBack () {
this.triggerEvent('toggle', { componentType: 'dokit'})
}
在这个方法中,第一个参数是触发了名为toggle
的事件,第二个参数是构建了一个detail
对象:属性为componentType
,值为dokit
字符串。
看完了子组件触发事件的函数,我们可以回来看父组件是如何响应处理这个事件的了:
- 响应函数的参数为e,event,是包括了子组件传递的信息的对象。
- 在函数内定义了个变量componentType,赋值为从e中获取到的detail对象的componentType值,并调用了组件的setData方法,将该变量的值传递给视图层,且修改index组件中data相应的属性。
可以注意,响应函数中不止是componentType = e.detail.componentType
而是e.currentTarget.dataset.type || e.detail.componentType
,而||运算符前一个值的出处是cover-image
<cover-image
bindtap="tooggleComponent"
data-type="debug"
class="dokit-entrance"
src="//pt-starimg.didistatic.com/static/starimg/img/W8OeOO6Pue1561556055823.png"
></cover-image>
在这个组件里监听的事件不是toggle
,而是tap
,这是由用户点击来触发的事件,此时传递的信息不是detail
对象,而是dataset
中属性为type
的值。在此简要的介绍一下dataset
和currentTarget
:
- currentTarget是事件绑定的当前组件,有两个属性:id和dataset。
- dataset是当前组件上由data-开头的自定义属性组成的集合,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。
- 在 WXML 中,dataset自定义数据以 data- 开头data-elementType ,最终会在js中呈现为 event.currentTarget.dataset.elementtype
更详细的信息请参考小程序官方文档。
至此,我们已经明白了index组件中数据层的curCom
是通过事件的方式来响应各种事件,如toggle
或tap
,进行修改的。但还有一个问题:onGoBack
函数虽然触发了toggle
事件,但这个函数又是在哪里,什么时候调用的呢?
进一步观察组件通信过程
我们现在已经知道了,逻辑层的方法是在视图层.wxml
文件中通过事件监听/响应的方式调用的。那么我们就搜索一下,onGoBack
函数是在哪被调用的。
很快,我们就能在各个组件里都找到这样一个语句,依然以debug组件为例:
<back bindreturn="onGoBack"></back>
与上一部分bindtoggle="toggleComponent"
的工作方式是相同的:debug
组件响应子组件back
的名为return
的事件,并通过onGoBack
函数来处理。
再以此类推,寻找return事件的通信过程:
back.js与back.wxml相关代码如下:
methods: {
onbackDokitEntry () {
this.triggerEvent('return')
}
}
<cover-image
bindtap="onbackDokitEntry"
data-type="debug"
class="dokit-back"
src="//pt-starimg.didistatic.com/static/starimg/img/W8OeOO6Pue1561556055823.png"
style="top: {{ top }}"
></cover-image>
可以看到back组件触发return事件的方式与index组件内cover-image
相似,都是bindtap+data-type的方式,不再赘述。
最后以一张图来梳理一下这个通信过程:
总结
到目前为止,我们熟悉了Dokit小程序端的整体目录结构,并阅读了入口组件——index组件的源代码。我们了解了自定义组件是如何通信的,子组件如何利用事件系统来向父组件传值,父组件是如何响应子组件的。
本次阅读的代码量并不多,但我们了解了小程序的一些特色功能如条件渲染、数据绑定、事件系统,为我们继续深入阅读源代码打下了简单的小程序基础。
作者信息
作者:亦庄亦谐
原文链接:juejin.cn/post/694807…
来源:掘金