微信小程序demo咯(求微信小程序开发)

网友投稿 808 2022-10-08

微信小程序demo咯(求微信小程序开发)

微信小程序demo咯(求微信小程序开发)

前言

我们之前对小程序做了基本学习:

1. 微信小程序开发07-列表页面怎么做2. 微信小程序开发06-一个业务页面的完成3. 微信小程序开发05-日历组件的实现4. 微信小程序开发04-打造自己的UI库5. 微信小程序开发03-这是一个组件6. 微信小程序开发02-小程序基本介绍7. 微信小程序开发01-小程序的执行流程是怎么样的?

阅读本文之前,如果大家想对小程序有更深入的了解,或者一些细节的了解可以先阅读上述文章,本文后面点需要对着代码调试阅读

对应的github地址是:https://github.com/yexiaochai/wxdemo

首先我们来一言以蔽之,什么是微信小程序?PS:这个问题问得好像有些扯:)

小程序是一个不需要-安装就可使用的应用,它实现了应用触手可及的梦想,用户扫一扫或者搜一下即可打开应用。也体现了用完即走的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载。从字面上看小程序具有类似Web应用的热部署能力,在功能上又接近于原生APP。

所以说,其实微信小程序是一套超级Hybrid的解决方案,现在看来,小程序应该是应用场景最广,也最为复杂的解决方案了。

很多公司都会有自己的Hybrid平台,我这里了解到比较不错的是携程的Hybrid平台、阿里的Weex、百度的糯米,但是从应用场景来说都没有微信来得丰富,这里根本的区别是:

微信小程序是给各个公司开发者接入的,其他公司平台多是给自己业务团队使用,这一根本区别,就造就了我们看到的很多小程序不一样的特性:

① 小程序定义了自己的标签语言WXML

② 小程序定义了自己的样式语言WXSS

③ 小程序提供了一套前端框架包括对应Native API

④ 禁用浏览器Dom API(这个区别,会影响我们的代码方式)

只要了解到这些区别就会知道为什么小程序会这么设计:

因为小程序是给各个公司的开发做的,其他公司的Hybrid方案是给公司业务团队用的,一般拥有Hybrid平台的公司实力都不错但是开发小程序的公司实力良莠不齐,所以小程序要做绝对的限制,最大程度的保证框架层(小程序团队)对程序的控制因为毕竟程序运行在微信这种体量的APP中

之前我也有一个疑惑为什么微信小程序会设计自己的标签语言,也在知乎看到各种各样的回答,但是如果出于设计层面以及应用层面考虑的话:这样会有更好的控制,而且我后面发现微信小程序事实上依旧使用的是webview做渲染(这个与我之前认为微信是NativeUI是向左的),但是如果我们使用的微信限制下面的标签,这个是有限的标签,后期想要换成NativeUI会变得更加轻易:

另一方面,经过之前的学习,我这边明确可以得出一个感受:

① 小程序的页面核心是标签,标签是不可控制的(我暂时没用到js操作元素的方法),只能按照微信给的玩法玩,标签控制显示是我们的view

② 标签的展示只与data有关联,和js是隔离的,没有办法在标签中调用js的方法

③ 而我们的js的唯一工作便是根据业务改变data,重新引发页面渲染,以后别想操作DOM,别想操作Window对象了,改变开发方式,改变开发方式,改变开发方式!

1 this.setData({'wxml': `2 3 动态插入的节点4 5 `});

然后可以看到这个是一个MVC模型

每个页面的目录是这个样子的:

1 project 2 ├── pages 3 | ├── index 4 | | ├── index.json index 页面配置 5 | | ├── index.js index 页面逻辑 6 | | ├── index.wxml index 页面结构 7 | | └── index.wxss index 页面样式表 8 | └── log 9 | ├── log.json log 页面配置10 | ├── log.wxml log 页面逻辑11 | ├── log.js log 页面结构12 | └── log.wxss log 页面样式表13 ├── app.js 小程序逻辑14 ├── app.json 小程序公共设置15 └── app.wxss 小程序公共样式表

每个组件的目录也大概是这个样子的,大同小异,但是入口是Page层。

小程序打包后的结构(这里就真的不懂了,引用:小程序底层框架实现原理解析):

所有的小程序基本都最后都被打成上面的结构

1、WAService.js  框架JS库,提供逻辑层基础的API能力

2、WAWebview.js 框架JS库,提供视图层基础的API能力

3、WAConsole.js 框架JS库,控制台

4、app-config.js 小程序完整的配置,包含我们通过app.json里的所有配置,综合了默认配置型

5、app-service.js 我们自己的JS代码,全部打包到这个文件

6、page-frame.html 小程序视图的模板文件,所有的页面都使用此加载渲染,且所有的WXML都拆解为JS实现打包到这里

