【DoKit&北大专题】-读小程序源代码(二)

我心飞翔 分类:javascript

专题背景

近几年随着开源在国内的蓬勃发展,一些高校也开始探索让开源走进校园,让同学们在学生时期就感受到开源的魅力,这也是高校和国内的头部互联网企业共同尝试的全新教学模式。本专题会记录这段时间内学生们的学习成果。

更多专题背景参考:【DoKit&北大专题】缘起

系列文章

【DoKit&北大专题】缘起

【DoKit&北大专题】-读小程序源代码(一)

【DoKit&北大专题】-读小程序源代码(二)

原文

第一篇文章里我们将Dokit的模块引入到了自己的小程序项目中,那么接下来就是正式的阅读Dokit的源代码了。

一、微信小程序自定义组件概述

DoKit模块是由多个自定义组件构成的,因此在阅读源码之前,我们需要先简单的了解一下微信小程序自定义组件的基本知识。
微信小程序可以说是由多个page页面构成的,而每个page页面是由多个组件构成的,其中包括微信的原生组件,也包括了用户的自定义组件。

小程序构成.png

自定义组件的构成与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文件夹

项目目录.png

我们先来了解一下该项目的目录分布,有助于我们对项目有个整体的把控:

  • 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.jsdata属性,可以看到该变量的值,初始化为curCom: 'dokit'
因此当组件第一次初始化时,会渲染<block wx:else>内的组件,也就是<cover-image>,通过修改data中的curCom为debug/appinformation可以测试一下会数据绑定的效果。可以看到根据curCom字符串的不同,index会展示不同的功能窗口。
简单的展示图如下:

index结构布局.png

我们通过修改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字符串。
看完了子组件触发事件的函数,我们可以回来看父组件是如何响应处理这个事件的了:

  1. 响应函数的参数为e,event,是包括了子组件传递的信息的对象。
  2. 在函数内定义了个变量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的值。在此简要的介绍一下datasetcurrentTarget

  1. currentTarget是事件绑定的当前组件,有两个属性:id和dataset。
  2. dataset是当前组件上由data-开头的自定义属性组成的集合,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。
  3. 在 WXML 中,dataset自定义数据以 data- 开头data-elementType ,最终会在js中呈现为 event.currentTarget.dataset.elementtype

更详细的信息请参考小程序官方文档。

至此,我们已经明白了index组件中数据层的curCom是通过事件的方式来响应各种事件,如toggletap,进行修改的。但还有一个问题: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…

来源:掘金

回复

我来回复
  • 暂无回复内容