7、pages 所有的页面,这个不是我们之前的wxml文件了,主要是处理WXSS转换,使用js插入到header区域

从设计的角度上说,小程序采用的组件化开发的方案,除了页面级别的标签,后面全部是组件,而组件中的标签view、data、js的关系应该是与page是一致的,这个也是我们平时建议的开发方式,将一根页面拆分成一个个小的业务组件或者UI组件:

从我写业务代码过程中,觉得整体来说还是比较顺畅的,小程序是有自己一套完整的前端框架的,并且释放给业务代码的主要就是page,而page只能使用标签和组件,所以说框架的对业务的控制力度很好。

最后我们从工程角度来看微信小程序的架构就更加完美了,小程序从三个方面考虑了业务者的感受:

① 开发工具+调试工具

② 开发基本模型(开发基本标准WXML、WXSS、JS、JSON)

③ 完善的构建(对业务方透明)

④ 自动化上传离线包(对业务费透明离线包逻辑)

⑤ 监控统计逻辑

所以,微信小程序从架构上和使用场景来说是很令人惊艳的,至少惊艳了我......所以我们接下来在开发层面对他进行更加深入的剖析,我们这边最近一直在做基础服务,这一切都是为了完善技术体系,这里对于前端来说便是我们需要做一个Hybrid体系,如果做App,React Native也是不错的选择,但是一定要有完善的分层:

① 底层框架解决开发效率,将复杂的部分做成一个黑匣子,给页面开发展示的只是固定的三板斧,固定的模式下开发即可

② 工程部门为业务开发者封装最小化开发环境,最优为浏览器,确实不行便为其提供一个类似浏览器的调试环境

如此一来,业务便能快速迭代,因为业务开发者写的代码大同小异,所以底层框架配合工程团队(一般是同一个团队),便可以在底层做掉很多效率性能问题。

稍微大点的公司,稍微宽裕的团队,还会同步做很多后续的性能监控、错误日志工作,如此形成一套文档->开发->调试->构建->发布->监控、分析 为一套完善的技术体系

如果形成了这么一套体系,那么后续就算是内部框架更改、技术革新,也是在这个体系上改造,这块微信小程序是做的非常好的。但很可惜,很多其他公司团队只会在这个路径上做一部分,后面由于种种原因不在深入,有可能是感觉没价值,而最恐怖的行为是,自己的体系没形成就贸然的换基础框架,戒之慎之啊!好了闲话少说,我们继续接下来的学习。

我对小程序的理解有限,因为没有源码只能靠惊艳猜测,如果文中有误,请各位多多提点

文章更多面对初中级选手,如果对各位有用,麻烦点赞哟

微信小程序的执行流程

微信小程序为了对业务方有更强的控制,App层做的工作很有限,我后面写demo的时候根本没有用到app.js,所以我这里认为app.js只是完成了一个路由以及初始化相关的工作,这个是我们看得到的,我们看不到的是底层框架会根据app.json的配置将所有页面js都准备好。

我这里要表达的是,我们这里配置了我们所有的路由:

"pages":[ "pages/index/index", "pages/list/list", "pages/logs/logs"],

微信小程序一旦载入,会开3个webview,装载3个页面的逻辑,完成基本的实例化工作,只显示首页!这个是小程序为了优化页面打开速度所做的工作,也势必会浪费一些资源,所以到底是全部打开或者预加载几个,详细底层Native会根据实际情况动态变化,我们也可以看到,从业务层面来说,要了解小程序的执行流程,其实只要能了解Page的流程就好了,关于Page生命周期,除了释放出来的API:onLoad -> onShow -> onReady -> onHide等,官方还出了一张图进行说明:

Native层在载入小程序时候,起了两个线程一个的view Thread一个是AppService Thread,我这边理解下来应该就是程序逻辑执行与页面渲染分离,小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。

因为之前我认为页面是使用NativeUI做渲染跟Webview没撒关系,便觉得这个图有问题,但是后面实际代码看到了熟悉的shadow-dom以及Android可以看到哪部分是Web的,其实小程序主体还是使用的浏览器渲染的方式,还是webview装载HTML和CSS的逻辑,最后我发现这张图是没有问题的,有问题的是我的理解,哈哈,这里我们重新解析这张图:

WXML先会被编译成JS文件,引入数据后在WebView中渲染,这里可以认为微信载入小程序时同时初始化了两个线程,分别执行彼此逻辑:

① WXML&CSS编译形成的JS View实例化结束,准备结束时向业务线程发送通知

② 业务线程中的JS Page部分同步完成实例化结束,这个时候接收到View线程部分的等待数据通知,将初始化data数据发送给View

③ View线程接到数据,开始渲染页面,渲染结束执行通知Page触发onReady事件

这里翻开源码,可以看到,应该是全局控制器完成的Page实例化,完成后便会执行onLoad事件,但是在执行前会往页面发通知:

1 __appServiceSDK__.invokeWebviewMethod({2 name: "appDataChange",3 args: o({}, e, {4 complete: n5 }),6 webviewIds: [t]7 })

真实的逻辑是这样的,全局控制器会完成页面实例化,这个是根据app.json中来的,全部完成实例化存储起来然后选择第一个page实例执行一些逻辑,然后通知view线程,即将执行onLoad事件,因为view线程和业务线程是两个线程,所以不会造成阻塞,view线程根据初始数据完成渲染,而业务线程继续后续逻辑,执行onLoad,如果onLoad中有setData,那么会进入队列继续通知view线程更新。

所以我个人感觉微信官网那张图不太清晰,我这里重新画了一个图:

再引用一张其他地方的图:

模拟实现

都这个时候了,不来个简单的小程序框架实现好像有点不对,我们做小程序实现的主要原因是想做到一端代码三端运行:web、小程序、Hybrid甚至Servce端

我们这里没有可能实现太复杂的功能,这里想的是就实现一个基本的页面展示带一个最基本的标签即可,只做Page一块的简单实现,让大家能了解到小程序可能的实现,以及如何将小程序直接转为H5的可能走法

1 2 3 4 {{pageData}}5

1 Page({2 data: {3 pageData: '页面数据'4 },5 onLoad: function () {6 console.log('onLoad')7 },8 })

1 2 3 {{innerText}}4 5

1 Component({ 2 properties: { 3 // 这里定义了innerText属性,属性值可以在组件使用时指定 4 innerText: { 5 type: String, 6 value: 'default value', 7 } 8 }, 9 data: {10 // 这里是一些组件内部数据11 someData: {}12 },13 methods: {14 // 这里是一个自定义方法15 customMethod: function () { }16 }17 })

我们直接将小程序这些代码拷贝一份到我们的目录:

我们需要做的就是让这段代码运行起来,而这里的目录是我们最终看见的目录,真实运行的时候可能不是这个样,运行之前项目会通过我们的工程构建,变成可以直接运行的代码,而我这里思考的可以运行的代码事实上是一个模块,所以我们这里从最终结果反推、分拆到开发结构目录,我们首先将所有代码放到index.html,可能是这样的:

这段代码,非常简单:

① 设置了一段模板,甚至,我们这里根本不关系其格式化状态,直接写成一行方便处理

this.template = '{{pageShow}}{{pageShow}}{{pageData}}';

② 然后我们将这段模板转为node节点(这里可以不用zepto,但是模拟实现怎么简单怎么来吧),然后遍历处理所有节点,我们就可以处理我们的数据了,最终形成了这个html:

1

ffsd
pageshow
pageData

③ 与此同时,我们存储了一个对象,这个对象包含所有与之相关的节点:

这个对象是所有setData会影响到node的一个映射表,后面调用setData的时候,便可以直接操作对应的数据了,介于此我们简单的模拟便先到此结束,这里结束的比较仓促有一些原因:

① 这段代码可以是最终打包构建形成的代码,但是我这里的完成度只有百分之一,后续需要大量的构建相关介入

② 这篇文章目的还是接受开发基础,而本章模拟实现太过复杂,如果篇幅大了会主旨不清

③ 这个是最重要的点,我一时也写不出来啊!!!,所以各位等下个长篇,小程序前端框架模拟实现吧

所以我们继续下章吧......

小程序中的Page的封装

小程序的Page类是这样写的:

1 Page({2 data: {3 pageData: '页面数据'4 },5 onLoad: function () {6 console.log('onLoad')7 },8 })

传入的是一个对象,显然,我们为了更好的拆分页面逻辑,前面我们介绍了小程序是采用组件化开发的方式,这里的说法可以更进一步,小程序是采用标签化的方式开发,而标签对应的控制器js只会改变数据影响标签显示,所以某种程度小程序开发的特点是:先标签后js,我们构建一个页面,首先就应该思考这个页面有哪些标签,哪些标签是公共的标签,然后设计好标签再做实现。

比如这里的业务日历模块仅仅是index的一部分(其他页面也可能用得到),所以我们实现了一个页面共用的记录,便与我们更好的分拆页面:

其中页面会用到的一块核心就是:

1 //pageData为页面级别数据,mod为模块数据,要求一定不能重复 2 initPage(pageData, mod) { 3 //debugger; 4 let _pageData = {}; 5 let key, value, k, v; 6 7 //为页面动态添加操作组件的方法 8 Object.assign(_pageData, this.getPageFuncs(), pageData); 910 //生成真实的页面数据11 _pageData.data = {};12 Object.assign(_pageData.data, this.getPageData(), pageData.data || {});1314 for( key in mod) {15 value = mod[key];16 for(k in value) {17 v = value[k];18 if(k === 'data') {19 Object.assign(_pageData.data, v);20 } else {21 _pageData[k] = v;22 }23 }24 }2526 console.log(_pageData);27 return _pageData;28 }

调用方式是:

1 Page(_page.initPage({ 2 data: { 3 sss: 'sss' 4 }, 5 // methods: uiUtil.getPageMethods(), 6 methods: { 7 }, 8 goList: function () { 9 if(!this.data.cityStartId) {10 this.showToast('请选择出发城市');11 return;12 }13 if(!this.data.cityArriveId) {14 this.showToast('请选择到达城市');15 return;16 }1718 wx.navigateTo({19 })2021 }22 }, {23 modCalendar: modCalendar,24 modCity: modCity25 }))

可以看到,其他组件,如这里的日历模块只是一个对象而已:

1 module.exports = { 2 showCalendar: function () { 3 this.setData({ 4 isCalendarShow: '' 5 }); 6 }, 7 hideCalendar: function () { 8 this.setData({ 9 isCalendarShow: 'none'10 });11 },12 preMonth: function () {1314 this.setData({15 calendarDisplayTime: util.dateUtil.preMonth(this.data.calendarDisplayTime).toString()16 });17 },18 nextMonth: function () {19 this.setData({20 calendarDisplayTime: util.dateUtil.nextMonth(this.data.calendarDisplayTime).toString()21 });22 },23 onCalendarDayTap: function (e) {24 let data = e.detail;25 var date = new Date(data.year, data.month, data.day);26 console.log(date)2728 //留下一个钩子函数29 if(this.calendarHook) this.calendarHook(date);30 this.setData({31 isCalendarShow: 'none',32 calendarSelectedDate: date.toString(),33 calendarSelectedDateStr: util.dateUtil.format(date, 'Y年M月D日')34 });35 },36 onContainerHide: function () {37 this.hideCalendar();38 },3940 data: {41 isCalendarShow: 'none',42 calendarDisplayMonthNum: 1,43 calendarDisplayTime: selectedDate,44 calendarSelectedDate: selectedDate,45 calendarSelectedDateStr: util.dateUtil.format(new Date(selectedDate), 'Y年M月D日')46 }47 }

但是在代码层面却帮我们做到了更好的封装,这个基类里面还包括我们自定义的常用组件,loading、toast等等:

page是最值得封装的部分,这里是基本page的封装,事实上,列表页是常用的一种业务页面,虽然各种列表页的筛选条件不一样,但是主体功能无非都是:

① 列表渲染② 滚动加载③ 条件筛选、重新渲染所以说我们其实可以将其做成一个页面基类,跟abstract-page一个意思,这里留待我们下次来处理吧

小程序中的组件

请大家对着github中的代码调试阅读这里

前面已经说了,小程序的开发重点是一个个的标签的实现,我们这里将业务组件设置成了一个个mod,UI组件设置成了真正的标签,比如我们页面会有很多非业务类的UI组件:

① alert类弹出层

② loading类弹出层

③ 日历组件

④ toast&message类提示弹出组件

⑤ 容器类组件

⑥ ......

这些都可以我们自己去实现,但是微信其实提供给我们了系统级别的组件:

这里要不要用就看实际业务需求了,一般来说还是建议用的,我们这里为了帮助各位更好的了解小程序组件,特别实现了一个较为复杂,而小程序又没有提供的组件日历组件,首先我们这里先建立一个日历组件目录:

其次我们这里先做最简单实现:

这个是非常简陋的日历雏形,在代码过程中有以下几点比较痛苦:

① WXML与js间应该只有数据传递,根本不能传递方法,应该是两个webview的通信,而日历组件这里在WXML层由不得不写一点逻辑

② 本来在WXML中写逻辑已经非常费劲了,而我们引入的WXS,使用与HTML中的js片段也有很大的不同,主要体现在日期操作

这些问题,一度让代码变得复杂,而可以看到一个简单的组件,还没有复杂功能,涉及到的文件都太多了,这里页面调用层引入标签后:

日历的基本页面就出来了:

这个日历组件应该是在小程序中写的最复杂的组件了,尤其是很多逻辑判断的代码都放在了WXML里面,根据之前的了解,小程序渲染在一个webview中,js逻辑在一个webview中,他这样做的目的可能是想让性能更好,这种UI组件使用的方式一般是直接使用,但是如果涉及到了页面业务,便需要独立出一个mod小模块去操作对应组件的数据,如图我们这里的日历组件一般