forked from godbasin/godbasin.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
459 lines (273 loc) · 243 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Here. There.</title>
<subtitle>Love ice cream. Love sunshine. Love life. Love the world. Love myself. Love you.</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://godbasin.github.io/"/>
<updated>2019-04-21T06:15:14.652Z</updated>
<id>https://godbasin.github.io/</id>
<author>
<name>被删</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>小程序开发月刊第四期(20190415)</title>
<link href="https://godbasin.github.io/2019/04/15/wxapp-latest-20190415/"/>
<id>https://godbasin.github.io/2019/04/15/wxapp-latest-20190415/</id>
<published>2019-04-15T15:54:30.000Z</published>
<updated>2019-04-21T06:15:14.652Z</updated>
<content type="html"><![CDATA[<p>每月都有小程序期刊,这个月新功能特别多~~<br><a id="more"></a></p><h1 id="小程序-latest"><a href="#小程序-latest" class="headerlink" title="小程序 latest"></a>小程序 latest</h1><h2 id="小程序能力"><a href="#小程序能力" class="headerlink" title="小程序能力"></a>小程序能力</h2><h3 id="「小程序评测」功能上线"><a href="#「小程序评测」功能上线" class="headerlink" title="「小程序评测」功能上线"></a>「小程序评测」功能上线</h3><p>小程序评测能力已上线beta版本,登录管理后台-【功能】-【小程序评测】可以查看。<br>评测达标的小程序,可获得「2小时极速审核」和「内测功能体验」奖励。</p><ul><li>常见问题查看:<a href="http://kf.qq.com/faq/190108BJnmUN190108RrEnqE.html" target="_blank" rel="external">http://kf.qq.com/faq/190108BJnmUN190108RrEnqE.html</a></li></ul><h3 id="小程序管理后台新增页面收录设置的开关"><a href="#小程序管理后台新增页面收录设置的开关" class="headerlink" title="小程序管理后台新增页面收录设置的开关"></a>小程序管理后台新增页面收录设置的开关</h3><p>小程序管理后台新增页面收录设置的开关,开发者可根据业务需要进行设置:</p><ol><li>小程序管理后台-【设置】-【基本设置】-【页面收录设置】,可对你的小程序帐号进行收录的开启和关闭的设置。</li><li>更新 微信开发者工具 ,可对 sitemap 进行特定页面的配置,可参考<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html" target="_blank" rel="external">小程序开发文档</a>。</li><li>此设置默认开启。若小程序中存在不适合展示的内容,或开发者不希望使用微信展示其小程序,建议开发者自行关闭该设置,<a href="https://mp.weixin.qq.com/wxopen/readtemplate?t=config/collection_agreement_tmpl" target="_blank" rel="external">详情查看</a>。</li></ol><p>PS: sitemap 功能仿原生目前是不会被收录的。开关的逻辑他们会特殊处理下,默认关闭(目前是默认开启)。同时:</p><ol><li>如果设置了不允许被搜索,但开启了允许被收录:不会进入搜索。</li><li>如果关闭了允许被收录,sitemap 设置会无效。</li></ol><h3 id="小程序关联公众号策略调整"><a href="#小程序关联公众号策略调整" class="headerlink" title="小程序关联公众号策略调整"></a>小程序关联公众号策略调整</h3><p>为了降低公众号与小程序间的合作门槛,我们将调整小程序关联公众号策略如下:</p><ol><li>公众号关联小程序将无需小程序管理员确认。</li><li>取消“小程序最多关联500个公众号”的限制。</li><li>若希望小程序在被关联时保留管理员确认环节,可前往“小程序管理后台-设置-基本设置-关联公众号设置”修改设置项。</li><li>公众号文章中可直接使用小程序素材,无需关联小程序。<br>开发者可在小程序管理后台-【设置】-【关联设置】中管理已关联的公众号。</li></ol><h3 id="小程序用户访问数据上报优化"><a href="#小程序用户访问数据上报优化" class="headerlink" title="小程序用户访问数据上报优化"></a>小程序用户访问数据上报优化</h3><p>为了提供更准确的用户访问数据,小程序数据上报做了系统优化,由微信客户端上报切换为基础库上报。当用户离开小程序页面,触发<code>onHide</code>或<code>onUnload</code>函数时,公共库会上报此次用户访问行为。<br>优化详情如下:</p><ul><li>优化了部分小程序存在页面脏数据的问题</li><li>优化了部分小程序存在错误小程序跳转数据的问题</li><li>当用户点击进入小程序,但小程序框架未加载完成,用户退出小程序,则不做上报,确保每次上报数据均为有效访问行为</li></ul><h3 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h3><ul><li><a href="https://developers.weixin.qq.com/community/develop/doc/000c8033998118cb3168228965b401" target="_blank" rel="external">周社区问题反馈以及功能优化更新(04.01-04.05)</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/0006882b218580bcaf58036f556c01" target="_blank" rel="external">周社区问题反馈以及功能优化更新(03.25-03.29)</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/000c249a62c968e59648fdcd051001" target="_blank" rel="external">周社区问题反馈以及功能优化更新(03.11-03.16)</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/000e8a372e0608040c481445956001" target="_blank" rel="external">周社区问题反馈以及功能优化更新(03.04-03.08)</a></li></ul><h2 id="开发者工具"><a href="#开发者工具" class="headerlink" title="开发者工具"></a>开发者工具</h2><h3 id="「微信开发者工具」新增企业微信小程序插件"><a href="#「微信开发者工具」新增企业微信小程序插件" class="headerlink" title="「微信开发者工具」新增企业微信小程序插件"></a>「微信开发者工具」新增企业微信小程序插件</h3><p>企业微信小程序模拟器插件是为了方便用户在微信开发者工具中进行企业微信小程序开发、调试和代码上传。</p><ul><li>参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/qywx-dev.html" target="_blank" rel="external">https://developers.weixin.qq.com/miniprogram/dev/devtools/qywx-dev.html</a></li></ul><h3 id="「小程序·云开发」云函数本地调试功能上线"><a href="#「小程序·云开发」云函数本地调试功能上线" class="headerlink" title="「小程序·云开发」云函数本地调试功能上线"></a>「小程序·云开发」云函数本地调试功能上线</h3><p>小程序·云开发提供了云函数本地调试功能,方便开发者在本地进行云函数调试,提高开发效率。开发者可通过右键点击云函数名唤起本地调试界面。目前云函数本地调试的支持手动触发和模拟器触发两种请求方式。</p><ul><li>参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/local-debug.html" target="_blank" rel="external">https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/local-debug.html</a></li></ul><h3 id="「小程序·云开发」新增云调用"><a href="#「小程序·云开发」新增云调用" class="headerlink" title="「小程序·云开发」新增云调用"></a>「小程序·云开发」新增云调用</h3><p>云调用是云开发提供的基于云函数使用小程序开放接口的能力。目前覆盖服务端调用的场景,后续将会陆续开放开放数据调用、消息推送、支付等其他多种使用场景。<br>云调用需要在云函数中通过<code>wx-server-sdk</code>使用。在云函数中使用云调用调用服务端接口无需换取<code>access_token</code>,只要是在从小程序端触发的云函数中发起的云调用都经过微信自动鉴权,可以在登记权限后直接调用如发送模板消息等开放接口。</p><ul><li>云调用文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/openapi.html" target="_blank" rel="external">https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/openapi.html</a></li></ul><h3 id="「小程序·云开发」全新云控制台上线"><a href="#「小程序·云开发」全新云控制台上线" class="headerlink" title="「小程序·云开发」全新云控制台上线"></a>「小程序·云开发」全新云控制台上线</h3><p>云开发控制台经过全新设计和改版,优化交互和视觉体验,功能分类更加清晰、各项功能更加易用</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p><strong>开发工具新增版本区分:</strong></p><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly.html" target="_blank" rel="external">开发版 Nightly Build</a>: 日常构建版本,用于尽快修复缺陷和敏捷上线小的特性;开发自测验证,稳定性欠佳</li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/rc.html" target="_blank" rel="external">预发布版 RC Build</a>: 预发布版本,包含大的特性;通过内部测试,稳定性尚可</li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html" target="_blank" rel="external">稳定版 Stable Build</a></li></ul><p>大家可以根据需要,下载对应的版本开发~</p><h1 id="小程序教程"><a href="#小程序教程" class="headerlink" title="小程序教程"></a>小程序教程</h1><h2 id="社区精选文章"><a href="#社区精选文章" class="headerlink" title="社区精选文章"></a>社区精选文章</h2><ul><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/0004e0543b8878a53b589986451413" target="_blank" rel="external">小程序自定义组件知多少</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/000eaadb944de06374485c3ed51813" target="_blank" rel="external">小程序多端框架全面测评</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/000caad3c4cbc03a5648e01e951013" target="_blank" rel="external">一种小成本的线下定位方案 —2019腾讯数字文创节小程序开发有感</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/000aa4e19a0d50bf0f6893b9f56c13" target="_blank" rel="external">Comi - 小程序 markdown 渲染和代码高亮解决方案</a></li></ul><p>更多可以查看<a href="https://developers.weixin.qq.com/community/develop/article" target="_blank" rel="external">文章分享</a>。</p><h2 id="最新踩坑-amp-amp-Tips"><a href="#最新踩坑-amp-amp-Tips" class="headerlink" title="最新踩坑 && Tips"></a>最新踩坑 && Tips</h2><ol><li><p>如果小程序里已经授权过,例如地理位置信息,取消授权的方法:右上角… -> 关于xxx -> 设置。</p></li><li><p>【小程序体验评分】遇到性能体验的问题,可以在小程序开发工具里找到协助定位性能的能力。<br>体验评分是一项给小程序的体验好坏打分的功能,它会在小程序运行过程中实时检查,分析出一些可能导致体验不好的地方,并且定位出哪里有问题,以及给出一些优化建议。</p></li><li><p>小程序里嵌套 web-view,小程序往 web-view 里传数据方法:</p><ol><li>把参数拼装在 url 中传进去,可通过 hash。</li><li>通过 postMessage 传递,只会在特定时机(小程序后退、组件销毁、分享)触发并收到消息, 参考:<a href="https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html。" target="_blank" rel="external">https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html。</a></li><li>通过后台拉取。</li></ol></li><li><p>setData单次设置的数据超过1024kB,工具上测试正常,手机上会报错。Taro 在 setData 的时候会带上一些不需要的数据。</p></li><li><p>小程序的 setStorage 缓存,会在客户端保存尽量久的时间,以下两种情况(会从最不常用的小程序删起):</p><ol><li>客户端空间不够。</li><li>小程序总体容量超过客户端容量5%。</li></ol></li></ol><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>这种没人关注依然狗狗祟祟坚持做的事情,大概是我最喜欢和最擅长的了。^_^</p>]]></content>
<summary type="html">
<p>每月都有小程序期刊,这个月新功能特别多~~<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>前端这几年--1.转岗之路</title>
<link href="https://godbasin.github.io/2019/04/13/about-front-end-1-begin-in/"/>
<id>https://godbasin.github.io/2019/04/13/about-front-end-1-begin-in/</id>
<published>2019-04-13T12:32:34.000Z</published>
<updated>2019-04-13T12:48:41.926Z</updated>
<content type="html"><![CDATA[<p>常常在想,成为前端也有好几年了,新知识新技术层出不穷,那这些年来到底积累了什么呢。如果说有什么可以给到你们,除去前端技术相关,大概也只剩下这一路上的历程和思考,以及一些方式和方法了。<br><a id="more"></a></p><h1 id="从哪里来"><a href="#从哪里来" class="headerlink" title="从哪里来"></a>从哪里来</h1><p>无名小辈的自传,总是不值得关注的。不过我自己的这一生,如果写成一本书或是拍成电视剧的话,毫无疑问我也能成为个主角。</p><h2 id="我的名字被删"><a href="#我的名字被删" class="headerlink" title="我的名字被删"></a>我的名字被删</h2><p>所以首先要自我介绍一下,我叫被删。为什么要叫被删,因为这是我名字的谐音。我爱用谐音,这不为什么我起的英文名叫 basin,出国旅游的时候才知道自己有多蠢,现在倒是挺喜欢用 deleted,不过都没啥影响,你们只需要记得被删就好了,很好记。</p><h2 id="非科班出身"><a href="#非科班出身" class="headerlink" title="非科班出身"></a>非科班出身</h2><p>如果真的写自传,可能我得从幼儿园记事开始写起了。虽然我的成长过程,回想起来也色彩缤纷,也很丰富精彩,不过那些都和如今要跟你们讲的没有多大关系。所以,我们从大学毕业开始吧。</p><p>很遗憾,我大学学的光信息,每天都在各种计算推导,稍微好玩些的也就是全息相关的实验。说来怎么会和写代码扯上关系的呢?大学里我闲得无聊,刚好有个朋友在跟着教授写项目,刚好去参观了下。教授搞的 web 物理引擎,以及游戏设计,他给我演示了在做的一个汽车透视结构图,好像好厉害的样子。</p><p>“好像好厉害”这几个字大概是人生中各种入坑的原因。就跟当初报这个光信息的专业,也是觉得好像很高大上。</p><p>毕业的时候,其实三方签的去华为,做的数通,简单来说包括路由器交换机防火墙这块。培训两个月,然后大家都发配到世界各地去支持了。每天住在酒店里,基本上除了机房、酒店,没有其他地方去,也没有什么人可以交流。做了半年,实在耐不住寂寞,辞职了。离职前刚评了绩效,拿了A跑了,被主管批了一顿。</p><h2 id="外包又怎样"><a href="#外包又怎样" class="headerlink" title="外包又怎样"></a>外包又怎样</h2><p>很显然,非科班出身的我,又没有类似的开发经验,自学了一周多就疯狂投简历。找工作很头疼,裸辞的一番热血,再热烈也很容易被浇灭。</p><p>我还记得找的第一份工作,老板看我学历还行、长得也还可以,让我当秘书。他开了两份offer,秘书的待遇比前端的好一倍不止。那一天真的很苦恼,最后是几位写后台的大哥鼓励下,才下定决心开始学代码。</p><p>那会从华为出来,待遇的落差总会不断地提醒我,到底是为了什么呢?但满怀的热情使得我每天上班充满干劲,下班后也继续在床上打着灯看书和写demo学习。那是jQuery横行的年代,似乎只需要掌握了它,你就能所向披靡。噢当然还有CSS,CSS的调试也是10%的理解+90%的日积月累不断沉淀的。</p><p>那段时间能感觉到,成长很快,几位后台开发小哥哥带着我入门,然后就停不下来了。我曾经在面试的时候说过我学习能力很强,但是通常别人会问,你怎么证明呢?(语塞)</p><p>不知道为什么,现在似乎大家多多少少都会不正视外包,“要不是能力不够也不会当外包”、“不能指望他们能学会什么”这样的话也经常会听到。可能是因为很多人的经历和体验里,都是比较顺畅阻力较少。而我也很荣幸曾经置于职业的低谷,使得很多时候能看到更多的可能性。</p><h2 id="小公司也可以很棒"><a href="#小公司也可以很棒" class="headerlink" title="小公司也可以很棒"></a>小公司也可以很棒</h2><p>成为前端以来,成长最快、回忆最满的一段工作经验,是在商汤度过的。当然,那时候深圳分部还只有几十个人,还只是个小公司。</p><p>接触 angularJS、react.js、vue.js 这些框架,都是在这里完成的。当然最初选型的时候,那会大热的是 angularJS,react 刚刚起步。在其中一个小伙伴还在犹豫用哪个的时候,我就吭哧吭哧地用 angularJS 来重构了。</p><p>后面项目越来越大,来了两个实习生跟着一块干。两个小伙都很棒,当然人多之后,项目管理就会出现问题了。代码规范、接口规范这些都慢慢地一边磨合一边调整,其中一个小弟带着我们一块用上了 Typescript,另外一个则发起组件封装、抽象方式的探讨。</p><p>在这里,深刻领悟到的是,争吵和摩擦其实可以带来很好的正反馈。因为每个人都拥有不一样的想法和角度,在相互碰撞和磨合之后,所谓的集众人之智慧,时可以做出非常棒的事情来。技术博客的念头,也是几位实习生的起哄下开始持续写的。</p><p>新人的优势在于敢想敢做,而老人家的长处在于帮着收拾残局。好几次要发布了,合版本出现很多问题,熬不住的便让他们先走,于是除了在华为之外,熬夜通宵发版的经历,也都是在这里度过的。即使这样,很多新的尝试也让我们快速地成长,收获更多。</p><p>那时候的小团队,大概是目前为止遇到过最优秀的团队了。从前端、后台、平台层、算法层,那段通宵赶版本、去现场跟上线的日子,也打破了对加班的一些偏见。和优秀的人一起,有相同的目标、冲着一样的目的地,努力和坚持便成了回忆里锦上添花的一部分。</p><h2 id="大公司也是人的合集"><a href="#大公司也是人的合集" class="headerlink" title="大公司也是人的合集"></a>大公司也是人的合集</h2><p>曾经,BAT 是所有像我这种从底层爬起的开发者的梦想。当时离开,总监也问我,为什么想走,他从那里面来,也不过如此,这里有更多的机会。我看着他说,体验过的人才有资格说不,我没去过,所以我要去。</p><p>然后我来腾讯了。</p><p>和每次换工作的过程都一样,干脆利落,还没开始准备就开始扔简历了。所以前面几次电面都没过,直到有一个面试官问我,“我看你前面都面了一两次了,你不知道来腾讯这些是必备知识吗?”。我跟他讲,“我知道,我只是还没准备,要是准备了肯定能过。”他给了我一周的时间。</p><p>一周后,电话如约打来,当然我也对答如流,直接约了第二天现场面试,后续当然就是入职啦。在腾讯差不多两年了,也换了一次部门,整体来说,这边的技术能力和氛围,会有些不如预期吧。</p><p>其实早该想明白的,大公司制度再完善,规范再严格。组成公司的,其实也都是一个个有血有肉的人,有人在的地方,都会有躲不掉的一些事情。不过越是困难,原则和坚持才会愈加显得有意义。</p><p>总而言之,时间流逝严重,一眨眼毕业快五年了,而我如今也走到了这里。这里面我遇到了很多的人,也有很多的故事。好的、坏的,一言难尽,等哪天我有心情写故事的时候,再看看要不要写吧~</p><h1 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h1><p>为什么要在博客写这种东西呢?对这个世界来说,成功的人的经历才值得借鉴,他们说的话才有参考价值。</p><p>我还未成功,但是成功的定义在每个人眼里都不一样吧。对我来说,有些事情坚持下来了,几年,十几年,几十年,坚持到一辈子,大概就是属于我的成功了。目前来说,几年下来,也算是达成阶段性的成就了。</p><p>嗯,说不定什么时候,我也只剩下讲故事这项技能了。趁着还爱着敲键盘的时候,代码也好,文字也好,此时此刻,我在做着喜欢的事情,也够了。</p><p>活了这么多年,我也终于发现了自己最擅长的事情,大概是自己搭建舞台,自己表演,然后自己给自己鼓掌。</p><p>“啪,啪,啪”。</p>]]></content>
<summary type="html">
<p>常常在想,成为前端也有好几年了,新知识新技术层出不穷,那这些年来到底积累了什么呢。如果说有什么可以给到你们,除去前端技术相关,大概也只剩下这一路上的历程和思考,以及一些方式和方法了。<br>
</summary>
<category term="工作这杯茶" scheme="https://godbasin.github.io/categories/%E5%B7%A5%E4%BD%9C%E8%BF%99%E6%9D%AF%E8%8C%B6/"/>
<category term="心态" scheme="https://godbasin.github.io/tags/%E5%BF%83%E6%80%81/"/>
</entry>
<entry>
<title>小程序开发月刊第三期(20190315)</title>
<link href="https://godbasin.github.io/2019/03/15/wxapp-latest-20190315/"/>
<id>https://godbasin.github.io/2019/03/15/wxapp-latest-20190315/</id>
<published>2019-03-15T15:33:32.000Z</published>
<updated>2019-03-21T15:34:48.507Z</updated>
<content type="html"><![CDATA[<p>这个月很多能力在开发中,不过社区推出文章分享沉淀,赶紧去看看吧~<br><a id="more"></a></p><h1 id="小程序-latest"><a href="#小程序-latest" class="headerlink" title="小程序 latest"></a>小程序 latest</h1><h2 id="小程序能力"><a href="#小程序能力" class="headerlink" title="小程序能力"></a>小程序能力</h2><h3 id="自定义组件支持数据监听器"><a href="#自定义组件支持数据监听器" class="headerlink" title="自定义组件支持数据监听器"></a>自定义组件支持数据监听器</h3><p>数据监听器可以用于监听和响应任何属性和数据字段的变化。从小程序基础库版本 2.6.1 开始支持。<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/observer.html" target="_blank" rel="external">详情</a></p><h3 id="微信开放社区新增“文章分享”模块"><a href="#微信开放社区新增“文章分享”模块" class="headerlink" title="微信开放社区新增“文章分享”模块"></a>微信开放社区新增“文章分享”模块</h3><p>微信开放社区新增了“文章分享”的模块,希望大家可以将设计、开发和运营的小程序经验分享给更多的用户,将大家平时积累的经验分享出来,也沉淀下来。<br>对于优质的文章,会被选为精选,精选文章会逐步在社区首页展示,并且每周依次在开发者的公众号被推送。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul><li>新增小程序音频中断开始和结束事件<code>wx.onAudioInterruptionBegin</code>、<code>wx.onAudioInterruptionEnd</code>。<a href="https://developers.weixin.qq.com/miniprogram/dev/api/wx.onAudioInterruptionBegin.html" target="_blank" rel="external">详情</a></li></ul><h3 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h3><ul><li><a href="https://developers.weixin.qq.com/community/develop/doc/000cca3e4142280b76388814356c01" target="_blank" rel="external">周社区问题反馈以及功能优化更新(02.26-03.02)</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/000ac69de541e8b0fb28c9fab5b001" target="_blank" rel="external">周社区问题反馈以及功能优化更新(02.11-02.22)</a></li></ul><h1 id="小程序教程"><a href="#小程序教程" class="headerlink" title="小程序教程"></a>小程序教程</h1><h2 id="社区精选文章"><a href="#社区精选文章" class="headerlink" title="社区精选文章"></a>社区精选文章</h2><ul><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/000e64f299cdd8c55a3848f7451013" target="_blank" rel="external">【优化】小程序优化-代码篇</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/00026a9b984b604ad9287077a51413" target="_blank" rel="external">从源码看微信小程序启动过程</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/000c8eba1ec3b8c7ce287954c53c13" target="_blank" rel="external">小程序架构设计(二)</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/0000cc199900b8f66628f610b56413" target="_blank" rel="external">小程序性能和体验优化方法</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/000ccc7eec00e0e93c280153251c13" target="_blank" rel="external">一次在微信小程序里跑 h5 页面的尝试</a></li><li><a href="https://developers.weixin.qq.com/community/develop/article/doc/000a4c1620c188f3adf7db9ab5b413" target="_blank" rel="external">小程序架构设计(一)</a></li></ul><p>更多可以查看<a href="https://developers.weixin.qq.com/community/develop/article?tag=%E7%B2%BE%E9%80%89" target="_blank" rel="external">文章分享</a>。</p><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>微信开发者社区新增了文章分享后,大家可以多多去逛一下,集合大家的智慧和力量,越走越远。</p>]]></content>
<summary type="html">
<p>这个月很多能力在开发中,不过社区推出文章分享沉淀,赶紧去看看吧~<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>写文章这件事</title>
<link href="https://godbasin.github.io/2019/03/10/work-2-article/"/>
<id>https://godbasin.github.io/2019/03/10/work-2-article/</id>
<published>2019-03-10T14:12:34.000Z</published>
<updated>2019-03-13T13:53:37.165Z</updated>
<content type="html"><![CDATA[<p>经验沉淀、定期思考,并以文章的形式进行记录和分享。工作中也一样,把许多经验整理成文档,当然也不乏“你工作就是写文章吧”这样的声音。至于为什么要这么做呢,我想谈谈我的一些想法。<br><a id="more"></a></p><h2 id="程序员与文档的前世今生"><a href="#程序员与文档的前世今生" class="headerlink" title="程序员与文档的前世今生"></a>程序员与文档的前世今生</h2><p>我们程序员,最讨厌的就是看别人的代码,和没有文档。文档,其实是很小的一件事。项目也好,框架或是工具库都好,作为原作者或者踩过坑的,写文档其实并不会花很多的时间。</p><p>也遇到过,有人纯手动撸了一整个前端框架,好几个项目都在用,但是一句话文档都没有,甚至<code>README</code>都没有。怎么启动项目?有什么注意事项?新人来了都重新踩一遍坑,和口口相传。</p><p>写文档,其实也是个思考的过程。如果在做一个项目或是工具之前,有认真做设计、做方案、横向比较业界方案,会留下有系统设计的相关资料。</p><p>但为什么就很多人不愿意做呢?因为觉得更新代码的时候,还要更新文档很麻烦;项目太紧急了,没时间写文档;因为觉得与我无关;因为我已经能徒手造轮子了,文档不重要。</p><p>我们常常把“麻烦”、“没时间”的罪名强加在文档上,却经常忽略了缺乏文档带来的效率低下、设计不合理导致返工和事故等潜在问题。</p><p>我们的人生,并不能只固执于当下的利益和输赢。同样的,写代码也不能抱着“有问题再解决”、“写文档并不能给我带来什么收益”、“说不定明天这个项目就不存在了”这类的想法,做一件事,需要努力地想着怎么做得更好,而不是抱着无所谓的心态。</p><p>遗憾的是,快餐式的消费习惯,渐渐已经成为很多人的生活和工作方式。相比于需要长时间持续沉淀和学习才能获得的技能,很多人选择了囫囵吞枣、先做再说的方式。</p><p>越来越多的人每天的时间都安排很满,从而觉得自己真的很忙。而社会从悄悄惩罚那些不努力的人,变成了悄悄惩罚那些不思考的人。</p><h2 id="知识共享的世界进程"><a href="#知识共享的世界进程" class="headerlink" title="知识共享的世界进程"></a>知识共享的世界进程</h2><p>在这个世界里,我们每个人都是独立的个体,然后相遇并组成一个个的集体。</p><p>互联网早已经出现了,共享的世界也越来越近了。</p><p>分享,交流,共同成长和提升,在这样一个过程里,很多人会有所收获。</p><p>很多人怕被超越,便悄咪咪地把自己所学所有藏了起来。但真正的核心竞争力,即使你共享出来,也没有人能复制。因为那是你独特的人生经历、性格脾气形成的,没有两个人是完全一致的。</p><p>如果,每个人都把自己踩过的坑填了,把积累的经验都沉淀下来,我们在奔向美好未来的路上是可以越跑越快的。每个人都要学习的课本、所有出版的读物、科研界的成果,不都是这样的存在吗?</p><p>我们每个程序员都会去的 Github,不也正是这样的存在吗?</p><p>狭隘的视角,局限的只是自己。外面世界的步伐,并不会因为某个井口太窄而受到阻碍。</p><h2 id="很多事物需要记录"><a href="#很多事物需要记录" class="headerlink" title="很多事物需要记录"></a>很多事物需要记录</h2><p>很久以前就有开始,每隔一段时间就做一段生活记录,反思、想象、或是单纯的记录。</p><p>接触代码之后,发现要学的东西太多了,也开始慢慢记录踩过的坑、自身的理解、经验的沉淀。接手一个项目,也会习惯性地补齐 README 和一些相关的说明文档。试用框架、工具的新能力,也会顺便记下一些总结和心得。</p><p>这些内容,最终都会落到文章的形式进行沉淀。同时,也顺手扔到博客里,或者其他平台上,若能恰好帮上一些人的忙,便再好不过。</p><p>如果遇到的一些问题,都能在网上找到解决方案。如果找不到答案的一些问题,能在解决之后顺手写下来。那后面来的人,甚至自己又遇到一样的问题,不就可以很快地修复吗?</p><p>工作中,也会遇到很多的牛人。而并不是每一位大牛都喜欢做分享和写文章的,好些厉害的人都是自己默默地玩耍代码,很多的思考和想法都藏在心里,只有偶尔的交流和讨论才能接触到。“哇,原来还能这么做”,“哇,这想法好棒”,真正接触到的时候,禁不住会有这样的想法。</p><p>这些精彩的思考方式,优秀的设计模式,也都想要记下来。很多改变世界的念头,都是在思想的碰撞中产生。所有的这些,属于我的,不属于我的,也都想要写下来。</p><p>这样,即使没有站在巨人的肩膀上,也能依靠每个人上山的时候铺下的路,齐心协力地登上峰顶,看到最美的风景了。</p><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>到如今,博客也坚持写了3年多了,而一些生活相关的文章,也断断续续地记录了快 5 年了。</p><p>如果说最大的收获,大概是在这个过程中一边敲下键盘,一边进行思考而沉淀下来的反省。而一路上的历程,也能以某种方式能看的到自身的成长,慢慢调整方向,越走越坚定。</p><p>我觉得这是个很好的习惯,同样地也分享给你们。生活中,职场中,会遇到很多的低谷,依然希望你们能攀上顶峰。祝好。</p><p>——————————————————————————<br>我的工作和生活,界限分明。如果你想要关注非工作非技术状态的我,欢迎关注“牧羊的猪”公众号。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/qrcode_for_gh_0404f6ab1848_258.jpg" alt=""></p>]]></content>
<summary type="html">
<p>经验沉淀、定期思考,并以文章的形式进行记录和分享。工作中也一样,把许多经验整理成文档,当然也不乏“你工作就是写文章吧”这样的声音。至于为什么要这么做呢,我想谈谈我的一些想法。<br>
</summary>
<category term="工作这杯茶" scheme="https://godbasin.github.io/categories/%E5%B7%A5%E4%BD%9C%E8%BF%99%E6%9D%AF%E8%8C%B6/"/>
<category term="心态" scheme="https://godbasin.github.io/tags/%E5%BF%83%E6%80%81/"/>
</entry>
<entry>
<title>小程序自定义组件知多少</title>
<link href="https://godbasin.github.io/2019/02/23/wxapp-component/"/>
<id>https://godbasin.github.io/2019/02/23/wxapp-component/</id>
<published>2019-02-23T14:38:58.000Z</published>
<updated>2019-02-23T14:39:23.332Z</updated>
<content type="html"><![CDATA[<p>小程序里,自定义组件作为一个贯穿小程序架构核心的组成,你对它又掌握了多少呢?<br><a id="more"></a></p><h1 id="自定义组件"><a href="#自定义组件" class="headerlink" title="自定义组件"></a>自定义组件</h1><h2 id="why"><a href="#why" class="headerlink" title="why"></a>why</h2><h3 id="代码的复用"><a href="#代码的复用" class="headerlink" title="代码的复用"></a>代码的复用</h3><p>在起初小程序只支持 Page 的时候,就会有这样蛋疼的问题:多个页面有相同的组件,每个页面都要复制粘贴一遍,每次改动都要全局搜索一遍,还说不准哪里改漏了就出翔了。</p><h3 id="组件化设计"><a href="#组件化设计" class="headerlink" title="组件化设计"></a>组件化设计</h3><p>在前端项目中,组件化是很常见的方式,某块通用能力的抽象和设计,是一个必备的技能。组件的管理、数据的管理、应用状态的管理,这些在我们设计的过程中都是需要去思考的。当然你也可以说我就堆代码就好了,不过一个真正的码农是不允许自己这么随便的!</p><p>所以,组件化是现代前端必须掌握的生存技能!</p><h2 id="自定义组件的实现"><a href="#自定义组件的实现" class="headerlink" title="自定义组件的实现"></a>自定义组件的实现</h2><h3 id="一切都从-Virtual-DOM-说起"><a href="#一切都从-Virtual-DOM-说起" class="headerlink" title="一切都从 Virtual DOM 说起"></a>一切都从 Virtual DOM 说起</h3><p>前面<a href="https://godbasin.github.io/2018/10/05/wxapp-set-data/">《解剖小程序的 setData》</a>有讲过,基于小程序的双线程设计,视图层(Webview 线程)和逻辑层(JS 线程)之间通信(表现为 setData),是基于虚拟 DOM 来实现数据通信和模版更新的。</p><p>自定义组件一样的双线程,所以一样滴基于 Virtual DOM 来实现通信。那在这里,Virtual DOM 的一些基本知识(包括生成 VD 对象、Diff 更新等),就不过多介绍啦~</p><h3 id="Shadow-DOM-模型"><a href="#Shadow-DOM-模型" class="headerlink" title="Shadow DOM 模型"></a>Shadow DOM 模型</h3><p>基于 Virtual DOM,我们知道在这样的设计里,需要一个框架来支撑维护整个页面的节点树相关信息,包括节点的属性、事件绑定等。在小程序里,Exparser 承担了这个角色。</p><p>前面<a href="https://godbasin.github.io/2018/09/23/wxapp-basic-lib/">《关于小程序的基础库》</a>也讲过,Exparser 的主要特点包括:</p><ul><li>基于 Shadow DOM 模型</li><li>可在纯 JS 环境中运行</li></ul><p>Shadow DOM 是什么呢,它就是我们在写代码时候写的自定义组件、内置组件、原生组件等。Shadow DOM 为 Web 组件中的 DOM 和 CSS 提供了封装。Shadow DOM 使得这些东西与主文档的 DOM 保持分离。</p><p>简而言之,Shadow DOM 是一个 HTML 的新规范,其允许开发者封装 HTML 组件(类似 vue 组件,将 html,css,js 独立部分提取)。</p><p>例如我们定义了一个自定义组件叫<code><my-component></code>,你在开发者工具可以见到:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-component-1.jpg" alt="Shadow DOM"></p><p><code>#shadow-root</code>称为影子根,DOM 子树的根节点,和文档的主要 DOM 树分开渲染。可以看到它在<code><my-component></code>里面,换句话说,<code>#shadow-root</code>寄生在<code><my-component></code>上。<code>#shadow-root</code>可以嵌套,形成节点树,即称为影子树(Shadow Tree)。</p><p>像这样:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-component-2.jpg" alt="Shadow Tree"></p><h3 id="Shadow-Tree-拼接"><a href="#Shadow-Tree-拼接" class="headerlink" title="Shadow Tree 拼接"></a>Shadow Tree 拼接</h3><p>既然组件是基于 Shadow DOM,那组件的嵌套关系,其实也就是 Shadow DOM 的嵌套,也可称为 Shadow Tree 的拼接。</p><p>Shadow Tree 拼接是怎么做的呢?一切又得从模版引擎讲起。</p><p>我们知道,Virtual DOM 机制会将节点解析成一个对象,那这个对象要怎么生成真正的 DOM 节点呢?数据变更又是怎么更新到界面的呢?这大概就是模版引擎做的事情了。</p><p><a href="https://godbasin.github.io/2017/10/21/template-engine/">《前端模板引擎》</a>里有详细描述模版引擎的机制,通常来说主要有这些:</p><ul><li>DOM 节点的创建和管理:<code>appendChild</code>/<code>insertBefore</code>/<code>removeChild</code>/<code>replaceChild</code>等</li><li>DOM 节点的关系(嵌套的处理):<code>parentNode</code>/<code>childNodes</code></li><li>通常创建后的 DOM 节点会保存一个映射,在更新的时候取到映射,然后进行处理(通常包括替换节点、改变内容<code>innerHTML</code>、移动删除新增节点、修改节点属性<code>setAttribute</code>)</li></ul><p>在上面的图我们也可以看到,在 Shadow Tree 拼接的过程中,有些节点并不会最终生成 DOM 节点,例如<code><slot></code>这种。</p><p>但是,常用的前端模版引擎,能直接用在小程序里吗?</p><h2 id="双线程的难题"><a href="#双线程的难题" class="headerlink" title="双线程的难题"></a>双线程的难题</h2><h3 id="自定义组件渲染流程"><a href="#自定义组件渲染流程" class="headerlink" title="自定义组件渲染流程"></a>自定义组件渲染流程</h3><p>双线程的设计,给小程序带来了很多便利,安全性管控力都拥有了,当然什么鬼东西都可以比作一把双刃剑,双线程也不例外。</p><p>我们知道,小程序分为 Webview 和 JS 双线程,逻辑层里是没法拿到真正的 DOM 节点,也没法随便动态变更页面的。那在这种情况下,我们要怎么去使用映射来更新模版呢(因为我们压根拿不到 Webview 节点的映射)?</p><p>所以在双线程下,其实两个线程都需要保存一份节点信息。这份节点信息怎么来的呢?其实就是我们需要在创建组件的时候,通过事件通知的方式,分别在逻辑层和视图层创建一份节点信息。</p><p>同时,视图层里的组件是有层级关系的,但是 JS 里没有怎么办?为了维护好父子嵌套等节点关系,所以我们在 逻辑层也需要维护一棵 Shadow Tree。</p><p>那么我们自定义组件的渲染流程大概是:</p><ol><li>组件创建。<ul><li>逻辑层:先是 wxml + js 生成一个 JS 对象(因为需要访问组件实例 this 呀),然后是 JS 其中节点部分生成 Virtual DOM,拼接 Shadow Tree 什么的,最后通过底层通信通知到 视图层</li><li>视图层:拿到节点信息,然后吭哧吭哧开始创建 Shadow DOM,拼接 Shadow Tree 什么的,最后生成真实 DOM,并保留下映射关系</li></ul></li><li>组件更新。</li></ol><p>这时候我们知道,不管是逻辑层,还是视图层,都维护了一份 Shadow Tree,要怎么保证他们之间保持一致呢?</p><h3 id="让-JS-和-Webview-的组件保持一致"><a href="#让-JS-和-Webview-的组件保持一致" class="headerlink" title="让 JS 和 Webview 的组件保持一致"></a>让 JS 和 Webview 的组件保持一致</h3><p>为了让两边的 Shadow Tree 保持一致,可以使用同步队列来传递信息。(这样就不会漏掉啦)</p><p>同步队列可以,每次变动我们就往队列里塞东西就好了。不过这样还会有个问题,我们也知道 setData 其实在实际项目里是使用比较频繁的,要是像 Component 的 observer 里做了 setData 这类型的操作,那不是每次变动会导致一大堆的 setDate?这样通信效率会很低吧?</p><p>所以,其实可以把一次操作里的所有 setData 都整到一次通信里,通过排序保证好顺序就好啦。</p><h2 id="Page-和-Component"><a href="#Page-和-Component" class="headerlink" title="Page 和 Component"></a>Page 和 Component</h2><h3 id="Component-是-Page-的超集"><a href="#Component-是-Page-的超集" class="headerlink" title="Component 是 Page 的超集"></a>Component 是 Page 的超集</h3><p>事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用<code>Component</code>构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应 json 文件中包含<code>usingComponents</code>定义段。</p><blockquote><p>来自<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html" target="_blank" rel="external">官方文档-Component</a></p></blockquote><p>所以,基于 Component 是 Page 的超集,那么其实组件的渲染流程、方式,其实跟页面没多大区别,应该可以一个方式去理解就差不多啦。</p><h3 id="页面渲染"><a href="#页面渲染" class="headerlink" title="页面渲染"></a>页面渲染</h3><p>既然页面就是组件,那其实页面的渲染流程跟组件的渲染流程基本保持一致。</p><p>视图层渲染,可以参考<a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=00024a319d00b87b008612f5f5640a" target="_blank" rel="external">7.4 视图层渲染</a>说明。</p><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>其实很多新框架新工具出来的时候,经常会让人眼前一亮,觉得哇好厉害,哇好高大上。<br>但其实更多时候,我们需要挖掘新事物的核心,其实大多数都是在原有的事物上增加了个新视角,从不一样的视角看,看到的就不一样了呢。作为一名码农,我们要看到不变的共性,变化的趋势。</p>]]></content>
<summary type="html">
<p>小程序里,自定义组件作为一个贯穿小程序架构核心的组成,你对它又掌握了多少呢?<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序开发月刊第二期(20190215)</title>
<link href="https://godbasin.github.io/2019/02/15/wxapp-latest-20190215/"/>
<id>https://godbasin.github.io/2019/02/15/wxapp-latest-20190215/</id>
<published>2019-02-15T15:21:30.000Z</published>
<updated>2019-02-16T06:24:01.960Z</updated>
<content type="html"><![CDATA[<p>听说年后会有更多的能力进入开发状态噢,尤其是云开发的能力,大家期待不~<br><a id="more"></a></p><h1 id="小程序-latest"><a href="#小程序-latest" class="headerlink" title="小程序 latest"></a>小程序 latest</h1><h2 id="小程序能力"><a href="#小程序能力" class="headerlink" title="小程序能力"></a>小程序能力</h2><h3 id="「微信开发者·代码管理」功能上线"><a href="#「微信开发者·代码管理」功能上线" class="headerlink" title="「微信开发者·代码管理」功能上线"></a>「微信开发者·代码管理」功能上线</h3><p>之前在<a href="https://godbasin.github.io/2019/01/10/wxapp-official-functions/">超实用小程序官方能力</a>中提到过 TGit 代码托管,现在 TGit 能力已升级为<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/wechatvcs.html" target="_blank" rel="external">微信开发者·代码管理</a>。</p><h3 id="video-组件同层渲染"><a href="#video-组件同层渲染" class="headerlink" title="video 组件同层渲染"></a>video 组件同层渲染</h3><p>小程序 video 组件的同层渲染目前已经全量了,基础库 2.4.0 以上都支持。</p><p><strong>啥是同层渲染?下面是辅助理解:</strong></p><ul><li><a href="https://godbasin.github.io/2018/10/05/wxapp-set-data/">小程序系列4–解剖小程序的 setData</a></li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/component/native-component.html" target="_blank" rel="external">原生组件相关说明</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/000aa28d030f60a3c4183eecb5d801" target="_blank" rel="external">小程序 video 组件同层渲染公测</a></li></ul><p>总之,同层渲染是利用黑科技让原生组件的层级和非原生组件一样可控,这样我们开发中最蛋疼的原生组件的样式调试就舒服很多了,拜拜<code><cover-view></code>,拜拜<code><cover-image></code></p><blockquote><p>其他的原生组件后续都计划支持同层渲染的,敬请期待。</p></blockquote><h3 id="小程序数据需求征集"><a href="#小程序数据需求征集" class="headerlink" title="小程序数据需求征集"></a>小程序数据需求征集</h3><p>目前小程序已经开放了小程序访问、分享、添加等基础数据,以及用户画像和交易分析等数据。<br>如果大家在使用小程序后台统计模块和小程序数据助手时,有一些不满足的数据需求,或者不好的体验?如果大家对数据工具有更好的想法和建议,欢迎大家留言给小程序团队。</p><blockquote><p>例如,你可以说我想要更详细的加载性能的数据,想要用户行为上报的能力,等等。</p></blockquote><h3 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h3><ul><li><a href="https://developers.weixin.qq.com/community/develop/doc/000ae8f2264c6855e718440a95b801" target="_blank" rel="external">周社区问题反馈以及功能优化更新(02.04-02.08)</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/000c8a1f334310a3ab08a7ab950801" target="_blank" rel="external">周社区问题反馈以及功能优化更新(01.21-01.25)</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/0006a6e1b50c20cb18081c56d5bc01" target="_blank" rel="external">周社区问题反馈以及功能优化更新(01.14-01.18)</a></li></ul><h2 id="开发者工具"><a href="#开发者工具" class="headerlink" title="开发者工具"></a>开发者工具</h2><h3 id="更新日志-1"><a href="#更新日志-1" class="headerlink" title="更新日志"></a>更新日志</h3><ul><li>修复界面调试样式信息显示不全的问题</li><li>修复 app.json usingComponent 没有扩散的问题</li><li>修复 长路径的项目无法正常打开的问题</li><li>更多请查看<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/uplog.html" target="_blank" rel="external">工具日志</a></li></ul><blockquote><p>听说云函数的本地调试能力在开发ing了,还有很多能力都会在后面支持到,期待~</p></blockquote><h1 id="小程序教程"><a href="#小程序教程" class="headerlink" title="小程序教程"></a>小程序教程</h1><h2 id="最新踩坑-Tips"><a href="#最新踩坑-Tips" class="headerlink" title="最新踩坑 Tips"></a>最新踩坑 Tips</h2><ol><li>小程序有办法类似于通过条件判断然后调用 e.preventDefault 和 stopPropagation 的方法吗?<br>暂时不支持,只能通过 wxml 里面用 catch-tap 阻断,但是这个是一定阻断的,不能写条件。</li></ol><blockquote><p>暂时没有更多了,之前用来记录的临时文件忘记保存,重启了电脑丢掉了233333</p></blockquote><h1 id="tools"><a href="#tools" class="headerlink" title="tools"></a>tools</h1><h2 id="框架"><a href="#框架" class="headerlink" title="框架"></a>框架</h2><blockquote><p>Tips 仅供参考: 小程序开发本身就比较方便,个人感觉最好的方式建议直接使用原生开发噢</p></blockquote><h3 id="taro"><a href="#taro" class="headerlink" title="taro"></a>taro</h3><ul><li>Github: <a href="https://github.com/NervJS/taro" target="_blank" rel="external">https://github.com/NervJS/taro</a><blockquote><p>多端统一开发框架,支持用 React 的开发方式编写一次代码,生成能运行在微信/百度/支付宝/字节跳动小程序、H5、React Native 等的应用。</p></blockquote></li></ul><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><h3 id="crypto-js"><a href="#crypto-js" class="headerlink" title="crypto-js"></a>crypto-js</h3><p>JS 加解码库,小程序适用。</p><ul><li>Github: <a href="https://github.com/brix/crypto-js" target="_blank" rel="external">https://github.com/brix/crypto-js</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>本来计划写双周刊的,最后改成了月刊,因为小程序和开发者工具的更新相对而言会慢一些,不像前端每天都有新框架和新工具库。当然,你也可以手动关注小程序社区来获取最新的bug和修复信息~欢迎对月刊内容进行<a href="https://github.com/godbasin/godbasin.github.io/issues/15" target="_blank" rel="external">留言讨论和推荐</a>~</p>]]></content>
<summary type="html">
<p>听说年后会有更多的能力进入开发状态噢,尤其是云开发的能力,大家期待不~<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序开发月刊第一期(20190114)</title>
<link href="https://godbasin.github.io/2019/01/14/wxapp-latest-20190114/"/>
<id>https://godbasin.github.io/2019/01/14/wxapp-latest-20190114/</id>
<published>2019-01-14T14:55:52.000Z</published>
<updated>2019-02-16T06:23:59.037Z</updated>
<content type="html"><![CDATA[<p>小程序的一些能力更新、踩坑历史、以及一些开源工具库和框架的推荐记录第一弹。<br><a id="more"></a></p><h1 id="小程序-latest"><a href="#小程序-latest" class="headerlink" title="小程序 latest"></a>小程序 latest</h1><h2 id="开发者工具"><a href="#开发者工具" class="headerlink" title="开发者工具"></a>开发者工具</h2><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/settings.html" target="_blank" rel="external">新增黑色主题</a></li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/edit.html#typescript-%E6%94%AF%E6%8C%81" target="_blank" rel="external">支持 Typescript</a></li></ul><h1 id="小程序教程"><a href="#小程序教程" class="headerlink" title="小程序教程"></a>小程序教程</h1><h2 id="使用-Tips"><a href="#使用-Tips" class="headerlink" title="使用 Tips"></a>使用 Tips</h2><ul><li><a href="https://godbasin.github.io/2019/01/10/wxapp-official-functions/">超实用小程序官方能力</a></li></ul><blockquote><p>目前来说,大多数是我自己的文章,也非常欢迎大家推荐文章来~</p></blockquote><h2 id="最新踩坑-Tips"><a href="#最新踩坑-Tips" class="headerlink" title="最新踩坑 Tips"></a>最新踩坑 Tips</h2><ol><li><p>调试小程序时,有些请求例如上传文件请求,无法再控制台里查看到完整的请求信息。<br>解决办法:小程序开发工具可以设置网络代理,转发到抓包工具例如Charles中即可。</p></li><li><p>在原生页跳转进入小程序场景下(仿原生,没有关闭和后退按钮),有没有内部能力从小程序返回到原生页?<br>可使用<a href="https://developers.weixin.qq.com/miniprogram/dev/api/wx.navigateBackMiniProgram.html?search-key=navigateBackMiniProgram" target="_blank" rel="external">navigateBackMiniProgram</a>返回到上一个小程序。只有在当前小程序是被其他小程序打开时可以调用成功</p><blockquote><p>虽然写的是返回小程序,但是他就是可以用。算是实现的有bug,目前测试没遇到过不行的情况。<br>iOS仿原生点了是切后台,安卓仿原生是关闭,这个无解。<br>想安心一点就用<code><navigator target="miniProgram" open-type="exit"></code>,版本比较高</p></blockquote></li><li><p>小程序右上角的关闭按钮,只是将小程序切换到后台并不会关闭小程序。若需要重新加载,需要在微信首页下拉删掉使用过的小程序;另从后台唤醒时,会触发<code>onShow</code>而不是<code>onLoad</code>。</p></li><li><p>如果用户通过微信首页下拉删掉使用过的小程序,那么小程序代码里面通过 localStorage 保存的缓存信息以及通过文件管理器保存的文件都会被清掉。</p></li><li><p>小程序工具上传代码,勾选 ES6 转 ES5,只会针对 ES6 进行编译,对 ES7/ES8 代码并不会编译,可能导致兼容性问题(如<code>Object.values</code>)。<br>节后工具会上线一个 ES6+ 转 ES5 的能力,一站式全部处理掉。</p></li></ol><blockquote><p>后续踩坑相关的,可能会整理到一个地方一起沉淀吧~~~</p></blockquote><h1 id="tools"><a href="#tools" class="headerlink" title="tools"></a>tools</h1><h2 id="框架"><a href="#框架" class="headerlink" title="框架"></a>框架</h2><blockquote><p>Tips 仅供参考: 小程序开发本身就比较方便,个人感觉最好的方式建议直接使用原生开发噢</p></blockquote><h3 id="wepy"><a href="#wepy" class="headerlink" title="wepy"></a>wepy</h3><ul><li>Github: <a href="https://github.com/Tencent/wepy" target="_blank" rel="external">https://github.com/Tencent/wepy</a></li></ul><h3 id="mpvue"><a href="#mpvue" class="headerlink" title="mpvue"></a>mpvue</h3><ul><li>Github: <a href="https://github.com/mpvue" target="_blank" rel="external">https://github.com/mpvue</a></li></ul><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><h3 id="weRequest"><a href="#weRequest" class="headerlink" title="weRequest"></a>weRequest</h3><p>解决繁琐的小程序会话管理,一款自带登录态管理的网络请求组件:</p><ul><li>session 登录态管理(静默续期)</li><li>cache 请求缓存能力</li><li><p>封装请求测速能力</p></li><li><p>Github: <a href="https://github.com/IvinWu/weRequest" target="_blank" rel="external">https://github.com/IvinWu/weRequest</a></p></li></ul><h3 id="we-cropper"><a href="#we-cropper" class="headerlink" title="we-cropper"></a>we-cropper</h3><p>微信小程序图片裁剪工具。</p><ul><li>Github: <a href="https://github.com/we-plugin/we-cropper" target="_blank" rel="external">https://github.com/we-plugin/we-cropper</a></li></ul><h3 id="westore"><a href="#westore" class="headerlink" title="westore"></a>westore</h3><p>微信小程序状态管理解决方案</p><ul><li>Github: <a href="https://github.com/Tencent/westore" target="_blank" rel="external">https://github.com/Tencent/westore</a></li></ul><blockquote><p>Tips: store 为全局状态,不同组件或页面需要注意数据隔离</p></blockquote><h2 id="图表"><a href="#图表" class="headerlink" title="图表"></a>图表</h2><h3 id="echarts-for-weixin"><a href="#echarts-for-weixin" class="headerlink" title="echarts-for-weixin"></a>echarts-for-weixin</h3><p>ECharts 的微信小程序版本</p><ul><li>Github: <a href="https://github.com/ecomfe/echarts-for-weixin" target="_blank" rel="external">https://github.com/ecomfe/echarts-for-weixin</a></li></ul><blockquote><p>注意:移动端中使用 ECharts,折线图的点击事件体验会很差</p></blockquote><h3 id="wx-f2"><a href="#wx-f2" class="headerlink" title="wx-f2"></a>wx-f2</h3><p>F2 的微信小程序图表示例</p><ul><li>Github: <a href="https://github.com/antvis/wx-f2" target="_blank" rel="external">https://github.com/antvis/wx-f2</a></li></ul><h3 id="wx-charts"><a href="#wx-charts" class="headerlink" title="wx-charts"></a>wx-charts</h3><p>微信小程序图表charts组件</p><ul><li>Github: <a href="https://github.com/xiaolin3303/wx-charts" target="_blank" rel="external">https://github.com/xiaolin3303/wx-charts</a></li></ul><blockquote><p>该图表组件已没有维护,但是源码比较简单清晰,可自定义修改</p></blockquote><h2 id="UI-库"><a href="#UI-库" class="headerlink" title="UI 库"></a>UI 库</h2><h3 id="weui-小程序"><a href="#weui-小程序" class="headerlink" title="weui-小程序"></a>weui-小程序</h3><ul><li>Github: <a href="https://github.com/Tencent/weui-wxss" target="_blank" rel="external">https://github.com/Tencent/weui-wxss</a></li></ul><h3 id="wux-weapp"><a href="#wux-weapp" class="headerlink" title="wux-weapp"></a>wux-weapp</h3><ul><li>Github: <a href="https://github.com/wux-weapp/wux-weapp" target="_blank" rel="external">https://github.com/wux-weapp/wux-weapp</a></li></ul><blockquote><p>Tips: 表单相关的 input/textarea 稍微有毒,其他暂可正常使用</p></blockquote><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>本期作为第一期,所以把目前的一些工具库和文章沉淀一并发出,大家如果有很好的文章、工具推荐,可以一起<a href="https://github.com/godbasin/godbasin.github.io/issues/15" target="_blank" rel="external">留言讨论和推荐</a>~~</p>]]></content>
<summary type="html">
<p>小程序的一些能力更新、踩坑历史、以及一些开源工具库和框架的推荐记录第一弹。<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>超实用小程序官方能力</title>
<link href="https://godbasin.github.io/2019/01/10/wxapp-official-functions/"/>
<id>https://godbasin.github.io/2019/01/10/wxapp-official-functions/</id>
<published>2019-01-10T14:53:58.000Z</published>
<updated>2019-01-10T14:54:09.869Z</updated>
<content type="html"><![CDATA[<p>小程序官方平台和工具里,其实有很多很好用的能力,你都了解吗?<br><a id="more"></a></p><h1 id="小程序管理后台"><a href="#小程序管理后台" class="headerlink" title="小程序管理后台"></a>小程序管理后台</h1><p>微信公众平台里,其实藏着一些好用的能力,一起来看看把。</p><h2 id="问题定位辅助"><a href="#问题定位辅助" class="headerlink" title="问题定位辅助"></a>问题定位辅助</h2><hr><h3 id="运维中心"><a href="#运维中心" class="headerlink" title="运维中心"></a>运维中心</h3><p>在小程序管理后台,【开发】-【运维中心】里,可以有以下能力:</p><ul><li><strong>错误查询</strong>: 可以查到所有小程序运行错误的记录。</li><li><strong>性能监控</strong>: 可以监控小程序运行的性能,包括不同时间段的<code>启动耗时</code>、<code>下载耗时</code>、<code>初次渲染耗时</code>等。</li><li><strong>告警设置</strong>: 错误告警通过微信群来通知,每个小程序对应唯一的告警群,扫码加入后即可接收告警通知。</li></ul><blockquote><p>微信7.0以后:</p><ol><li>如果使用工具压缩和编译代码,会自动带上 sourcemap ,运维中心的错误会显示原来文件名字和行号</li><li>如果使用第三方框架,在代码中内敛 sourcemap 或者有同名 sourcemap 文件存在,工具会自动合并和解析,从而做到错误会显示原来文件名字和行号。</li></ol></blockquote><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1547096780.jpg" alt="运维中心界面"></p><hr><h3 id="日志管理"><a href="#日志管理" class="headerlink" title="日志管理"></a>日志管理</h3><ol><li>开发中日志打印,使用日志管理器实例<code>LogManager</code>。使用方式查看<a href="https://developers.weixin.qq.com/miniprogram/dev/api/LogManager.html" target="_blank" rel="external">API - LogManager</a>。</li><li>用户在使用过程中,可以在小程序的 profile 页面,点击【投诉与反馈】-【功能异常】-【勾选上传日志】,则可以上传日志。</li></ol><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1547097426.png" alt="这里需要勾上才会上传"></p><ol><li>在小程序管理后台,【管理】-【反馈管理】,就可以查看上传的日志。</li></ol><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1547097350.jpg" alt="这里可以下载日志"></p><hr><h3 id="运营数据"><a href="#运营数据" class="headerlink" title="运营数据"></a>运营数据</h3><p>有两种方式可以方便的看到小程序的<a href="https://developers.weixin.qq.com/miniprogram/dev/quickstart/basic/release.html#%E8%BF%90%E8%90%A5%E6%95%B0%E6%8D%AE" target="_blank" rel="external">运营数据</a>:</p><ol><li>在小程序管理后台,【统计】,可以看到常规分析(概况、访问分析、来源分析、用户画像)与自定义分析,点击相应的 tab 可以看到相关的数据。</li><li>使用小程序数据助手,在微信中方便的查看运营数据。</li></ol><p><strong>常规分析(不需要配置或开发)</strong></p><blockquote><p>参考文档: <a href="https://developers.weixin.qq.com/miniprogram/analysis/regular/" target="_blank" rel="external">常规分析</a></p></blockquote><ul><li>概况:提供小程序关键指标趋势以及 top 页面访问数据,快速了解小程序发展概况;</li><li>访问分析:提供小程序用户访问规模、来源、频次、时长、深度、留存以及页面详情等数据,具体分析用户新增、活跃和留存情况;</li><li>实时统计:提供小程序实时访问数据,满足实时监控需求;</li><li>用户画像:提供小程序的用户画像数据,包括用户年龄、性别、地区、终端及机型分布。</li></ul><p>似乎晒点图会更加直观:<br><img src="https://developers.weixin.qq.com/miniprogram/analysis/image/weanalytics/2_1.png?t=19010912" alt="昨日概况"><br><img src="https://developers.weixin.qq.com/miniprogram/analysis/image/weanalytics/4_2.png?t=19010912" alt="访问分布"><br><img src="https://developers.weixin.qq.com/miniprogram/analysis/image/weanalytics/6_4.png?t=19010912" alt="终端及机型分布"></p><p><strong>自定义分析(需自行配置和开发)</strong></p><blockquote><p>参考文档: <a href="https://developers.weixin.qq.com/miniprogram/analysis/custom/" target="_blank" rel="external">自定义分析</a></p></blockquote><p>配置自定义上报,精细跟踪用户在小程序内的行为,结合用户属性、系统属性、事件属性进行灵活多维的事件分析和漏斗分析,满足小程序的个性化分析需求。</p><p>同样,晒点官方图:<br><img src="https://developers.weixin.qq.com/miniprogram/analysis/image/weanalytics/5_14.png?t=19010912" alt=""><br><img src="https://developers.weixin.qq.com/miniprogram/analysis/image/weanalytics/5_21.png?t=19010912" alt=""></p><h2 id="第三方能力"><a href="#第三方能力" class="headerlink" title="第三方能力"></a>第三方能力</h2><hr><h3 id="TGit-代码托管"><a href="#TGit-代码托管" class="headerlink" title="TGit 代码托管"></a>TGit 代码托管</h3><p><strong>重要Tips: TGit 能力即将会升级为 git.weixin.qq.com,将全量免费提供给微信的开发者,期待最新消息!~</strong></p><p>更多详情,可以参考<a href="https://developers.weixin.qq.com/miniprogram/dev/qcloud/tgit.html" target="_blank" rel="external">TGit开通及配置流程</a>。</p><h1 id="小程序开发工具"><a href="#小程序开发工具" class="headerlink" title="小程序开发工具"></a>小程序开发工具</h1><h2 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h2><hr><h3 id="真机调试"><a href="#真机调试" class="headerlink" title="真机调试"></a>真机调试</h3><blockquote><p>参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/remote-debug.html" target="_blank" rel="external">真机调试</a></p></blockquote><p>我们经常会遇到小程序开发工具没有问题,但是真机上跑的时候就出翔了,或者某些UI歪掉了,这时候真机调试就显得特别方便啦~<br>真机远程调试功能可以实现直接利用开发者工具,通过网络连接,对手机上运行的小程序进行调试,帮助开发者更好的定位和查找在手机上出现的问题。</p><p><strong>使用方式</strong>:<br>点击开发者工具的工具栏上 “远程调试” 按钮。</p><p><strong>实用能力</strong>:</p><ul><li>调试:断点、单步调试,可在 console 里调试<code>wx.***</code>能力</li><li>查看和 debug 请求</li><li>查看 UI 布局和样式(定位奇葩的手机兼容的时候,特别好用)</li></ul><p><img src="https://developers.weixin.qq.com/miniprogram/dev/devtools/image/devtools2/remote-debug/iphone.jpg?t=19011013" alt="使用效果"></p><h2 id="构建"><a href="#构建" class="headerlink" title="构建"></a>构建</h2><hr><h3 id="npm-支持"><a href="#npm-支持" class="headerlink" title="npm 支持"></a>npm 支持</h3><blockquote><p>参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html" target="_blank" rel="external">npm 支持</a></p></blockquote><p>小程序基础库版本<code>2.2.1</code>开始,就支持 npm 构建啦。</p><ol><li>安装 npm 依赖。</li><li>点击开发者工具中的菜单栏:【工具】-【构建 npm】,就可以啦。</li></ol><hr><h3 id="自定义预处理"><a href="#自定义预处理" class="headerlink" title="自定义预处理"></a>自定义预处理</h3><p>通过自定义预处理,我们可以设置在上传代码之前,做一些什么操作,例如跑测试、编译构建等。可以通过<code>project.config.json</code>中的<code>scripts</code>来配置:</p><ul><li><code>beforeCompile</code>: 编译前预处理命令</li><li><code>beforePreview</code>: 预览前预处理命令</li><li><code>beforeUpload</code>: 上传前预处理命令</li></ul><h2 id="测试体验"><a href="#测试体验" class="headerlink" title="测试体验"></a>测试体验</h2><hr><h3 id="小程序开发助手"><a href="#小程序开发助手" class="headerlink" title="小程序开发助手"></a>小程序开发助手</h3><blockquote><p>参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/mydev.html" target="_blank" rel="external">小程序开发助手</a></p></blockquote><p>微信公众平台发布的官方小程序,帮助开发和运营人员在手机端更方便快捷地查看和预览小程序。<br>有权限的项目成员,可以直接点击体验任何一个需要测试或体验的版本,而不需要二维码的口口相传。</p><p><strong>最新消息:小程序开发助手已经升级为小程序助手了,在移动端可以管理版本了~</strong></p><hr><h3 id="体验评分"><a href="#体验评分" class="headerlink" title="体验评分"></a>体验评分</h3><blockquote><p>参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/audits.html" target="_blank" rel="external">体验评分</a></p></blockquote><p>体验评分是一项给小程序的体验好坏打分的功能,它会在小程序运行过程中实时检查,分析出一些可能导致体验不好的地方,并且定位出哪里有问题,以及给出一些优化建议。<br><img src="https://developers.weixin.qq.com/miniprogram/dev/devtools/image/devtools/audits.png?t=19010919" alt="体验评分效果"><br><strong>手动启动:</strong></p><ol><li>在调试器区域切换到 Audits 面板。</li><li>点击左上角”开始“按钮,然后自行操作小程序界面,运行过的页面就会被“体验评分”检测到。</li><li>点击 “Stop” 停止分析,就会看到一份分析报告,之后便可根据分析报告进行相关优化。</li></ol><p><strong>自动运行(实时检查):</strong><br>开发者在工具的右上角 “详情” 面板里勾选 “自动运行体验评分” 选项即可开启。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/debug.html" target="_blank" rel="external">小程序调试</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>很多时候,我们觉得小程序的开发和调试总是有哪里不满意,但其实官方在很认真地完善各个环节,不过他们比较低调,很多能力我们都没有用上。<br>很多 web 开发写小程序,都喜欢用像 mpvue、wepy 这些框架。不用去了解小程序的运行机制、底层原理,其实也很方便。不过作为一个细腻的开发,其实了解一下也能发现小程序它棒在哪里了。</p>]]></content>
<summary type="html">
<p>小程序官方平台和工具里,其实有很多很好用的能力,你都了解吗?<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>如何发布 typescript npm 包</title>
<link href="https://godbasin.github.io/2019/01/05/ts-npm-package/"/>
<id>https://godbasin.github.io/2019/01/05/ts-npm-package/</id>
<published>2019-01-05T01:09:58.000Z</published>
<updated>2019-01-05T01:10:07.235Z</updated>
<content type="html"><![CDATA[<p>有些时候,我们需要搞个 npm 包,typescript 那么棒,我们要怎么发布个带 typing 的 npm 依赖包呢。<br><a id="more"></a></p><h2 id="为啥要用-npm-包捏"><a href="#为啥要用-npm-包捏" class="headerlink" title="为啥要用 npm 包捏"></a>为啥要用 npm 包捏</h2><hr><p>傻孩子,因为你不想在每个项目里都复制一遍你的代码,然后每次改动和修 bug 的时候,在每个使用到的项目里都改一遍吧?</p><p>这个时候,你就可以用 npm 包来管理啦,自带版本功能,你可以用简单的一句<code>npm install [email protected]</code>就可以更新代码啦。另外,我们还可以不占用自己的任何空间,npm 就会帮你保管各个版本的依赖包,你还可以把每个版本的代码都翻出来看。</p><h2 id="npm-发布"><a href="#npm-发布" class="headerlink" title="npm 发布"></a>npm 发布</h2><hr><p>别急,首先我们来讲下常规的 npm 包发布的流程。</p><h3 id="1-申请-npm-账号"><a href="#1-申请-npm-账号" class="headerlink" title="1. 申请 npm 账号"></a>1. 申请 npm 账号</h3><p><a href="https://www.npmjs.com/signup" target="_blank" rel="external">点这里</a></p><h3 id="2-登录-npm-账号"><a href="#2-登录-npm-账号" class="headerlink" title="2. 登录 npm 账号"></a>2. 登录 npm 账号</h3><p>很简单,在终端命令行直接敲下:<br><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">npm login</div></pre></td></tr></table></figure></p><p>对的,就是这么直接。然后根据提示输入账号和密码就好啦,成功之后会看到这样的提示:</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Logged <span class="keyword">in</span> as bamblehorse to scope @username on https://registry.npmjs.org/.</div></pre></td></tr></table></figure><p>棒!</p><h3 id="3-初始化-npm-设置"><a href="#3-初始化-npm-设置" class="headerlink" title="3. 初始化 npm 设置"></a>3. 初始化 npm 设置</h3><p>同样的,你只需要输入以下命令行:<br><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">npm init</div></pre></td></tr></table></figure></p><p>就可以根据提示,把你的依赖包名字、介绍、email、github 啥的都一块生成,这样:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1546593090.jpg" alt="你问的有点多耶"></p><p><strong>配置说明</strong><br>更多常用的配置可以看看<a href="https://docs.npmjs.com/files/package.json" target="_blank" rel="external">官网</a>,这里我们简单讲几个跟代码相关的:</p><ul><li><code>main</code>: 入口文件。别人安装了你的依赖包之后,会根据这里的路径来寻找入口,通常你要<code>module.exports = xxx</code>来输出</li><li><code>scripts</code>: 构建命令,例如你可能会需要<code>dev</code>、<code>test</code>、<code>build</code>等一些构建命令</li><li><code>bin</code>: 脚手架命令。通常在做脚手架的时候,你会通过<code>bin</code>来提供一些脚手架的命令来给开发者使用,像<code>vue init</code>,这里的<code>init</code>便指向一个可执行的脚本文件</li><li><code>dependencies</code>: 这里管理你的依赖包里使用到的一些依赖包,用户在安装你的包的时候,也会将对应的依赖包安装上,这也是为啥通常我们<code>npm install</code>一个包的时候,会看到好多的依赖安装</li></ul><h3 id="4-发布代码"><a href="#4-发布代码" class="headerlink" title="4. 发布代码"></a>4. 发布代码</h3><p>嗯,发布也就一句命令:<br><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">npm publish</div></pre></td></tr></table></figure></p><p>这里需要注意的是,你如果想发布一个 npm 包,先去<a href="https://www.npmjs.com" target="_blank" rel="external">官网</a>查一下有没有人已经用了这个名字哇,不然发布的时候会提示你咩有权限:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1546594083.jpg" alt="像酱紫"><br>一看,发现别人已经占用了:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1546596854.jpg" alt="被占用啦"></p><p>正常发布的话,应该就是这样提示成功的:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1546595444.jpg" alt="成功!"></p><p>你会发现,你只有一个<code>package.json</code>文件,也能依然成功发布。</p><p><strong>取消发布</strong><br>注意,npm 包的最新版本,跟你发布的<code>version</code>并没有多大关系,即使你上一次发布的是<code>1.1.0</code>版本,你发布个<code>0.0.1</code>版本,也依然会盖掉。</p><p>像上面这里,我就很悲剧地把原有代码给盖掉了。这个时候你可以赶紧取消发布(24小时内有效,更久的得自己申述了):<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1546595796.jpg" alt="还好还好"></p><h3 id="5-提交到-github"><a href="#5-提交到-github" class="headerlink" title="5. 提交到 github"></a>5. 提交到 github</h3><p>通常,我们发布个 npm 依赖包,也多半是想要开源的。</p><p>这个时候,我们就能在自己的 github 账号下开一个 repo,然后直接把源码提交上去啦。酱紫,别人也可以给你提交 pr 啦。</p><h2 id="typescript-npm-包发布"><a href="#typescript-npm-包发布" class="headerlink" title="typescript npm 包发布"></a>typescript npm 包发布</h2><hr><p>我们接着讲讲怎么发布 typescript 的依赖包叭~</p><h3 id="1-webpack-构建?"><a href="#1-webpack-构建?" class="headerlink" title="1. webpack 构建?"></a>1. webpack 构建?</h3><p>很多时候,我们的依赖包也想用高大上的 ES6/ES7/Typescript 来写,但是这样的代码并不能直接输出到用户,因为他们如果没有构建的话,使用起来兼容性可能就会出翔。</p><p>通常的做法,我们也跟平时一样去搞个 webpack 环境就好啦。然后我们生成两份代码,一份压缩一份不压缩:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// webpack.config.js</span></div><div class="line"><span class="keyword">let</span> path = <span class="built_in">require</span>(<span class="string">"path"</span>);</div><div class="line"><span class="keyword">let</span> webpack = <span class="built_in">require</span>(<span class="string">"webpack"</span>);</div><div class="line"><span class="keyword">let</span> pk = <span class="built_in">require</span>(<span class="string">"./package.json"</span>);</div><div class="line"></div><div class="line"><span class="built_in">module</span>.exports = [</div><div class="line"> { <span class="comment">// 第一份生产环境的压缩代码</span></div><div class="line"> mode: <span class="string">"production"</span>,</div><div class="line"> <span class="attr">entry</span>: <span class="string">"./src/index.ts"</span>, <span class="comment">// 入口文件呀</span></div><div class="line"> <span class="built_in">module</span>: {</div><div class="line"> <span class="attr">rules</span>: [ <span class="comment">// ts loader</span></div><div class="line"> {</div><div class="line"> <span class="attr">test</span>: <span class="regexp">/\.tsx?$/</span>,</div><div class="line"> <span class="attr">use</span>: <span class="string">"ts-loader"</span>,</div><div class="line"> <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span></div><div class="line"> }</div><div class="line"> ]</div><div class="line"> },</div><div class="line"> <span class="attr">resolve</span>: { <span class="comment">// 解析文件呀</span></div><div class="line"> extensions: [<span class="string">".tsx"</span>, <span class="string">".ts"</span>, <span class="string">".js"</span>]</div><div class="line"> },</div><div class="line"> <span class="attr">output</span>: {</div><div class="line"> <span class="attr">path</span>: path.join(__dirname, <span class="string">"build"</span>),</div><div class="line"> <span class="attr">filename</span>: <span class="string">"yourLibName.min.js"</span>,</div><div class="line"> <span class="attr">library</span>: <span class="string">"yourLibName"</span>,</div><div class="line"> <span class="attr">libraryTarget</span>: <span class="string">"commonjs-module"</span></div><div class="line"> }</div><div class="line"> },</div><div class="line"> { <span class="comment">// 第二份开发环境的不压缩代码</span></div><div class="line"> mode: <span class="string">"development"</span>,</div><div class="line"> <span class="attr">entry</span>: <span class="string">"./src/index.ts"</span>,</div><div class="line"> <span class="attr">module</span>: {</div><div class="line"> <span class="attr">rules</span>: [ <span class="comment">// 如果只是正常的 js 构建,请改成 babel-loader 啦</span></div><div class="line"> {</div><div class="line"> <span class="attr">test</span>: <span class="regexp">/\.tsx?$/</span>,</div><div class="line"> <span class="attr">use</span>: <span class="string">"ts-loader"</span>,</div><div class="line"> <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span></div><div class="line"> }</div><div class="line"> ]</div><div class="line"> },</div><div class="line"> <span class="attr">resolve</span>: {</div><div class="line"> <span class="attr">extensions</span>: [<span class="string">".tsx"</span>, <span class="string">".ts"</span>, <span class="string">".js"</span>]</div><div class="line"> },</div><div class="line"> <span class="attr">output</span>: {</div><div class="line"> <span class="attr">path</span>: path.join(__dirname, <span class="string">"build"</span>),</div><div class="line"> <span class="attr">filename</span>: <span class="string">"yourLibName.js"</span>,</div><div class="line"> <span class="attr">library</span>: <span class="string">"yourLibName"</span>,</div><div class="line"> <span class="attr">libraryTarget</span>: <span class="string">"commonjs-module"</span></div><div class="line"> },</div><div class="line"> <span class="attr">devtool</span>: <span class="string">"inline-source-map"</span></div><div class="line"> }</div><div class="line">];</div></pre></td></tr></table></figure></p><p><strong>tsc 构建</strong><br>在一些时候,我们其实并不需要搞很复杂,我们只需要简单的弄一下<code>tsc</code>原地产生js文件输出就好啦。</p><h3 id="2-生成-typing?"><a href="#2-生成-typing?" class="headerlink" title="2. 生成 typing?"></a>2. 生成 typing?</h3><p>我们既然自己用了 typescript 写代码爽歪歪,怎么可以不让其他开发者一起用呢?</p><p>我们不用再重新弄一份整齐的<code>xxx.d.ts</code>文件,只需要在<code>tsconfig.json</code>里加一个命令就好啦:<br><figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> "compilerOptions": {</div><div class="line"> // ...其他配置</div><div class="line"> "declaration": true // 对,就是它!</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><p>加上这个,不管是 webpack 还是 tsc,每个 ts 文件都会生成一个<code>xxx.d.ts</code>文件,里面包括你的所有方法的 typing 定义啦~</p><p>效果如图:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1546598800.jpg" alt="用得开心用得放心"></p><p>如果你想要了解更多,可以直接拉<a href="https://github.com/godbasin/weRequest/tree/1.1.0_ts" target="_blank" rel="external">这里的代码看看</a>,记得是ts分支噢~</p><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>开源的意义,在于和更多的人去合作和分享,结合大家的智慧,来打造很棒的东西!</p>]]></content>
<summary type="html">
<p>有些时候,我们需要搞个 npm 包,typescript 那么棒,我们要怎么发布个带 typing 的 npm 依赖包呢。<br>
</summary>
<category term="柴米油盐工具集" scheme="https://godbasin.github.io/categories/%E6%9F%B4%E7%B1%B3%E6%B2%B9%E7%9B%90%E5%B7%A5%E5%85%B7%E9%9B%86/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序 gulp 简单构建</title>
<link href="https://godbasin.github.io/2018/12/30/wxapp-gulp/"/>
<id>https://godbasin.github.io/2018/12/30/wxapp-gulp/</id>
<published>2018-12-30T04:00:30.000Z</published>
<updated>2018-12-30T04:00:35.339Z</updated>
<content type="html"><![CDATA[<p>虽然 webpack 用的比较多,不过在小程序这种场景下,简单的 gulp 也是个不错的选择吧~<br><a id="more"></a></p><h2 id="gulp-构建小程序"><a href="#gulp-构建小程序" class="headerlink" title="gulp 构建小程序"></a>gulp 构建小程序</h2><hr><h3 id="简单的-copy"><a href="#简单的-copy" class="headerlink" title="简单的 copy"></a>简单的 copy</h3><p>对小程序来说,除了<code>app.js</code>作为程序入口之外,每个<code>page</code>页面都可以作为一个页面入口,更倾向是固定路径模式的多页应用。</p><p>最终提交的代码,便是这种结构的代码:</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">├── app.js</div><div class="line">├── app.json</div><div class="line">├── app.wxss</div><div class="line">├── pages</div><div class="line">│ │── index</div><div class="line">│ │ ├── index.wxml</div><div class="line">│ │ ├── index.js</div><div class="line">│ │ ├── index.json</div><div class="line">│ │ └── index.wxss</div><div class="line">│ └── logs</div><div class="line">│ ├── logs.wxml</div><div class="line">│ └── logs.js</div></pre></td></tr></table></figure><p>所以,在编译的过程,很多文件都是需要简单地 copy 到目标目录的。我们定义复制和变动复制的任务:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 待复制的文件,不包含需要编译的文件</span></div><div class="line"><span class="keyword">var</span> copyPath = [</div><div class="line"> <span class="string">"src/**/!(_)*.*"</span>,</div><div class="line"> <span class="string">"!src/**/*.less"</span>,</div><div class="line"> <span class="string">"!src/**/*.ts"</span>,</div><div class="line"> <span class="string">"!src/img/**"</span></div><div class="line">];</div><div class="line"><span class="comment">// 复制不包含需要编译的文件,和图片的文件</span></div><div class="line">gulp.task(<span class="string">"copy"</span>, () => {</div><div class="line"> <span class="keyword">return</span> gulp.src(copyPath, option).pipe(gulp.dest(dist));</div><div class="line">});</div><div class="line"><span class="comment">// 复制不包含需要编译的文件,和图片的文件(只改动有变动的文件)</span></div><div class="line">gulp.task(<span class="string">"copyChange"</span>, () => {</div><div class="line"> <span class="keyword">return</span> gulp</div><div class="line"> .src(copyPath, option)</div><div class="line"> .pipe(changed(dist))</div><div class="line"> .pipe(gulp.dest(dist));</div><div class="line">});</div></pre></td></tr></table></figure><h3 id="文件编译"><a href="#文件编译" class="headerlink" title="文件编译"></a>文件编译</h3><p>我们想要用高级语法,想要写<code>async/await</code>,想要用<code>less</code>来写样式,想要用<code>typescript</code>来写代码,则需要针对每种文件做编译。</p><p>这里用<code>ts</code>来举例:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> ts = <span class="built_in">require</span>(<span class="string">"gulp-typescript"</span>);</div><div class="line"><span class="keyword">var</span> tsProject = ts.createProject(<span class="string">"tsconfig.json"</span>);</div><div class="line"><span class="keyword">var</span> sourcemaps = <span class="built_in">require</span>(<span class="string">"gulp-sourcemaps"</span>);</div><div class="line"><span class="keyword">var</span> tsPath = [<span class="string">"src/**/*.ts"</span>, <span class="string">"src/app.ts"</span>]; <span class="comment">// 定义ts文件</span></div><div class="line"><span class="comment">// 编译</span></div><div class="line">gulp.task(<span class="string">"tsCompile"</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">return</span> tsProject</div><div class="line"> .src(tsPath)</div><div class="line"> .pipe(sourcemaps.init())</div><div class="line"> .pipe(tsProject())</div><div class="line"> .js.pipe(sourcemaps.write()) <span class="comment">// 添加sourcemap</span></div><div class="line"> .pipe(gulp.dest(<span class="string">"dist"</span>)); <span class="comment">// 最终输出到dist目录对应的位置</span></div><div class="line">});</div></pre></td></tr></table></figure><p>当然,用到 typescript 的话,也记得把<code>tsconfig.json</code>和<code>tslint.json</code>加上哇。</p><h3 id="watch-任务"><a href="#watch-任务" class="headerlink" title="watch 任务"></a>watch 任务</h3><p>在我们写代码的时候,就需要监听文件变动并自动复制、编译和更新,这时候我们就需要 watch 任务:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//监听</span></div><div class="line">gulp.task(<span class="string">"watch"</span>, () => {</div><div class="line"> gulp.watch(tsPath, gulp.series(<span class="string">"tsCompile"</span>)); <span class="comment">// ts编译</span></div><div class="line"> <span class="keyword">var</span> watcher = gulp.watch(copyPath, gulp.series(<span class="string">"copyChange"</span>)); <span class="comment">// 复制任务</span></div><div class="line"> gulp.watch(watchLessPath, gulp.series(<span class="string">"less"</span>)); <span class="comment">// less处理</span></div><div class="line"> gulp.watch(imgPath, gulp.series(<span class="string">"imgChange"</span>)); <span class="comment">// 图片处理</span></div><div class="line"> watcher.on(<span class="string">"change"</span>, <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>{</div><div class="line"> <span class="comment">// 删除的时候,也更新删除任务到目标文件夹</span></div><div class="line"> <span class="keyword">if</span> (event.type === <span class="string">"deleted"</span>) {</div><div class="line"> <span class="keyword">var</span> filepath = event.path;</div><div class="line"> <span class="keyword">var</span> filePathFromSrc = path.relative(path.resolve(<span class="string">"src"</span>), filepath);</div><div class="line"> <span class="comment">// Concatenating the 'build' absolute path used by gulp.dest in the scripts task</span></div><div class="line"> <span class="keyword">var</span> destFilePath = path.resolve(<span class="string">"dist"</span>, filePathFromSrc);</div><div class="line"> del.sync(destFilePath);</div><div class="line"> }</div><div class="line"> });</div><div class="line">});</div></pre></td></tr></table></figure><h3 id="最终任务"><a href="#最终任务" class="headerlink" title="最终任务"></a>最终任务</h3><p>最后,我们需要把这些任务一个个拼起来,最终对外输出两种:<code>dev</code>和<code>build</code>一般就够了:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// dev && watch</span></div><div class="line">gulp.task(</div><div class="line"> <span class="string">"default"</span>,</div><div class="line"> gulp.series(</div><div class="line"> <span class="comment">// sync</span></div><div class="line"> gulp.parallel(<span class="string">"copy"</span>, <span class="string">"img"</span>, <span class="string">"less"</span>, <span class="string">"tsCompile"</span>),</div><div class="line"> <span class="string">"watch"</span></div><div class="line"> )</div><div class="line">);</div><div class="line"></div><div class="line"><span class="comment">// build</span></div><div class="line">gulp.task(</div><div class="line"> <span class="string">"build"</span>,</div><div class="line"> gulp.series( <span class="comment">// 串行任务</span></div><div class="line"> <span class="comment">// sync</span></div><div class="line"> <span class="string">"clear"</span>,</div><div class="line"> gulp.parallel( <span class="comment">// 并行任务</span></div><div class="line"> <span class="comment">// async</span></div><div class="line"> <span class="string">"copy"</span>,</div><div class="line"> <span class="string">"img"</span>,</div><div class="line"> <span class="string">"less"</span>,</div><div class="line"> <span class="string">"tsCompile"</span></div><div class="line"> )</div><div class="line"> )</div><div class="line">);</div></pre></td></tr></table></figure><h3 id="项目目录结构"><a href="#项目目录结构" class="headerlink" title="项目目录结构"></a>项目目录结构</h3><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line">├─dist //编译之后的项目文件(带 sorcemap,支持生产环境告警定位)</div><div class="line">├─src //开发目录</div><div class="line">│ │ app.ts //小程序起始文件</div><div class="line">│ │ app.json</div><div class="line">│ │ app.less</div><div class="line">│ │</div><div class="line">│ ├─assets //静态资源</div><div class="line">│ ├─less//公共less</div><div class="line">│ ├─img //图片资源</div><div class="line">│ ├─components //组件</div><div class="line">│ ├─utils //工具库</div><div class="line">│ ├─config //配置文档</div><div class="line">│ ├─pages //小程序相关页面</div><div class="line">│</div><div class="line">│ project.config.json //小程序配置文件</div><div class="line">│ gulpfile.js //工具配置</div><div class="line">│ package.json //项目配置</div><div class="line">│ README.<span class="built_in">md</span> //项目说明</div><div class="line">│ tsconfig.json //typescript配置</div><div class="line">│ tslint.json //代码风格配置</div></pre></td></tr></table></figure><p>最终效果,可以参考<a href="https://github.com/godbasin/wxapp-typescript-demo" target="_blank" rel="external">wxapp-typescript-demo</a>。</p><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>其实小程序也有人出了框架,像 mpvue 和 wepy,开发风格类似 Vue。<br>不过个人的想法不一样,小程序开发和浏览器开发不一样,小程序官方的 API 会一直不停地进化和完善。如果再使用二次封装的框架,框架是否能跟上小程序 API 的更新节奏,二次封装带来更多的学习成本,这些都需要考虑的。或许有一天,框架的能力优势,最终会被小程序自身取代呢。<br>而简单的构建任务,却可以很棒地使用到 ES6/ES7、Less、Typescript 这些好用的语法和工具呢。</p>]]></content>
<summary type="html">
<p>虽然 webpack 用的比较多,不过在小程序这种场景下,简单的 gulp 也是个不错的选择吧~<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序的奇技淫巧之 watch 观察属性</title>
<link href="https://godbasin.github.io/2018/12/26/wxapp-watch/"/>
<id>https://godbasin.github.io/2018/12/26/wxapp-watch/</id>
<published>2018-12-26T15:45:10.000Z</published>
<updated>2018-12-26T15:46:14.638Z</updated>
<content type="html"><![CDATA[<p>上一节我们介绍了小程序的 computed 计算属性,这次我们来讲讲 watch 观察属性叭~<br><a id="more"></a></p><h2 id="watch-观察属性使用"><a href="#watch-观察属性使用" class="headerlink" title="watch 观察属性使用"></a>watch 观察属性使用</h2><hr><p>这里我们直接先讲解下怎么用哇。</p><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><p>整个实现的依赖包,放在 Github 上,大家可以去翻看和点星星:<a href="https://github.com/godbasin/watch-behavior" target="_blank" rel="external">watch-behavior</a>。</p><ul><li>安装<code>watch</code>:</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">npm <span class="keyword">install</span> <span class="comment">--save miniprogram-watch</span></div></pre></td></tr></table></figure><blockquote><p>使用 behavior 需要依赖小程序基础库 2.2.3 以上版本,同时依赖开发者工具的 npm 构建。具体详情可查阅<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html" target="_blank" rel="external">官方 npm 文档</a>。</p></blockquote><h3 id="在-Component-中使用"><a href="#在-Component-中使用" class="headerlink" title="在 Component 中使用"></a>在 Component 中使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 需要开发者工具 npm 依赖</span></div><div class="line"><span class="keyword">const</span> watchBehavior = <span class="built_in">require</span>(<span class="string">"miniprogram-watch"</span>);</div><div class="line"></div><div class="line">Component({</div><div class="line"> <span class="attr">behaviors</span>: [watchBehavior],</div><div class="line"> <span class="attr">properties</span>: {</div><div class="line"> <span class="attr">propA</span>: {</div><div class="line"> <span class="attr">type</span>: <span class="built_in">Number</span>,</div><div class="line"> <span class="attr">value</span>: <span class="number">0</span></div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="attr">data</span>: {</div><div class="line"> <span class="attr">a</span>: <span class="number">0</span>,</div><div class="line"> <span class="attr">b</span>: {</div><div class="line"> <span class="attr">c</span>: {</div><div class="line"> <span class="attr">d</span>: <span class="number">33</span></div><div class="line"> },</div><div class="line"> <span class="attr">e</span>: [<span class="number">1</span>, <span class="number">2</span>, [<span class="number">3</span>, <span class="number">4</span>]]</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="comment">// 可以将需要监听的数据放入 watch 里面,当数据改变时推送相应的订阅事件</span></div><div class="line"> <span class="comment">// 支持 data 以及 properties 的监听</span></div><div class="line"> watch: {</div><div class="line"> propA(val, oldVal) {</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"propA new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> },</div><div class="line"> a(val, oldVal) {</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"a new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> },</div><div class="line"> <span class="string">"b.c.d"</span>: <span class="function"><span class="keyword">function</span>(<span class="params">val, oldVal</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"b.c.d new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> },</div><div class="line"> <span class="string">"b.e[2][0]"</span>: <span class="function"><span class="keyword">function</span>(<span class="params">val, oldVal</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"b.e[2][0] new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> },</div><div class="line"> <span class="string">"b.e[3][4]"</span>: <span class="function"><span class="keyword">function</span>(<span class="params">val, oldVal</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"b.e[3][4] new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="attr">methods</span>: {</div><div class="line"> onTap() {</div><div class="line"> <span class="keyword">this</span>.setData({</div><div class="line"> <span class="attr">a</span>: <span class="number">2</span>,</div><div class="line"> <span class="string">"b.c.d"</span>: <span class="number">3</span>,</div><div class="line"> <span class="string">"b.e[2][0]"</span>: <span class="number">444</span>,</div><div class="line"> <span class="attr">c</span>: <span class="number">123</span></div><div class="line"> });</div><div class="line"> <span class="comment">// 不在 data 里面的数据项不会放入观察者列表,比如这里的'b.e[3][4]'</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure><h3 id="在-Page-中使用"><a href="#在-Page-中使用" class="headerlink" title="在 Page 中使用"></a>在 Page 中使用</h3><p><strong><code>Component</code>是<code>Page</code>的超集,因此可以使用<code>Component</code>构造器构造页面。</strong></p><p><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html" target="_blank" rel="external">官方文档</a>:</p><blockquote><p>事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用<code>Component</code>构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应<code>json</code>文件中包含<code>usingComponents</code>定义段。</p></blockquote><ul><li><code>page.json</code></li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="attr">"usingComponents"</span>: {}</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>page.js</code></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 这里我们就可以使用 Component 代替 Page</span></div><div class="line">Component({</div><div class="line"> <span class="attr">data</span>: {</div><div class="line"> <span class="attr">a</span>: <span class="number">0</span>,</div><div class="line"> <span class="attr">b</span>: {</div><div class="line"> <span class="attr">c</span>: {</div><div class="line"> <span class="attr">d</span>: <span class="number">33</span></div><div class="line"> },</div><div class="line"> <span class="attr">e</span>: [<span class="number">1</span>, <span class="number">2</span>, [<span class="number">3</span>, <span class="number">4</span>]]</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="comment">// 可以将需要监听的数据放入 watch 里面,当数据改变时推送相应的订阅事件</span></div><div class="line"> watch: {</div><div class="line"> a(val, oldVal) {</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"a new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> },</div><div class="line"> <span class="string">"b.c.d"</span>: <span class="function"><span class="keyword">function</span>(<span class="params">val, oldVal</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"b.c.d new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> },</div><div class="line"> <span class="string">"b.e[2][0]"</span>: <span class="function"><span class="keyword">function</span>(<span class="params">val, oldVal</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"b.e[2][0] new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> },</div><div class="line"> <span class="string">"b.e[3][4]"</span>: <span class="function"><span class="keyword">function</span>(<span class="params">val, oldVal</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"b.e[3][4] new: %s, old: %s"</span>, val, oldVal);</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="attr">methods</span>: {</div><div class="line"> <span class="comment">// 页面的生命周期方法(即`on`开头的方法,如上面的`onLoad`),应写在`methods`定义段中。</span></div><div class="line"> onLoad() {</div><div class="line"> <span class="comment">// 如访问页面`/pages/index/index?paramA=123&paramB=xyz`,如果声明有属性(`properties`)`paramA`或`paramB`,则它们会被赋值为`123`或`xyz`</span></div><div class="line"> <span class="keyword">this</span>.data.paramA <span class="comment">// 页面参数 paramA 的值</span></div><div class="line"> <span class="keyword">this</span>.data.paramB <span class="comment">// 页面参数 paramB 的值</span></div><div class="line"> }</div><div class="line"> onTap() {</div><div class="line"> <span class="keyword">this</span>.setData({</div><div class="line"> <span class="attr">a</span>: <span class="number">2</span>,</div><div class="line"> <span class="string">"b.c.d"</span>: <span class="number">3</span>,</div><div class="line"> <span class="string">"b.e[2][0]"</span>: <span class="number">444</span>,</div><div class="line"> <span class="attr">c</span>: <span class="number">123</span></div><div class="line"> });</div><div class="line"> <span class="comment">// 不在 data 里面的数据项不会放入观察者列表,比如这里的'b.e[3][4]'</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure><p>更多的,也可以参考本人的<a href="https://github.com/godbasin/wxapp-typescript-demo" target="_blank" rel="external">wxapp-typescript-demo</a>中的<a href="https://github.com/godbasin/wxapp-typescript-demo/tree/master/src/pages/watch" target="_blank" rel="external">watch page</a>,后续也会持续更新方便好用的能力 demo。</p><h2 id="watch-观察属性实现"><a href="#watch-观察属性实现" class="headerlink" title="watch 观察属性实现"></a>watch 观察属性实现</h2><hr><p>自定义组件中<code>computed</code>计算属性的实现,由<a href="https://github.com/wechat-miniprogram/computed" target="_blank" rel="external">官方</a>提供的。上一篇<a href="https://godbasin.github.io/2018/12/23/wxapp-computed/">《小程序的奇技淫巧之 computed 计算属性》</a>中,也有讲解大致思路和使用方法。</p><p>现在,轮到我们自己来实现一个<code>watch</code>观察属性了。</p><p>这里的实现主要也是针对自定义组件中的<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html" target="_blank" rel="external"><code>behaviors</code></a>。上一篇已经讲过,这里就不再复述啦。</p><h3 id="watch-触发机制"><a href="#watch-触发机制" class="headerlink" title="watch 触发机制"></a>watch 触发机制</h3><p>其实<code>watch</code>的触发机制,基本都在<code>setData</code>的时候触发。而在自定义组件里,会有两种情况需要需要触发对应的<code>watch</code>监听:</p><ul><li><code>properties</code>属性变化时</li><li><code>data</code>属性变化时(调用<code>setData</code>)</li></ul><h3 id="watch-监听更新机制"><a href="#watch-监听更新机制" class="headerlink" title="watch 监听更新机制"></a>watch 监听更新机制</h3><p>既然<code>properties</code>和<code>data</code>都需要监听,我们来整理下逻辑。大致流程如下:</p><ol><li>在组件初始化的时候,将对应的<code>watch</code>路径加进观察队列<code>observers</code>。</li><li>在<code>properties</code>和<code>data</code>属性变更时,触发更新。<ul><li><code>properties</code>可根据<code>observer</code>触发更新</li><li><code>data</code>可根据<code>setData</code>触发更新</li></ul></li><li>更新时,先对比变更路径,然后根据路径是否匹配(即<code>observers</code>是否存在对应观察者),来确定是否需要通知相应的观察者。</li><li>确定存在变更路径,则对比新数据与旧数据是否一致,一致则拦截不做通知。</li><li>因为<code>watch</code>可能存在循环触发更新,对一次更新的最大通知次数做限制(这里限制5次)。</li></ol><p>具体的实现可以在<a href="https://github.com/godbasin/watch-behavior" target="_blank" rel="external">watch</a>中找到。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html" target="_blank" rel="external">Component构造器</a></li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html" target="_blank" rel="external">behaviors</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>Npm 包的发布,是一件简单也挺复杂的事情。简单的话,你可以发布一个单文件,不带任何构建等。复杂的时候,你需要写好demo、test、构建环境等等。<br>有些时候,自己亲自参与做一下,会让你更容易理解,这也是为什么我们偶尔需要造点轮子的原因。</p>]]></content>
<summary type="html">
<p>上一节我们介绍了小程序的 computed 计算属性,这次我们来讲讲 watch 观察属性叭~<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序的奇技淫巧之 computed 计算属性</title>
<link href="https://godbasin.github.io/2018/12/23/wxapp-computed/"/>
<id>https://godbasin.github.io/2018/12/23/wxapp-computed/</id>
<published>2018-12-23T09:11:39.000Z</published>
<updated>2018-12-23T09:11:54.451Z</updated>
<content type="html"><![CDATA[<p>小程序的出身,基于安全和管控的考虑,使用了双线程的设计,同时对于 DOM 操作、动态创建 DOM 这些都隔离了。在写代码的时候,模版语法不支持函数计算等,computed 的方法就显得十分重要了。<br><a id="more"></a></p><h2 id="自定义组件"><a href="#自定义组件" class="headerlink" title="自定义组件"></a>自定义组件</h2><hr><p>小程序的自定义组件涉及功能很多,这篇只针对<code>computed</code>展开来讲。</p><p><code>computed</code>比较适合较复杂逻辑的计算,同时在小程序无法在模板里使用<code>methods</code>这样的场景下,计算属性的需求就更强烈了。</p><h3 id="behaviors"><a href="#behaviors" class="headerlink" title="behaviors"></a>behaviors</h3><p>自定义组件中,提供了<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html" target="_blank" rel="external"><code>behaviors</code>的使用和定义</a>。</p><p>从官方文档我们能看到:</p><blockquote><p><code>behaviors</code>是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。<br>每个<code>behavior</code>可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个<code>behavior</code>。</p></blockquote><p>简单来说,我们能通过<code>behaviors</code>来重构<code>Component</code>的能力。</p><p>如果说,我们能“混入”<code>Component</code>,其实基本很多能力都能实现啦。其实我们自己封装一层的<code>MyComponent</code>也能达到一定的效果,但是这样的拓展性会变得很糟。</p><p>通过<code>behaviors</code>的方式,每个组件可以按需引入自己需要的<code>behavior</code>啦。</p><h3 id="computed-实现"><a href="#computed-实现" class="headerlink" title="computed 实现"></a>computed 实现</h3><p>我们来梳理下这里的逻辑,我们需要一个<code>computed</code>能力,需要处理的主要是:<code>setData</code>的时候,根据<code>computed</code>来计算哪些数据需要处理。</p><p>所以我们要做的是:</p><ol><li>记下来需要<code>computed</code>的变量。</li><li>在每次<code>setData</code>之前,看看是否包含到需要<code>computed</code>的变量,匹配到了就进行<code>computed</code>处理。</li><li>使用处理后的数据,进行<code>setData</code>。</li></ol><p>官方已经提供了<a href="https://github.com/wechat-miniprogram/computed" target="_blank" rel="external">计算属性实现的behavior</a>,大家也可以尽情翻看实现的<a href="https://github.com/wechat-miniprogram/computed/blob/master/src/index.js" target="_blank" rel="external">源码</a>,和使用这种拓展能力。</p><h2 id="Page-的超集"><a href="#Page-的超集" class="headerlink" title="Page 的超集"></a>Page 的超集</h2><hr><h3 id="hack-实现-Page-computed-能力"><a href="#hack-实现-Page-computed-能力" class="headerlink" title="hack 实现 Page computed 能力"></a>hack 实现 Page computed 能力</h3><p>想必大家都会有疑惑,<code>Component</code>里支持<code>behaviors</code>,但是<code>Page</code>依然写起来很不方便呀。虽然所有的<code>Page</code>最终也能通过<code>Component</code>来实现,但是这样是否需要多包装一层呢?</p><p>答案是不用。</p><h3 id="使用-Component-构造器构造页面"><a href="#使用-Component-构造器构造页面" class="headerlink" title="使用 Component 构造器构造页面"></a>使用 Component 构造器构造页面</h3><p><code>Component</code>是<code>Page</code>的超集,因此可以使用<code>Component</code>构造器构造页面。</p><p>同样的,我们来看看<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html" target="_blank" rel="external">官方文档</a>:</p><blockquote><p>事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用<code>Component</code>构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应<code>json</code>文件中包含<code>usingComponents</code>定义段。</p></blockquote><p>也就是说,我们这样的页面:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line">Page({</div><div class="line"> <span class="attr">data</span>: {</div><div class="line"> <span class="attr">logs</span>: []</div><div class="line"> },</div><div class="line"> onLoad(query) {</div><div class="line"> <span class="comment">// 如访问页面`/pages/index/index?paramA=123&paramB=xyz`,如果声明有属性(`properties`)`paramA`或`paramB`,则它们会被赋值为`123`或`xyz`</span></div><div class="line"> query.paramA <span class="comment">// 页面参数 paramA 的值</span></div><div class="line"> query.paramA <span class="comment">// 页面参数 paramB 的值</span></div><div class="line"> <span class="keyword">this</span>.setData({</div><div class="line"> <span class="attr">logs</span>: <span class="function">(<span class="params">wx.getStorageSync(<span class="string">"logs"</span></span>) || []).<span class="params">map</span>(<span class="params">(log: number</span>) =></span> {</div><div class="line"> <span class="keyword">return</span> formatTime(<span class="keyword">new</span> <span class="built_in">Date</span>(log));</div><div class="line"> })</div><div class="line"> });</div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure><p>可以这么写:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="attr">"usingComponents"</span>: {}</div><div class="line">}</div></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line">Component({</div><div class="line"> <span class="comment">// 组件的属性可以用于接收页面的参数</span></div><div class="line"> properties: {</div><div class="line"> <span class="attr">paramA</span>: <span class="built_in">Number</span>,</div><div class="line"> <span class="attr">paramB</span>: <span class="built_in">String</span>,</div><div class="line"> },</div><div class="line"> <span class="attr">data</span>: {</div><div class="line"> <span class="attr">logs</span>: []</div><div class="line"> },</div><div class="line"> <span class="attr">methods</span>: {</div><div class="line"> onLoad() {</div><div class="line"> <span class="comment">// 如访问页面`/pages/index/index?paramA=123&paramB=xyz`,如果声明有属性(`properties`)`paramA`或`paramB`,则它们会被赋值为`123`或`xyz`</span></div><div class="line"> <span class="keyword">this</span>.data.paramA <span class="comment">// 页面参数 paramA 的值</span></div><div class="line"> <span class="keyword">this</span>.data.paramB <span class="comment">// 页面参数 paramB 的值</span></div><div class="line"> <span class="keyword">this</span>.setData({</div><div class="line"> <span class="attr">logs</span>: <span class="function">(<span class="params">wx.getStorageSync(<span class="string">"logs"</span></span>) || []).<span class="params">map</span>(<span class="params">(log: number</span>) =></span> {</div><div class="line"> <span class="keyword">return</span> formatTime(<span class="keyword">new</span> <span class="built_in">Date</span>(log));</div><div class="line"> })</div><div class="line"> });</div><div class="line"> }</div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure><p>这样,我们就能愉快地使用<code>behaviors</code>啦。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> computedBehavior = <span class="built_in">require</span>(<span class="string">"miniprogram-computed"</span>);</div><div class="line">Component({</div><div class="line"> <span class="attr">behaviors</span>: [computedBehavior],</div><div class="line"> <span class="attr">data</span>: {</div><div class="line"> <span class="attr">logs</span>: []</div><div class="line"> },</div><div class="line"> <span class="attr">computed</span>: {</div><div class="line"> logsAfterComputed() {</div><div class="line"> <span class="comment">// 计算属性同样挂在 data 上,每当进行 setData 的时候会重新计算</span></div><div class="line"> <span class="comment">// 比如此字段可以通过 this.data.b 获取到</span></div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.data.logs.map(<span class="function"><span class="params">x</span> =></span> {</div><div class="line"> <span class="keyword">return</span> {</div><div class="line"> <span class="attr">log</span>: x,</div><div class="line"> <span class="attr">logAfterCompute</span>: x + <span class="string">"logAfterCompute"</span></div><div class="line"> };</div><div class="line"> });</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="attr">methods</span>: {</div><div class="line"> onLoad() {</div><div class="line"> <span class="keyword">this</span>.setData({</div><div class="line"> <span class="attr">logs</span>: <span class="function">(<span class="params">wx.getStorageSync(<span class="string">"logs"</span></span>) || []).<span class="params">map</span>(<span class="params">(log: number</span>) =></span> {</div><div class="line"> <span class="keyword">return</span> formatTime(<span class="keyword">new</span> <span class="built_in">Date</span>(log));</div><div class="line"> })</div><div class="line"> });</div><div class="line"> }</div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure><p>使用<code>Component</code>构造器构造页面,需要注意:</p><ol><li>组件的属性可以用于接收页面的参数,如访问页面<code>/pages/index/index?paramA=123&paramB=xyz</code>,如果声明有属性(<code>properties</code>)<code>paramA</code>或<code>paramB</code>,则它们会被赋值为<code>123</code>或<code>xyz</code>。(可参考<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html" target="_blank" rel="external">官方代码示例</a>)</li><li>页面的生命周期方法(即<code>on</code>开头的方法,如上面的<code>onLoad</code>),应写在<code>methods</code>定义段中。</li></ol><p>这样,你就能愉快地在代码里面使用<code>computed</code>计算属性啦~</p><p>更多的,也可以参考本人的<a href="https://github.com/godbasin/wxapp-typescript-demo" target="_blank" rel="external">wxapp-typescript-demo</a>中的<a href="https://github.com/godbasin/wxapp-typescript-demo/tree/master/src/pages/logs" target="_blank" rel="external">log page</a>,后续也会持续更新方便好用的能力 demo。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html" target="_blank" rel="external">Component构造器</a></li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html" target="_blank" rel="external">behaviors</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>小程序提供的能力其实挺多的,但是很多时候由于文档很多、查找不方便,会导致我们有些很好用的功能没有发现,然后苦逼地一边吐槽一边悲壮地撸代码。<br>官方提供的目前只有<code>computed</code>,大家可以看看,是不是还可以做<code>watch</code>之类的能力呢?</p>]]></content>
<summary type="html">
<p>小程序的出身,基于安全和管控的考虑,使用了双线程的设计,同时对于 DOM 操作、动态创建 DOM 这些都隔离了。在写代码的时候,模版语法不支持函数计算等,computed 的方法就显得十分重要了。<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>选择这件事</title>
<link href="https://godbasin.github.io/2018/12/16/work-1-choice/"/>
<id>https://godbasin.github.io/2018/12/16/work-1-choice/</id>
<published>2018-12-16T06:10:54.000Z</published>
<updated>2018-12-16T06:11:00.006Z</updated>
<content type="html"><![CDATA[<p>工作中,并不只有技术,还有各式各样的“其他挑战”。关于这些事情,简单地谈谈吧。<br><a id="more"></a></p><h2 id="有关加班"><a href="#有关加班" class="headerlink" title="有关加班"></a>有关加班</h2><p>其实个人并不反感加班,项目紧急,突发状况,都是人之常情。团队偶尔进行的突破和挑战,其实有些时候也会有美好的回忆的。</p><p>只是,这个社会很多时候,会理所当然地将高效当作不饱和。</p><p>为什么如今加班严重?大家真的那么忙吗?甚至愈演愈烈,夜里、周末,也会各种被找,即使并不是特别紧急的事情。</p><p>逃得掉吗?</p><p>踮脚效应。一群人站着围观某个东西,如果前面的人为了看多一点点踮起脚,那么后面的人也得踮起脚,后面的后面也得跟着踮起脚,最后所有人都得踮起脚受累,这就是踮脚效应。</p><p>因为在刚开始的时候,总有人愿意去踮起脚来,来获取更多的视野。所以很多时候,我们是逃不掉的。</p><h2 id="有关硝烟"><a href="#有关硝烟" class="headerlink" title="有关硝烟"></a>有关硝烟</h2><p>作为一个转行的非科班程序员,也转过好几份工作了。每个团队多多少少都会有一些问题,但一般来说都无伤大雅。</p><p>换工作的原因也很简单,无非就是更多的机会、更好的待遇、更合心意的团队。</p><p>刚开始,我以为程序员这个职业,作为理工科的一份子,多是耿直真诚的直男。但有些时候并不是这样的,技术上拼不过的,演技可以补齐。</p><p>智商情商几乎都被说烂了,但工作里基本不会遇到拼智商的情况,而所谓的情商,大多数只剩下了你情我愿。</p><p>有人爱做试探性的行为,去试探身边人的底线。点到为止,不可能的。本该纯粹的技术工作,有人的地方,就有了利益冲突,便有了斗争。人越是多的地方,就越容易出现争执,也越来越多人跟随着利益既得者的步伐走。</p><p>好些人都跟我说,职场就是这样的,你也稍稍改变下,适应了就好了。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20180109225558.jpg" alt="这个世界,就是这样子的啦"></p><p>我以为是说话方式的问题,也曾有一段时间,各种找人请教是否可以用更好的方式去表达。但有时候并不是这样的,和说话方式没有关系,你有再多的想法,也只能咽肚子里,把别人的想法做出来才是政治正确。</p><p>战争已经开始了,我说我只想粗茶淡饭认真搬砖,却被告之不允许,你必须带上你的面具参战。</p><h2 id="有关选择"><a href="#有关选择" class="headerlink" title="有关选择"></a>有关选择</h2><p>选择,这是我认为最重要的一点。工作和我的关系,是双向选择的关系,直到现在我还是这么坚持的。</p><p>找一份工作就像谈一场恋爱,没有谁对谁错,只是我们并不适合而已。</p><p>但很多人并不这么想的。拿人钱财替人消灾,很神奇的是,本该是你情我愿的事情,总有人爱戴上帽子,“你过来就是替我干活的,我说啥你做啥就好”、“我给了你一份工作,你应该懂得感恩”,这样奇奇怪怪的想法也都遇到过。</p><p>或许有些时候,我并不知道自己真正想要的是什么,但是我很清楚,我不想要什么。颠簸流离的日子不是没有经历过,而我唯一不想要的,就是活丢了自己。</p><p>很多人都爱说,“没办法,我也不想这样”,“老板要求这样,我没得选”。</p><p>但是你要知道,没有选择,它也是一种选择。</p><p>并没有世界末日,也没有刀架在脖子上,所谓的没有选择,其实只是你在各种利益衡量过后的一种选择。我不讨厌爱表现的人,也不讨厌做了没法选这种选择的人,大家都是成年人,有能力自己承担选择所带来的后果就足够了。</p><p>我只是不喜欢那种,把责任都推到环境、工作、上司和别人身上,不敢直面自己的行为而已。我也只是不喜欢那种,自己做了选择一定要把别人拉下水,否则便指责你为何不的行为而已。</p><p>每一个伟大的人,他们都有过不被理解的时候、被各种评价甚至指责的时候,而他们的成功在于,他们坚持下来了。而如今你或者我处在这样的环境里,为何一定要改呢?</p><p>这个世界这么大,你选择相信现实是很残酷的,我选择相信生活是很美好的。</p><p>人生,不过选择而已。</p><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>算下来,工作四年了,代码也写了该有三年多了。</p><p>第一次在技术博客写技术无关的内容,但工作其实也是很重要的一部分吧。工作相关而技术无关的内容,后续可能也会继续来写,毕竟或许很多人都会有过一样的疑惑。写文的意义不在于教会阅读者道理,而在于唤起阅读者的思考。</p><p>我的工作和生活,界限分明。如果你想要关注非工作非技术状态的我,欢迎关注“爱我爱家爱天下”公众号(改名中,新名字“牧羊的猪”)。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/qrcode_for_gh_0404f6ab1848_258.jpg" alt=""></p>]]></content>
<summary type="html">
<p>工作中,并不只有技术,还有各式各样的“其他挑战”。关于这些事情,简单地谈谈吧。<br>
</summary>
<category term="工作这杯茶" scheme="https://godbasin.github.io/categories/%E5%B7%A5%E4%BD%9C%E8%BF%99%E6%9D%AF%E8%8C%B6/"/>
<category term="心态" scheme="https://godbasin.github.io/tags/%E5%BF%83%E6%80%81/"/>
</entry>
<entry>
<title>小程序上Typescript啦</title>
<link href="https://godbasin.github.io/2018/11/30/wxapp-typescript/"/>
<id>https://godbasin.github.io/2018/11/30/wxapp-typescript/</id>
<published>2018-11-30T14:36:46.000Z</published>
<updated>2018-11-30T14:41:45.901Z</updated>
<content type="html"><![CDATA[<p>[2018.11.14 A 新增 typescript 支持] 小程序开发工具静悄悄地更新了版本,添加上了对 Typescript 的支持。Typescript ??? Typescript !!!<br><a id="more"></a></p><h1 id="期待已久的-Typescript"><a href="#期待已久的-Typescript" class="headerlink" title="期待已久的 Typescript"></a>期待已久的 Typescript</h1><hr><h2 id="为什么要用-Typescript"><a href="#为什么要用-Typescript" class="headerlink" title="为什么要用 Typescript"></a>为什么要用 Typescript</h2><p>关于 Typescript,可以看看以前写过的这篇<a href="https://godbasin.github.io/2017/09/01/about-typescript/">《关于Typescript》</a>。文末的故事,便是大多数情况下 Typescript 能帮我们解决的痛点。</p><p>过了很久之后,想法还是一样:<strong>Typescript 这事情,当你管理大点的应用的时候,就会感受到它的好处了</strong>。尤其涉及团队配合的时候!</p><p>当然,如果你的项目比较小,或是写个小公(工)举(具)、小 demo 的时候, store 状态管理、typescript 编译这些,除非已经很熟悉、没有额外成本的时候,才勉强适合接入。离了具体场景谈架构,都是耍(xia)流(che)氓(dan)。</p><p>为什么要用 Typescript?</p><h3 id="变量类型不明确"><a href="#变量类型不明确" class="headerlink" title="变量类型不明确"></a>变量类型不明确</h3><p>之前带外包写小程序,除了代码风格不一致之外,还遇到一个会变的变量问题。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> formGroups = <span class="keyword">this</span>.currentStep.formGroups; <span class="comment">// 猜猜我的 formGroups 是数组数组 [[], [], []],还是对象数组 [{}, {}, {}]?</span></div><div class="line"><span class="keyword">let</span> flattenFields = _.flatten(formGroups); <span class="comment">// 不用猜了,我用个 flatten 抹平,它就一定是对象 [{}, {}, {}] 了!</span></div><div class="line"></div><div class="line">flattenFields.forEach(<span class="function"><span class="params">item</span> =></span> {</div><div class="line"> <span class="keyword">if</span> (item.fields) { <span class="comment">// 猜猜我的 item.fields 是数组还是对象?</span></div><div class="line"> flattenFields.push(..._.values(item.fields)); <span class="comment">// 不用猜了,我用个 values 抹平,它就一定是对象了!</span></div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure><p>当我帮忙 debug 个问题的时候,打断点看到:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-1.png" alt="这好像是个数组?"><br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-2.png" alt="这怎么变成个对象???"></p><p>喵喵喵???</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"># 我和外包童鞋的对话:</div><div class="line">我:话说你这些到底是什么类型,从命名和上下文都看不出来。。</div><div class="line">我:得去翻更细的代码。。</div><div class="line"></div><div class="line">外包童鞋:values好像可以改一下试试</div><div class="line"></div><div class="line">我:是数组还是对象?</div><div class="line"></div><div class="line">外包童鞋:有的是数组,有的是对象</div><div class="line">外包童鞋:一般带复数的是数组</div><div class="line"></div><div class="line">我:(刀.jpg)</div><div class="line">我:卧槽</div><div class="line">我:你这item.fields,有时候是数组,有时候是对象,这样真的好吗</div><div class="line">我:大哥</div><div class="line">我:(刀.jpg)* <span class="number">2</span></div></pre></td></tr></table></figure><p>或许有人想,即使上了 Typescript 也可能会被 any 打败啊。</p><p>什么?在我眼皮底下用 any?!!</p><h3 id="接口协议不符合"><a href="#接口协议不符合" class="headerlink" title="接口协议不符合"></a>接口协议不符合</h3><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">前端:帮忙看看这个接口为什么返回失败了?</div><div class="line">后台:你这个接口字段少了啊,这个xxx</div><div class="line">(哼哧哼哧修改)</div><div class="line">前端:帮忙看看这个接口为啥又报错了啊?</div><div class="line">后台:你这个字段类型不对...我协议里有写的</div><div class="line">前端:喔不好意思我改</div><div class="line">(哼哧哼哧修改)</div><div class="line">前端:(泪光)帮忙看看这个接口为啥还报错?</div><div class="line">后台:...你这字段名拼错了啊!!!!</div></pre></td></tr></table></figure><p>当然,这个案例里稍微夸张了一点,一般我们都会自己一个个对着协议检查哪里不对,但是很多时候被 bug 光环环绕的时候,你就是发现不了问题。</p><p>这个时候,我们就可以用 Typescript 来管理接口啦。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">interface</span> IDemoResponse {</div><div class="line"> date: <span class="built_in">string</span>;</div><div class="line"> someNumber: <span class="built_in">number</span>;</div><div class="line"> otherThing: <span class="built_in">any</span>;</div><div class="line">}</div></pre></td></tr></table></figure><p><strong>1. 使用约定的变量的时候,会有相关提示</strong>(请忽略我的强行any)。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-10.jpg" alt="输入提示"></p><p><strong>2. 使用约定以外的属性时候,会报错提示。</strong></p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-11.jpg" alt="错误提示"></p><p>除此以外,还有很多很棒的用法呢~</p><h3 id="一键调整协议"><a href="#一键调整协议" class="headerlink" title="一键调整协议"></a>一键调整协议</h3><p>前端和后台协议约定后,就开始各自开发了。但是,我们总会遇到各种各样的问题,可能导致我们的协议变更。</p><p>字段的变更什么的最讨厌了,例如后台要把某个接口下<code>date</code>改成<code>day</code>。一般来说前端是拒绝的,你不能说让我改我就得改,我得看看我写了多少代码,评估下工作量。</p><p>什么,全局替换?你知道使用<code>date</code>多普遍吗?万一我替换错了咋办??</p><p>这时候,如果你使用了 Typescript 并定义了协议接口的话,就很好办了~</p><p>依然是这段代码:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">interface</span> IDemoResponse {</div><div class="line"> date: <span class="built_in">string</span>;</div><div class="line"> someNumber: <span class="built_in">number</span>;</div><div class="line"> otherThing: <span class="built_in">any</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> demoResponse: IDemoResponse = {} as <span class="built_in">any</span>;</div><div class="line"><span class="keyword">const</span> date = demoResponse.date;</div></pre></td></tr></table></figure><p><strong>1. 选中需要重命名的属性。</strong></p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-12.jpg" alt="选中需要重命名的属性"></p><p><strong>2. 按下F2,重新输入属性名。</strong></p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-13.jpg" alt="选中需要重命名的属性"></p><p><strong>3. 按下回车,使用到的地方都会更新。</strong></p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-14.jpg" alt="属性更新"></p><p>是不是很酷~~~</p><h3 id="跨过-Babel-直接使用-ES6-ES7,跨过-eslint-直接使用-prettier"><a href="#跨过-Babel-直接使用-ES6-ES7,跨过-eslint-直接使用-prettier" class="headerlink" title="跨过 Babel 直接使用 ES6/ES7,跨过 eslint 直接使用 prettier"></a>跨过 Babel 直接使用 ES6/ES7,跨过 eslint 直接使用 prettier</h3><p>其实小程序工具本身也支持了不少的 ES6 新语法,不过像<code>async/await</code>这种,则还是需要自己搞个 Babel 来编译。</p><p>现在直接上 Typescript,连 Babel 都可以直接跳过啦。</p><p><strong>Prettier</strong></p><p>这里重点推荐 <a href="https://prettier.io/" target="_blank" rel="external">prettier</a> 神器,也是团队配合的好工具啊:</p><ul><li>项目代码没有配 eslint?导致每次拉下来的代码一大堆冲突?</li><li>团队成员使用不同的编辑器?有的没有自动格式化?导致拉下来代码还是一堆冲突?</li><li>用 standard?有些规范和实际项目不符合,但是偏偏没得改??</li></ul><p>偷偷地往项目里装个 Prettier,然后所有的矛盾都不见啦。不管你的代码格式多独特,最终在 Git commit 的时候,就被同化啦,而且 Prettier 的格式化也不会影响到 Git 记录。</p><h2 id="小程序与-Typescript"><a href="#小程序与-Typescript" class="headerlink" title="小程序与 Typescript"></a>小程序与 Typescript</h2><h3 id="Typescript-编译下就可以用?"><a href="#Typescript-编译下就可以用?" class="headerlink" title="Typescript 编译下就可以用?"></a>Typescript 编译下就可以用?</h3><p>其实小程序它最终运行的还是 Javascript,那不是我们直接自己编译下就好了吗?</p><p>少年你太天真了。咱们写 Typescript 最重要的是什么呀?是 Typing 库呀!</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-3.jpg" alt="没错,就是这货"></p><p>网上开源的关于小程序和 Typescript 的工具或者脚手架也一大堆,为啥不用呢?因为小程序的 API 在不断地变化呀~</p><p>有了官方的支持,即使小程序的 API 变了,我们也可以及时地更新呀(奸笑)~</p><h3 id="开箱即用的尝鲜"><a href="#开箱即用的尝鲜" class="headerlink" title="开箱即用的尝鲜"></a>开箱即用的尝鲜</h3><p>既然官方提供支持了,义不容辞地使用呀!</p><ol><li>首先,我们更新到最近的工具版本,然后创建项目就能看到了:</li></ol><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-4.jpg" alt="看到了没"></p><ol><li>创建模版,我们来看看代码长什么样子。</li></ol><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-5.jpg" alt="嗯,typing 在就足够了"></p><p>我们可以看到,在 package.json 里面多了俩脚本,其实也就是将 ts 文件原地编译,然后上传代码的时候忽略掉了。</p><ol><li>仔细瞧瞧代码。</li></ol><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-6.jpg" alt="emmmmm"></p><p>额,好像混入了一些奇怪的东西进去,感叹号是什么鬼???</p><p>后面问了下开发GG,是因为这里比较特殊,目前定义的文件暂时没法兼顾,等后面的版本会兼容。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-7.jpg" alt="但是还是很棒"></p><p>终于用上 Typescript 啦,爽歪歪~</p><h3 id="调整下代码结构"><a href="#调整下代码结构" class="headerlink" title="调整下代码结构"></a>调整下代码结构</h3><p>小项目的话,其实也不用带什么编译啦。不过如果你还想用 less,也想用 typescript,还不想看到项目下面乱糟糟的文件:<br><figure class="highlight cmd"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">index.js</div><div class="line">index.ts</div><div class="line">index.json</div><div class="line">index.less</div><div class="line">index.wxss</div><div class="line">index.wxml</div></pre></td></tr></table></figure></p><p>我们就简单弄个 gulp,把编译加上吧~</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-8.jpg" alt="会长这样"></p><p>然后我们再把 prettier 愉快地加上。这里就不多讲解啦,大家也可以参考我的 demo 项目:<br><a href="https://github.com/godbasin/wxapp-typescript-demo" target="_blank" rel="external">wxapp-typescript-demo</a></p><p>对了,目前官方的 typing 库也不是非常完善,如果需要写组件、插件、小游戏的你,可能会面临一大堆的 any 冲击波噢~</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/wxapp-typescript-9.jpg" alt="emmmm"></p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html" target="_blank" rel="external">小程序工具更新</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>Typescript 的普及度其实不算高,小程序的确是又一次给到惊喜。反观下我们自己呢?有没有被业务代码冲得找不到方向呢?<br>很多时候,我们总爱说写业务没啥技术提升,但真的是这样吗?我看过很棒的业务代码,从框架设计到具体的实现,开发者都对自己做了很高的要求。而写技术需求代码的,就一定会写得很好吗?<br>“我们是业务部门,技术肯定比不上”<br>“项目很紧急,怎么快怎么来”<br>“随便找一些能用的就好了,不要浪费时间在这些上面”<br>…<br>以上这些话,我是不认同的。当然项目急的时候可以理解,事后一定要把欠下的债务给还了。(较真脸)</p>]]></content>
<summary type="html">
<p>[2018.11.14 A 新增 typescript 支持] 小程序开发工具静悄悄地更新了版本,添加上了对 Typescript 的支持。Typescript ??? Typescript !!!<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序多页面接口数据缓存</title>
<link href="https://godbasin.github.io/2018/11/24/wxapp-multi-request/"/>
<id>https://godbasin.github.io/2018/11/24/wxapp-multi-request/</id>
<published>2018-11-24T11:22:34.000Z</published>
<updated>2018-11-24T11:23:20.860Z</updated>
<content type="html"><![CDATA[<p>小程序里面多个页面,有时候会需要用到同一个接口的数据。而这些数据全局来说只需要拉取一遍,如果要存到缓存,要怎么保证其他页面取缓存的时候,数据已经拉取回来了呢?<br><a id="more"></a></p><h2 id="多页面接口数据缓存实现"><a href="#多页面接口数据缓存实现" class="headerlink" title="多页面接口数据缓存实现"></a>多页面接口数据缓存实现</h2><hr><h3 id="思路设计"><a href="#思路设计" class="headerlink" title="思路设计"></a>思路设计</h3><p>其实这种场景和实现方式,与小程序关系并不大,很多常见的应用开发都会遇到。这次刚好在小程序里用到了,就顺便做下记录。</p><p>在这里,我们假设需要全局拉取一个用户信息。在涉及异步请求中,我们常用的方式是封装成一个<code>Promise</code>:</p><ol><li>方法统一对外返回一个<code>Promise</code>。</li><li>加锁,在请求中不再请求,返回缓存的<code>Promise</code>。</li><li>若已有缓存,则返回一个马上<code>resolve</code>的<code>Promise</code>。</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> isLoading = <span class="literal">false</span>;</div><div class="line"><span class="keyword">let</span> info = <span class="literal">null</span>;</div><div class="line"><span class="keyword">let</span> promise = <span class="literal">null</span>;</div><div class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getInfo</span>(<span class="params">data = {}</span>) </span>{</div><div class="line"> <span class="keyword">if</span> (info) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</div><div class="line"> resolve(info);</div><div class="line"> });</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (isLoading) {</div><div class="line"> <span class="keyword">return</span> promise;</div><div class="line"> }</div><div class="line"> isLoading = <span class="literal">true</span>;</div><div class="line"> <span class="keyword">return</span> <span class="function">(<span class="params">promise = <span class="keyword">new</span> <span class="built_in">Promise</span>((resolve, reject</span>) =></span> {</div><div class="line"> <span class="comment">//登录权限接口</span></div><div class="line"> wx.request({</div><div class="line"> <span class="attr">url</span>: <span class="string">"your_url"</span>,</div><div class="line"> <span class="attr">method</span>: <span class="string">"GET"</span>,</div><div class="line"> data,</div><div class="line"> <span class="attr">success</span>: <span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>{</div><div class="line"> resolve(res)</div><div class="line"> },</div><div class="line"> <span class="attr">fail</span>: <span class="function"><span class="keyword">function</span> (<span class="params">err</span>) </span>{</div><div class="line"> reject(err)</div><div class="line"> }</div><div class="line"> })</div><div class="line"> }));</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="稍作优化"><a href="#稍作优化" class="headerlink" title="稍作优化"></a>稍作优化</h3><p>这种情况下,我们在一个生命周期中都会只请求一次,其他都只会在缓存中获取。我们还可以做些调整:</p><ol><li>将数据写入本地缓存,小程序启用的时候获取。</li><li>提供强制拉取新数据的配置控制,这里用<code>needRefresh</code>参数控制。</li><li>使用上节<a href="https://godbasin.github.io/2018/11/17/wxapp-login/">《小程序的登录与静默续期》</a>封装的<code>request</code>方法来发起请求。</li></ol><p>我们来更新下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> isLoading = <span class="literal">false</span>;</div><div class="line"><span class="keyword">let</span> info = <span class="literal">null</span>;</div><div class="line"><span class="keyword">try</span> {</div><div class="line"> info = wx.getStorageSync(<span class="string">'info'</span>)</div><div class="line">} <span class="keyword">catch</span> (e) {}</div><div class="line"><span class="keyword">let</span> promise = <span class="literal">null</span>;</div><div class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getInfo</span>(<span class="params">data = {}, needRefresh = false</span>) </span>{</div><div class="line"> <span class="keyword">if</span> (info && !needRefresh) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</div><div class="line"> resolve(info);</div><div class="line"> });</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (isLoading) {</div><div class="line"> <span class="keyword">return</span> promise;</div><div class="line"> }</div><div class="line"> isLoading = <span class="literal">true</span>;</div><div class="line"> <span class="keyword">return</span> (promise = request({</div><div class="line"> <span class="attr">url</span>: <span class="string">"your_url"</span>,</div><div class="line"> <span class="attr">method</span>: <span class="string">"GET"</span>,</div><div class="line"> data</div><div class="line"> }));</div><div class="line">}</div></pre></td></tr></table></figure><p><strong>Tips:</strong>前面也提到过,小程序的设计很大程度上考虑了管控力。在这里,为了保证小程序不乱用任意域名的服务,<code>wx.request</code>请求的域名需要在小程序管理平台进行配置,如果小程序正式版使用<code>wx.request</code>请求未配置的域名,在控制台会有相应的报错。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/api/network-request.html#wxrequestobject" target="_blank" rel="external">小程序网络API</a></li><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=000ee27c9c8d98ab0086788fa5b00a" target="_blank" rel="external">《小程序开发指南》</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>前面我们也提到,小程序里面发起请求,都会经过 Native 发起。在应用开发实践中,对一些原理的掌握,很多时候能更多地提升我们解决问题的效率,也能对项目整体有更好的认知。</p>]]></content>
<summary type="html">
<p>小程序里面多个页面,有时候会需要用到同一个接口的数据。而这些数据全局来说只需要拉取一遍,如果要存到缓存,要怎么保证其他页面取缓存的时候,数据已经拉取回来了呢?<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序的登录与静默续期</title>
<link href="https://godbasin.github.io/2018/11/17/wxapp-login/"/>
<id>https://godbasin.github.io/2018/11/17/wxapp-login/</id>
<published>2018-11-17T02:22:58.000Z</published>
<updated>2018-11-24T11:25:30.005Z</updated>
<content type="html"><![CDATA[<p>每一个有数据交互的小程序,都会涉及到登录、token 等问题,openid 又是什么呢?怎么使用静默续期,来提升用户体验呢?<br><a id="more"></a></p><h2 id="小程序登录"><a href="#小程序登录" class="headerlink" title="小程序登录"></a>小程序登录</h2><hr><h3 id="登录时序"><a href="#登录时序" class="headerlink" title="登录时序"></a>登录时序</h3><p>一切的一切,都要从这么一张小程序登录时序图说起:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/%E6%8E%88%E6%9D%83%E6%97%B6%E5%BA%8F%E5%9B%BE.jpg" alt="image"></p><p>通常情况下,我们的小程序都会有业务身份,如何将微信帐号和业务身份关联起来呢?这个时候我们需要上图的步骤:</p><ol><li>小程序调用<code>wx.login()</code>获取临时登录凭证<code>code</code>。</li><li>小程序将<code>code</code>传到开发者服务器。</li><li>开发者服务器以<code>code</code>换取用户唯一标识<code>openid</code>和会话密钥<code>session_key</code>。</li><li>开发者服务器可绑定微信用户身份<code>id</code>和业务用户身份。</li><li>开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。</li></ol><h3 id="相关数据或参数"><a href="#相关数据或参数" class="headerlink" title="相关数据或参数"></a>相关数据或参数</h3><p>上面的登录时序中,我们会涉及到一些数据和参数,先来了解下它们都是用来做啥的。</p><p><strong>临时登录凭证 code</strong><br>在小程序中调用<code>wx.login()</code>,能拿到一个<code>code</code>作为用户登录凭证(有效期五分钟)。在开发者服务器后台,开发者可使用<code>code</code>换取<code>openid</code>和<code>session_key</code>等信息(<code>code</code>只能使用一次)。</p><p><code>code</code>的设计,主要用于防止黑客使用穷举等方式把业务侧个人信息数据全拉走。</p><p><strong>AppId 与 AppSecret</strong><br>为了确保拿<code>code</code>过来换取身份信息的人就是对应的小程序开发者,到微信服务器的请求要同时带上<code>AppId</code>和<code>AppSecret</code>。</p><p><strong>session_key</strong><br>会话密钥<code>session_key</code>是对用户数据进行加密签名的密钥。<strong>为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。</strong></p><p>设计<code>session_key</code>主要是为了节省流程消耗,如果每次都通过小程序前端<code>wx.login()</code>生成微信登录凭证<code>code</code>去微信服务器请求信息,步骤太多会造成整体耗时比较严重。</p><p>使用接口<code>wx.checkSession()</code>可以校验<code>session_key</code>是否有效。用户越频繁使用小程序,<code>session_key</code>有效期越长。<code>session_key</code>失效时,可以通过重新执行登录流程获取有效的<code>session_key</code>。</p><p><strong>openid</strong><br><code>openid</code>是微信用户<code>id</code>,可以用这个<code>id</code>来区分不同的微信用户。<br>微信针对不同的用户在不同的应用下都有唯一的一个<code>openid</code>, 但是要想确定用户是不是同一个用户,就需要靠<code>unionid</code>来区分。</p><p><strong>unionid</strong><br>如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过<code>unionid</code>来区分用户的唯一性。同一用户,对同一个微信开放平台下的不同应用,<code>unionid</code>是相同的。</p><h3 id="加锁的登录"><a href="#加锁的登录" class="headerlink" title="加锁的登录"></a>加锁的登录</h3><p>在某些情况下,我们或许多个地方会同时触发登录逻辑(如多个接口同时拉取,发现登录态过期的情况)。一般来说,我们会简单地给请求加个锁来解决:</p><ol><li>使用<code>isLogining</code>来标志是否请求中。</li><li>方法返回 Promise,登录态过期时静默续期后重新发起。</li><li>使用<code>sessionId</code>来记录业务侧的登录态。</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// session 参数 key(后台吐回)</span></div><div class="line"><span class="keyword">export</span> <span class="keyword">const</span> SESSION_KEY = <span class="string">'sessionId'</span>;</div><div class="line"></div><div class="line"><span class="keyword">let</span> isLogining = <span class="literal">false</span>;</div><div class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">doLogin</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</div><div class="line"> <span class="keyword">const</span> session = wx.getStorageSync(SESSION_KEY);</div><div class="line"> <span class="keyword">if</span> (session) {</div><div class="line"> <span class="comment">// 缓存中有 session</span></div><div class="line"> resolve();</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isLogining) {</div><div class="line"> <span class="comment">// 正在登录中,请求轮询稍后,避免重复调用登录接口</span></div><div class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</div><div class="line"> doLogin()</div><div class="line"> .then(<span class="function"><span class="params">res</span> =></span> {</div><div class="line"> resolve(res);</div><div class="line"> })</div><div class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</div><div class="line"> reject(err);</div><div class="line"> });</div><div class="line"> }, <span class="number">500</span>);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> isLogining = <span class="literal">true</span>;</div><div class="line"> wx.login({</div><div class="line"> <span class="attr">success</span>: <span class="function">(<span class="params">res</span>) =></span> {</div><div class="line"> <span class="keyword">if</span> (res.code) {</div><div class="line"> <span class="keyword">const</span> reqData: ILoginRequest = {</div><div class="line"> <span class="attr">code</span>: res.code</div><div class="line"> }</div><div class="line"> wx.request({</div><div class="line"> <span class="attr">url</span>: API.login,</div><div class="line"> <span class="attr">data</span>: reqData,</div><div class="line"> <span class="comment">// method: "POST",</span></div><div class="line"> success: <span class="function">(<span class="params">resp</span>) =></span> {</div><div class="line"> <span class="keyword">const</span> data = resp.data;</div><div class="line"> isLogining = <span class="literal">false</span>;</div><div class="line"> <span class="comment">// 保存登录态</span></div><div class="line"> <span class="keyword">if</span> (data.return_code === <span class="number">0</span>) {</div><div class="line"> wx.setStorageSync(SESSION_KEY, data[SESSION_KEY]);</div><div class="line"> resolve();</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> reject(data.return_msg);</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="attr">fail</span>: <span class="function"><span class="params">err</span> =></span> {</div><div class="line"> <span class="comment">// 登录失败,解除锁,防止死锁</span></div><div class="line"> isLogining = <span class="literal">false</span>;</div><div class="line"> reject(err);</div><div class="line"> }</div><div class="line"> });</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// 登录失败,解除锁,防止死锁</span></div><div class="line"> isLogining = <span class="literal">false</span>;</div><div class="line"> reject();</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="attr">fail</span>: <span class="function">(<span class="params">err</span>) =></span> {</div><div class="line"> <span class="comment">// 登录失败,解除锁,防止死锁</span></div><div class="line"> isLogining = <span class="literal">false</span>;</div><div class="line"> reject(err);</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure><h2 id="登录态静默续期的实现"><a href="#登录态静默续期的实现" class="headerlink" title="登录态静默续期的实现"></a>登录态静默续期的实现</h2><hr><h3 id="checkSession"><a href="#checkSession" class="headerlink" title="checkSession"></a>checkSession</h3><p>前面也提到,微信不会把<code>session_key</code>的有效期告知开发者,因此需要使用接口<code>wx.checkSession()</code>来校验<code>session_key</code>是否有效。</p><p>这里我们:</p><ol><li>使用<code>isCheckingSession</code>来标志是否查询中。</li><li>返回 Promise。</li><li>使用<code>isSessionFresh</code>来标志<code>session_key</code>是否有效。</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> { doLogin } <span class="keyword">from</span> <span class="string">"./doLogin"</span>;</div><div class="line"><span class="keyword">import</span> { SESSION_KEY } <span class="keyword">from</span> <span class="string">"./doLogin"</span>;</div><div class="line"></div><div class="line"><span class="keyword">let</span> isCheckingSession = <span class="literal">false</span>;</div><div class="line"><span class="keyword">let</span> isSessionFresh = <span class="literal">false</span>;</div><div class="line"></div><div class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">checkSession</span>(<span class="params"></span>): <span class="title">Promise</span><<span class="title">string</span>> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</div><div class="line"> <span class="keyword">const</span> session = wx.getStorageSync(SESSION_KEY);</div><div class="line"> <span class="keyword">if</span> (isCheckingSession) {</div><div class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</div><div class="line"> checkSession()</div><div class="line"> .then(<span class="function"><span class="params">res</span> =></span> {</div><div class="line"> resolve(res);</div><div class="line"> })</div><div class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</div><div class="line"> reject(err);</div><div class="line"> });</div><div class="line"> }, <span class="number">500</span>);</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (!isSessionFresh && session) {</div><div class="line"> isCheckingSession = <span class="literal">true</span>;</div><div class="line"> wx.checkSession({</div><div class="line"> <span class="attr">success</span>: <span class="function"><span class="params">()</span> =></span> {</div><div class="line"> <span class="comment">// session_key 未过期,并且在本生命周期一直有效</span></div><div class="line"> isSessionFresh = <span class="literal">true</span>;</div><div class="line"> resolve();</div><div class="line"> },</div><div class="line"> <span class="attr">fail</span>: <span class="function"><span class="params">()</span> =></span> {</div><div class="line"> <span class="comment">// session_key 已经失效,需要重新执行登录流程</span></div><div class="line"> wx.removeStorage({</div><div class="line"> <span class="attr">key</span>: <span class="string">"skey"</span>,</div><div class="line"> <span class="attr">complete</span>: <span class="function"><span class="params">()</span> =></span> {</div><div class="line"> doLogin()</div><div class="line"> .then(<span class="function"><span class="params">()</span> =></span> {</div><div class="line"> resolve();</div><div class="line"> })</div><div class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</div><div class="line"> reject(err);</div><div class="line"> });</div><div class="line"> }</div><div class="line"> });</div><div class="line"> },</div><div class="line"> <span class="attr">complete</span>: <span class="function"><span class="params">()</span> =></span> {</div><div class="line"> isCheckingSession = <span class="literal">false</span>;</div><div class="line"> }</div><div class="line"> });</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> doLogin()</div><div class="line"> .then(<span class="function"><span class="params">res</span> =></span> {</div><div class="line"> resolve(res);</div><div class="line"> })</div><div class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</div><div class="line"> reject(err);</div><div class="line"> });</div><div class="line"> }</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="静默续期的接口请求"><a href="#静默续期的接口请求" class="headerlink" title="静默续期的接口请求"></a>静默续期的接口请求</h3><p>至此,我们可以封装一个简单的接口,来在每次登录态过期的时候自动续期:</p><ol><li>在请求前,使用<code>checkSession()</code>检车本次周期内<code>session_key</code>是否有效,无效则<code>doLogin()</code>拉起登录获取<code>sessionId</code>。</li><li>请求接口,若返回特定登录态失效错误码(此处假设为<code>LOGIN_FAIL_CODE</code>),则<code>doLogin()</code>拉起登录获取<code>sessionId</code>。</li><li>使用<code>tryLoginCount</code>来标志重试次数,<code>TRY_LOGIN_LIMIT</code>来标志重试次数上限,避免进入死循环。</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> { doLogin } <span class="keyword">from</span> <span class="string">"./doLogin"</span>;</div><div class="line"><span class="keyword">import</span> { SESSION_KEY } <span class="keyword">from</span> <span class="string">"./doLogin"</span>;</div><div class="line"><span class="keyword">import</span> { checkSession } <span class="keyword">from</span> <span class="string">"./checkSession"</span>;</div><div class="line"></div><div class="line"><span class="comment">// 会话过期错误码,需要重新登录</span></div><div class="line"><span class="keyword">export</span> <span class="keyword">const</span> LOGIN_FAIL_CODES = [<span class="number">10000</span>];</div><div class="line"></div><div class="line"><span class="keyword">const</span> TRY_LOGIN_LIMIT = <span class="number">3</span>;</div><div class="line"></div><div class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">request</span>(<span class="params">obj: any = {}</span>): <span class="title">Promise</span><<span class="title">object</span>> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</div><div class="line"> checkSession()</div><div class="line"> .then(<span class="function"><span class="params">()</span> =></span> {</div><div class="line"> <span class="keyword">let</span> session = wx.getStorageSync(SESSION_KEY);</div><div class="line"> <span class="keyword">const</span> { url, data, method, header, dataType } = obj;</div><div class="line"> <span class="keyword">let</span> tryLoginCount = obj.tryLoginCount || <span class="number">0</span>;</div><div class="line"> <span class="comment">// 如果需要通过 data 把登录态 sessionId 带上</span></div><div class="line"> <span class="keyword">const</span> dataWithSession = { ...data, [SESSION_KEY]: session, <span class="attr">appid</span>: APPID };</div><div class="line"> wx.request({</div><div class="line"> url,</div><div class="line"> <span class="attr">data</span>: dataWithSession,</div><div class="line"> method,</div><div class="line"> header,</div><div class="line"> dataType,</div><div class="line"> <span class="attr">success</span>: <span class="function">(<span class="params">res: any</span>) =></span> {</div><div class="line"> <span class="keyword">if</span> (res.statusCode === <span class="number">200</span>) {</div><div class="line"> <span class="keyword">const</span> data: ICommonResponse = res.data;</div><div class="line"> <span class="comment">// 登陆态失效特定错误码判断,且重试次数未达到上限</span></div><div class="line"> <span class="keyword">if</span> (LOGIN_FAIL_CODES.indexOf(data.return_code) > <span class="number">-1</span> && tryLoginCount < TRY_LOGIN_LIMIT) {</div><div class="line"> doLogin().then(<span class="function"><span class="params">()</span> =></span> {</div><div class="line"> obj.tryLoginCount = ++tryLoginCount;</div><div class="line"> request(obj)</div><div class="line"> .then(<span class="function"><span class="params">res</span> =></span> {</div><div class="line"> resolve(res);</div><div class="line"> })</div><div class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</div><div class="line"> reject(err);</div><div class="line"> });</div><div class="line"> });</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> resolve(res);</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> reject(res);</div><div class="line"> }</div><div class="line"> },</div><div class="line"> <span class="attr">fail</span>: <span class="function"><span class="keyword">function</span>(<span class="params">err</span>) </span>{</div><div class="line"> reject(err);</div><div class="line"> }</div><div class="line"> });</div><div class="line"> })</div><div class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</div><div class="line"> reject(err);</div><div class="line"> });</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure><p>至此,我们大概包装了一个能自动登录或是进行静默续期的一个请求接口。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/api/api-login.html" target="_blank" rel="external">小程序登录API</a></li><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=000cc48f96c5989b0086ddc7e56c0a" target="_blank" rel="external">《小程序开发指南》</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>小程序的登录和登录态管理,大概是大部分小程序都需要的能力。<code>code</code>和<code>session_key</code>的设计,做了哪些事情来保护用户的数据。<br>如何在全局范围地保证登录态的有效性,微信侧的登录态也好,业务侧的登录态也好,静默续期的能力能给用户带来不少的体验提升。</p>]]></content>
<summary type="html">
<p>每一个有数据交互的小程序,都会涉及到登录、token 等问题,openid 又是什么呢?怎么使用静默续期,来提升用户体验呢?<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>理解小程序的安全与管控</title>
<link href="https://godbasin.github.io/2018/11/04/wxapp-manage-and-security/"/>
<id>https://godbasin.github.io/2018/11/04/wxapp-manage-and-security/</id>
<published>2018-11-04T02:50:22.000Z</published>
<updated>2018-11-13T14:50:40.017Z</updated>
<content type="html"><![CDATA[<p>作为一个平台,管控和安全是很有必要性的。虽然说这些是开发自己需要进行防范的,但是平台如果能解决,也算是皆大欢喜了。<br><a id="more"></a></p><h2 id="双线程到底解决了什么"><a href="#双线程到底解决了什么" class="headerlink" title="双线程到底解决了什么"></a>双线程到底解决了什么</h2><p>先给小程序团队的双线程设计鼓个掌,关于双线程大家也可以回顾下<a href="https://godbasin.github.io/2018/09/02/wxapp-technology-architecture/">《小程序的底层框架》</a>。</p><h3 id="H5-的隐患"><a href="#H5-的隐患" class="headerlink" title="H5 的隐患"></a>H5 的隐患</h3><p>要知道,Web 技术是非常开放灵活的,开发者可以利用 JavaScript 脚本随意地操作 DOM,这是会带来以下的问题:</p><p><strong>随意地跳转网页,改变界面上的任意内容</strong><br>开发者可以利用 JavaScript 脚本随意地跳转网页,或是改变界面上的任意内容。当然,恶意攻击者也能利用这种便利。</p><p><strong>获取页面数据</strong><br>小程序也提供可一种可以展示敏感数据的组件,<code><open-data></code>能展示包括用户昵称、头像、性别、地理位置等信息(无需用户授权)。<br>如果开发者可以操作 DOM,意味着他们可以随意拿到用户的敏感信息。</p><p><strong>常见的前端漏洞</strong><br>开发者们普遍重视的安全漏洞,在前端常见的有 XSS 和 CSRF,XSS 是通过注入 JavaScript 脚本的方式来达到特定目的,而 CSRF 则是利用了 cookie。<br>XSS 在双线程的设计中就被过滤了,而 CSRF 会在后面讲到。</p><h3 id="难以实现的管控"><a href="#难以实现的管控" class="headerlink" title="难以实现的管控"></a>难以实现的管控</h3><p>为了解决管控与安全问题,小程序需要禁用掉:</p><ul><li>危险的 HTML 标签或者相关属性,如外跳 url 的 a 标签</li><li>危险的 API,如操作界面的 API、动态运行脚本的 API</li></ul><p>如果要一个一个禁止,JavaScript 的灵活性以及浏览器接口的丰富性,会导致很容易遗漏一些危险的接口。并且浏览器内核在不断更新,或许下一版本会新增一个可能会在这套体系下产生漏洞的接口,无法完全避免。</p><h3 id="安全的逻辑层"><a href="#安全的逻辑层" class="headerlink" title="安全的逻辑层"></a>安全的逻辑层</h3><p>要怎么彻底解决这些问题呢?给大家点提示:</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/sandbox.png" alt="image"></p><p>没错,就是沙箱环境。通过提供一个纯 JavaScript 的解释执行环境,这个环境没有浏览器相关接口,当然也不用担心操作 DOM、跳转等问题了。在 iOS 下是用内置的 JavaScriptCore 框架,在安卓下是 JsCore 环境(旧版是腾讯 x5 内核提供,新版是 v8 提供)。</p><p>一起来回顾下小程序的双线程长什么样子:</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1537414306%281%29.png" alt="image"></p><p>客户端系统有 JavaScript 的解释引擎,则可以创建一个单独的线程去执行 JavaScript,这个环境下只执行有关小程序业务逻辑的代码。界面渲染相关的任务呢,就丢到 webview 线程里面,通过逻辑层代码去控制渲染哪些界面。</p><p><strong>把开发者的 JS 逻辑代码放到单独的线程去运行,因为不在 Webview 线程里,所以这个环境没有 Webview 任何接口,自然的开发者就没法直接操作 DOM,也就没法动态去更改界面或者抓取页面数据。</strong></p><p>同时小程序不支持动态载入脚本,XSS 漏洞自然也无缝可钻。</p><h2 id="审核机制的管控"><a href="#审核机制的管控" class="headerlink" title="审核机制的管控"></a>审核机制的管控</h2><p>审核机制,故事要从公众号讲起了。</p><h3 id="WebView的飞速发展"><a href="#WebView的飞速发展" class="headerlink" title="WebView的飞速发展"></a>WebView的飞速发展</h3><p>当年随着公众号的出现和繁荣,WebView 的使用频率也越来越高。不少的企业或是小商家、外包公司开始做 H5 页面,各式各样的 H5 活动页、小商城、小测试、小游戏满天飞。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1537444454%281%29.jpg" alt="image"></p><p>当微信中的 WebView 逐渐成为移动 Web 的一个重要入口时,微信就有相关的 JS API 了。</p><p>2015年初,微信发布了一整套网页开发工具包,开放了拍摄、录音、语音识别、二维码、地图、支付、分享、卡券等几十个API,称之为 JS-SDK。</p><p>到这个时候,web开发者可以使用到微信的原生能力,去完成一些之前做不到或者很难做到的事情。</p><h3 id="难管控的-JSSDK"><a href="#难管控的-JSSDK" class="headerlink" title="难管控的 JSSDK"></a>难管控的 JSSDK</h3><p>由于使用 WebView 和 JSSDK 的人越来越多,微信上越来越多干坏事的人,有人做假红包,有人诱导分享,有伪造一些官方活动,他们会利用 JSSDK 的分享能力变相的去裂变分享到各个群或者朋友圈。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1537444835.jpg" alt="image"></p><p>由于 JSSDK 是根据域名来赋予 api 权限的,运营人员封了一个域名后,他们立马用别的域名又继续做坏,注册一个新的域名的成本是很低的。</p><h3 id="小程序的审核机制"><a href="#小程序的审核机制" class="headerlink" title="小程序的审核机制"></a>小程序的审核机制</h3><p>为了保证小程序的质量,以及符合相关的规范,小程序的发布是需要经过审核的。经过审核的小程序才能对外发布,同时在出现问题时,小程序会被下架停用。</p><p>另外,每个微信小程序需要事先设置一个通讯域名,小程序只可以跟指定的域名与进行网络通信,包括普通 HTTPS 请求、上传文件、下载文件和 WebSocket 通信,参考<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html" target="_blank" rel="external">框架-网络</a>。这些通讯域名,也都必须要求通过备案。</p><p>同时,小程序必须使用 HTTPS 发起网络请求。请求时系统会对服务器域名使用的 HTTPS 证书进行校验,如果校验失败,则请求不能成功发起。</p><p>这些种种的限制和管理模式,都进一步保障了用户的数据和隐私安全。</p><h2 id="安全的登录机制"><a href="#安全的登录机制" class="headerlink" title="安全的登录机制"></a>安全的登录机制</h2><p>想必在座的各位前端开发者,都清楚 CSRF 安全漏洞。</p><h3 id="危险的-cookie"><a href="#危险的-cookie" class="headerlink" title="危险的 cookie"></a>危险的 cookie</h3><p>跨站请求攻击(CSRF),简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。</p><p>这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。通常的罪魁祸首则是浏览器的 cookie 登录态。</p><p>除了检查 Referer 字段来防范,更有效的一种方式是使用 token。小程序也是这么做的。</p><h3 id="小程序登录"><a href="#小程序登录" class="headerlink" title="小程序登录"></a>小程序登录</h3><p>小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。参考官方时序图:</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/%E6%8E%88%E6%9D%83%E6%97%B6%E5%BA%8F%E5%9B%BE.jpg" alt="image"></p><p>在小程序中调用<code>wx.login()</code>,能拿到一个<code>code</code>作为用户登录凭证(有效期五分钟)。在开发者服务器后台,开发者可使用<code>code</code>换取<code>openid</code>和<code>session_key</code>等信息(<code>code</code>只能使用一次)。</p><h3 id="可靠的-code"><a href="#可靠的-code" class="headerlink" title="可靠的 code"></a>可靠的 code</h3><p>假设现在有个接口,请求 <a href="https://test.com/getUserInfo?id=1" target="_blank" rel="external">https://test.com/getUserInfo?id=1</a> 拉取到微信用户 id 为 1 在我们业务侧的个人信息,那么黑客就可以通过遍历所有的 id,把整个业务侧的个人信息数据全部拉走,会给业务带来很大的安全风险。</p><p>由于<code>code</code> 5 分钟后会过期,如果黑客要冒充一个用户的话,那他就必须在 5 分钟内穷举所有的身份证 id,然后去开发者服务器换取真实的用户身份。而<code>code</code>在成功换取一次信息之后也会立即失效,即便凭证<code>code</code>生成时间还没过期。显然,黑客要付出非常大的成本才能获取到一个用户信息,同时,开发者服务器也可以通过一些技术手段检测到5分钟内频繁从某个 ip 发送过来的登录请求,从而拒绝掉这些请求。</p><h3 id="需要保护的-AppSecret"><a href="#需要保护的-AppSecret" class="headerlink" title="需要保护的 AppSecret"></a>需要保护的 AppSecret</h3><p>开发者的后台就拿到了前边<code>wx.login()</code>所生成的微信登录凭证<code>code</code>,此时就可以拿这个<code>code</code>到微信服务器换取微信用户身份。微信服务器为了确保拿<code>code</code>过来换取身份信息的人就是刚刚对应的小程序开发者,到微信服务器的请求要同时带上<code>AppId</code>和<code>AppSecret</code>。</p><p><code>AppId</code>和<code>AppSecret</code>是微信鉴别开发者身份的重要信息,<code>AppId</code>是公开信息,泄露AppId不会带来安全风险,但是<code>AppSecret</code>是开发者的隐私数据不应该泄露,开发者需要好好保护。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=000cc48f96c5989b0086ddc7e56c0a" target="_blank" rel="external">《小程序开发指南》</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>作为一个开放的平台,小程序在提供微信加持、体验加持的能力给开发者使用的同时,也替用户和开发者做了很多安全性上的保障。<br>或许有人说,这是牺牲了开发者的开放性换来的,而我认为这样才是个有灵魂的平台。</p>]]></content>
<summary type="html">
<p>作为一个平台,管控和安全是很有必要性的。虽然说这些是开发自己需要进行防范的,但是平台如果能解决,也算是皆大欢喜了。<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>解剖小程序的 setData</title>
<link href="https://godbasin.github.io/2018/10/05/wxapp-set-data/"/>
<id>https://godbasin.github.io/2018/10/05/wxapp-set-data/</id>
<published>2018-10-05T10:56:59.000Z</published>
<updated>2018-10-05T10:57:10.329Z</updated>
<content type="html"><![CDATA[<p>小程序的双线程,之前也有详细讲过了。而双线程的设计,使得逻辑层和渲染层无法直接进行数据传输。那双线程的渲染机制、通信机制,setData 的出现、工作原理、使用建议等,应该要怎么去理解呢?<br><a id="more"></a></p><h1 id="无处不在的-setData"><a href="#无处不在的-setData" class="headerlink" title="无处不在的 setData"></a>无处不在的 setData</h1><hr><p>几乎每个开发者都会用到<code>setData</code>,要是在复杂的页面中,写了很多的<code>setData</code>,然后我们会发现页面真的是延迟严重,甚至卡顿、假死。</p><p><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips.html" target="_blank" rel="external">官方</a>在性能优化中有提到:</p><ol><li><strong>避免频繁的去 setData。</strong></li><li><strong>避免每次 setData 都传递大量新数据。</strong></li><li><strong>后台态页面进行 setData。</strong></li></ol><p>但是到底是为什么呢?<code>setData</code>的出现、设计方案是怎样的,又为何要这么设计呢?一切都还是要从双线程说起。</p><h2 id="小程序的虚拟-DOM"><a href="#小程序的虚拟-DOM" class="headerlink" title="小程序的虚拟 DOM"></a>小程序的虚拟 DOM</h2><h3 id="双线程的难题"><a href="#双线程的难题" class="headerlink" title="双线程的难题"></a>双线程的难题</h3><p>我们知道,小程序的双线程设计,主要为了管控安全,避免操作 DOM。(可参考<a href="https://godbasin.github.io/2018/09/02/wxapp-technology-architecture/">《小程序的底层框架》</a>)</p><p>把开发者的 JS 逻辑代码放到单独的线程去运行,因为不在 Webview 线程里,所以这个环境没有 Webview 任何接口,自然开发者就没法直接操作 DOM,也就没法动态去更改界面。</p><p>但是,这样就产生了新的问题。<strong>没法操作 DOM,那用户交互需要界面变化的话怎么办呢?</strong></p><h3 id="模板数据绑定"><a href="#模板数据绑定" class="headerlink" title="模板数据绑定"></a>模板数据绑定</h3><p>模版数据绑定的方案,已经成为前端框架中最基础的功能。</p><p><strong>数据绑定的过程其实不复杂:</strong></p><ol><li><strong>解析语法生成 AST。</strong></li><li><strong>根据 AST 结果生成 DOM。</strong></li><li><strong>将数据绑定更新至模板。</strong></li></ol><p>浏览器会把 HTML 解析成一棵树,最后渲染出来。整个界面是对应着一棵 DOM 树。</p><p>其实浏览器页面的 DOM 结构树,也是 AST 的一种,把 HTML DOM 语法解析并生成最终的页面。而模板引擎中常用的,则是将模板语法解析生成 HTML DOM。</p><p>而最容易引发性能问题的,主要是第三点。而关于数据更新的解决方案,React 首先提出了虚拟 DOM 的设计,而现在也基本被大部分框架吸收,小程序也不例外。</p><h3 id="虚拟-DOM-机制"><a href="#虚拟-DOM-机制" class="headerlink" title="虚拟 DOM 机制"></a>虚拟 DOM 机制</h3><p>说到数据更新的 Diff,更多的则是<code>Diff + 更新模板</code>这样一个过程。</p><p><strong>虚拟 DOM 解决了常见的局部数据更新的问题</strong>,例如数组中值位置的调换、部分更新。</p><p>一般来说计算过程如下:</p><ol><li><strong>用JS对象模拟DOM树。</strong></li></ol><p>一个真正的DOM元素非常庞大,拥有很多的属性值。而其中很多的属性对于计算过程来说是不需要的,所以我们的第一步就是简化 DOM 对象。我们用一个 JavaScript 对象结构表示 DOM 树的结构。</p><ol><li><strong>比较两棵虚拟DOM树的差异。</strong></li></ol><p>当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异。通常来说这样的差异需要记录,最后得到一组差异记录。</p><ol><li><strong>把差异应用到真正的DOM树上。</strong></li></ol><p>对差异记录要应用到真正的 DOM 树上,例如节点的替换、移动、删除,文本内容的改变等。</p><p>小程序里,由于无法直接操作 DOM,主要也是通过数据传递的方式来进行相关的模版更新。模版绑定的机制、数据更新的机制,都可以参照上面的说明,想更具体理解也可以参考<a href="https://github.com/godbasin/godbasin.github.io/issues/7" target="_blank" rel="external">《前端模板引擎》</a>。</p><p>那么既然不在一个线程,数据的通信是怎么做的呢?</p><h2 id="小程序的数据通信与渲染机制"><a href="#小程序的数据通信与渲染机制" class="headerlink" title="小程序的数据通信与渲染机制"></a>小程序的数据通信与渲染机制</h2><h3 id="双线程通信方式"><a href="#双线程通信方式" class="headerlink" title="双线程通信方式"></a>双线程通信方式</h3><p>小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。</p><p>一个小程序存在多个界面,所以渲染层存在多个 WebView 线程。 <strong>逻辑层和渲染层的通信会经由微信客户端(Native)做中转,逻辑层发送网络请求也经由 Native 转发</strong> ,小程序的通信模型如图:<br><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/%E4%B8%8B%E8%BD%BD.png" alt="小程序的通信模型图(官方)"></p><p>当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。<strong>即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。</strong></p><p>而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。所以我们的<code>setData</code>函数将数据从逻辑层发送到视图层,是异步的。</p><p>有了线程之间的通信,我们来看看小程序的渲染机制。</p><h3 id="双线程渲染机制"><a href="#双线程渲染机制" class="headerlink" title="双线程渲染机制"></a>双线程渲染机制</h3><p>双线程的渲染,其实是结合了前面的一系列机制(模版绑定、虚拟 DOM、线程通信),最后整合的一个执行步骤。</p><p><strong>1. 通过模版数据绑定和虚拟 DOM 机制,小程序提供了带有数据绑定语法的 DSL 给到开发者,用来在渲染层描述界面的结构。</strong></p><p>就是我们常见的这些:<br><figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">view</span>></span> {{ message }} <span class="tag"></<span class="name">view</span>></span></div><div class="line"><span class="tag"><<span class="name">view</span> <span class="attr">wx:if</span>=<span class="string">"{{condition}}"</span>></span> <span class="tag"></<span class="name">view</span>></span></div><div class="line"><span class="tag"><<span class="name">checkbox</span> <span class="attr">checked</span>=<span class="string">"{{false}}"</span>></span> <span class="tag"></<span class="name">checkbox</span>></span></div></pre></td></tr></table></figure></p><p>噢,这里顺便吐个槽,<code>wx:if</code>竟然不支持<code>[].indexOf(xx) > -1</code>等等相关的函数运算(摔!)。</p><p><strong>2. 小程序在逻辑层提供了设置页面数据的 api。</strong></p><p>不用问就是<code>setData</code>了:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">this</span>.setData({</div><div class="line"> <span class="attr">key</span>: value</div><div class="line">})</div></pre></td></tr></table></figure></p><p><code>setData</code>函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的<code>this.data</code>的值(同步)。</p><p><strong>3. 逻辑层需要更改界面时,只要把修改后的 data 通过 setData 传到渲染层。</strong></p><p>传输的数据,会转换为字符串形式传递,故应尽量避免传递大量数据。</p><p><strong>4. 渲染层会根据前面提到的渲染机制重新生成 VD(虚拟 DOM)树,并更新到对应的 DOM 树上,引起界面变化。</strong></p><h2 id="原生组件的出现"><a href="#原生组件的出现" class="headerlink" title="原生组件的出现"></a>原生组件的出现</h2><p>原生组件的出现,其实与 setData 的机制也有那么点关系,那么就当题外话一块补充下。</p><h3 id="频繁交互的性能"><a href="#频繁交互的性能" class="headerlink" title="频繁交互的性能"></a>频繁交互的性能</h3><p>我们知道,用户的一次交互,如点击某个按钮,开发者的逻辑层要处理一些事情,然后再通过 setData 引起界面变化。这样的一个过程需要四次通信:</p><ol><li>渲染层 -> Native(点击事件)。</li><li>Native -> 逻辑层(点击事件)。</li><li>逻辑层 -> Native(setData)。</li><li>Native -> 渲染层(setData)。</li></ol><p>在一些强交互的场景(表单、canvas等),这样的操作流程会导致用户体验卡顿。</p><h3 id="引入原生组件"><a href="#引入原生组件" class="headerlink" title="引入原生组件"></a>引入原生组件</h3><p>前面也说过,小程序是 Hybrid 应用,除了 Web 组件的渲染体系(上面讲到),还有由客户端原生参与组件(原生组件)的渲染。</p><p>引入原生组件主要有 3 个好处:</p><ol><li><strong>绕过 setData、数据通信和重渲染流程,使渲染性能更好。</strong></li><li><strong>扩展 Web 的能力。</strong>比如像输入框组件(input, textarea)有更好地控制键盘的能力。</li><li><strong>体验更好,同时也减轻 WebView 的渲染工作。</strong>比如像地图组件(map)这类较复杂的组件,其渲染工作不占用 WebView 线程,而交给更高效的客户端原生处理。</li></ol><p>而原生组件的渲染过程:</p><ol><li>组件被创建,包括组件属性会依次赋值。</li><li>组件被插入到 DOM 树里,浏览器内核会立即计算布局,此时我们可以读取出组件相对页面的位置(x, y坐标)、宽高。</li><li>组件通知客户端,客户端在相同的位置上,根据宽高插入一块原生区域,之后客户端就在这块区域渲染界面。</li><li>当位置或宽高发生变化时,组件会通知客户端做相应的调整。</li></ol><p>简单来说,就是 <strong>原生组件在 WebView 这一层只需要渲染一个占位元素,之后客户端在这块占位元素之上叠了一层原生界面。</strong></p><p>有利必有弊,原生组件也是有限制的:</p><ul><li>最主要的限制是一些 CSS 样式无法应用于原生组件</li><li>由于客户端渲染,原生组件的层级会比所有在 WebView 层渲染的普通组件要高</li></ul><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips.html" target="_blank" rel="external">setData</a></li><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=000caab39b88b06b00863ab085b80a" target="_blank" rel="external">《小程序开发指南–6.3 原生组件》</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>总而言之,这一节内容主要是围绕 setData 展开,包括双线程的渲染机制、通信机制,setData 的出现(逻辑层通知渲染层)、工作原理(evaluateJavascript 字符串传递)、使用建议(setData 交互性能)、性能优化(原生组件出现)。<br>小程序乍一看是简单的双线程设计,但仔细研究就会发现设计过程中也遇到了不少问题,不断探索解决才有了现在的美好样子。我们在开发过程中会踩的一些坑,其实在理解原理之后便很容易懂了。<br>现在再来看,官方在性能优化中说到的优化建议,你都能深刻理解了吗?</p>]]></content>
<summary type="html">
<p>小程序的双线程,之前也有详细讲过了。而双线程的设计,使得逻辑层和渲染层无法直接进行数据传输。那双线程的渲染机制、通信机制,setData 的出现、工作原理、使用建议等,应该要怎么去理解呢?<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>关于小程序的基础库</title>
<link href="https://godbasin.github.io/2018/09/23/wxapp-basic-lib/"/>
<id>https://godbasin.github.io/2018/09/23/wxapp-basic-lib/</id>
<published>2018-09-23T07:02:58.000Z</published>
<updated>2018-10-01T23:39:42.610Z</updated>
<content type="html"><![CDATA[<p>小程序的基础库,它包含了哪些东西,以及载入、更新的机制又是怎样的呢。<br><a id="more"></a></p><h2 id="小程序基础库的组成"><a href="#小程序基础库的组成" class="headerlink" title="小程序基础库的组成"></a>小程序基础库的组成</h2><h3 id="基础库成分"><a href="#基础库成分" class="headerlink" title="基础库成分"></a>基础库成分</h3><p>关于基础库的成分,不得不提到我们之前说过的小程序渲染机制,参考 React 的 Virtual DOM。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/13333.png" alt="image"></p><p><strong>基础库除了处理 VD 的渲染问题,它还包括内置组件和逻辑层API,总的来说负责处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑。</strong></p><p>小程序的基础库是 JavaScript 编写的,它可以被注入到渲染层和逻辑层运行。在渲染层可以用各类组件组建界面的元素,在逻辑层可以用各类 API 来处理各种逻辑。</p><p>同时,小程序的一些补充能力:自定义组件和插件,也有相应的基础代码,当然也需要添加到基础库里。</p><p>所以我们可以看到,<strong>小程序的基础库主要是</strong>:</p><ol><li><strong>提供 VD 渲染机制相关基础代码。(Exparser 框架)</strong></li><li><strong>提供封装后的内置组件。</strong></li><li><strong>提供逻辑层的 API。</strong></li><li><strong>提供其他补充能力(自定义组件和插件等)的基础代码。</strong></li></ol><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/124154.jpg" alt="基础库组成"></p><h3 id="Exparser-框架"><a href="#Exparser-框架" class="headerlink" title="Exparser 框架"></a>Exparser 框架</h3><p><strong>Exparser 是微信小程序的组件组织框架,内置在小程序基础库中,为小程序的各种组件提供基础的支持。</strong>小程序内的所有组件,包括内置组件和自定义组件,都由 Exparser 组织管理。</p><p>Exparser 会维护整个页面的节点树相关信息,包括节点的属性、事件绑定等,相当于一个简化版的 Shadow DOM 实现。Exparser 的主要特点包括以下几点:</p><ol><li>基于 Shadow DOM 模型:模型上与 WebComponents 的 ShadowDOM 高度相似,但不依赖浏览器的原生支持,也没有其他依赖库;实现时,还针对性地增加了其他 API 以支持小程序组件编程。</li><li>可在纯 JS 环境中运行:这意味着逻辑层也具有一定的组件树组织能力。</li><li>高效轻量:性能表现好,在组件实例极多的环境下表现尤其优异,同时代码尺寸也较小。</li></ol><p>基于这个框架,内置了一套组件,以涵盖小程序的基础功能,便于开发者快速搭建出任何界面。同时也提供了自定义组件的能力,开发者可以自行扩展更多的组件,以实现代码复用。</p><h3 id="内置组件"><a href="#内置组件" class="headerlink" title="内置组件"></a>内置组件</h3><p>小程序基于 Exparser 框架,内置了一套组件,提供了视图容器类、表单类、导航类、媒体类、开放类等几十种组件。</p><p><strong>内置组件在小程序框架里的定义是:在小程序架构里无法实现或者实现不好某类功能,使用组件内置到小程序框架里。</strong>常见包括:</p><ul><li>开放类组件:如 open-data 组件提供展示群名称、用户信息等微信体系下的隐私信息,有 button 组件里 open-type 属性所提供分享、跳转 App 等敏感操作的能力</li><li>视图容器类组件:如 movable-view 这种因双线程模型导致手势识别不好实现的组件(在双线程模型中,触摸事件从渲染层发出,派发到逻辑层,这中间是有一定的延时而导致视图跟随手指运动这类交互变得有些卡顿)</li></ul><h3 id="API"><a href="#API" class="headerlink" title="API"></a>API</h3><p>宿主环境提供了丰富的API,可以很方便调起微信提供的能力。<br>小程序提供的 API 按照功能主要分为几大类:网络、媒体、文件、数据缓存、位置、设备、界面、界面节点信息还有一些特殊的开放接口。</p><p>需要注意 API 调用大多都是异步的。</p><h3 id="自定义组件"><a href="#自定义组件" class="headerlink" title="自定义组件"></a>自定义组件</h3><p>自定义组件是开发者可以自行扩充的组件。开发者可以将常用的节点树结构提取成自定义组件,实现代码复用。</p><p>在使用自定义组件的小程序页面中,Exparser 将接管所有的自定义组件注册与实例化。以 Component 为例:</p><ol><li>在小程序启动时,构造器会将开发者设置的 properties、data、methods 等定义段,写入 Exparser 的组件注册表中。</li><li>这个组件在被其它组件引用时,就可以根据这些注册信息来创建自定义组件的实例。</li></ol><p>Page 构造器的大体运行流程与之相仿,只是参数形式不一样。这样每个页面就有一个与之对应的组件,称为“页面根组件”。<br>在初始化页面时,Exparser 会创建出页面根组件的一个实例,用到的其他组件也会响应创建组件实例(这是一个递归的过程)。</p><h3 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h3><p>插件是对一组 js 接口、自定义组件或页面的封装,用于嵌入到小程序中使用。</p><p>插件不能独立运行,必须嵌入在其他小程序中才能被用户使用;而第三方小程序在使用插件时,也无法看到插件的代码。因此,插件适合用来封装自己的功能或服务,提供给第三方小程序进行展示和使用。</p><p>插件开发者可以像开发小程序一样编写一个插件并上传代码,在插件发布之后,其他小程序方可调用。小程序平台会托管插件代码,其他小程序调用时,上传的插件代码会随小程序一起下载运行。</p><h2 id="小程序基础库机制"><a href="#小程序基础库机制" class="headerlink" title="小程序基础库机制"></a>小程序基础库机制</h2><h3 id="基础库的载入"><a href="#基础库的载入" class="headerlink" title="基础库的载入"></a>基础库的载入</h3><p>在开发网页时,经常会引用很多开源的 JS 库,在使用到这些库所提供的 API 前,我们需要先在业务代码前边引入这些库。</p><p>同样道理,我们需要在启动 APP 之前载入基础库,接着再载入业务代码。由于小程序的渲染层和逻辑层是两个线程管理,而我们 <strong>一般说起基础库,也通常包括 WebView 基础库(渲染层),和 AppService 基础库(逻辑层)。</strong></p><p>显然,所有小程序在微信客户端打开的时候,都需要注入相同的基础库。所以,<strong>小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。</strong></p><p>将基础库内置在微信客户端,有两个好处:</p><ol><li>降低业务小程序的代码包大小。</li><li>可以单独修复基础库中的Bug,无需修改到业务小程序的代码包。</li></ol><h3 id="小程序的启动"><a href="#小程序的启动" class="headerlink" title="小程序的启动"></a>小程序的启动</h3><p><strong>在小程序启动前,微信会提前准备好一个页面层级用于展示小程序的首页。</strong>这里就包括了逻辑层和渲染层分别的初始化以及公共库的注入。</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/1537455257%281%29.jpg" alt="小程序启动准备"></p><p><strong>在小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:下载小程序代码包、加载小程序代码包、初始化小程序首页。</strong></p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/777.png" alt="小程序启动过程"></p><h3 id="基础库的更新"><a href="#基础库的更新" class="headerlink" title="基础库的更新"></a>基础库的更新</h3><p>小程序的很多能力需要微信客户端来支撑,例如蓝牙、直播能力、微信运动等,可以说,小程序基础库的迭代离不开微信客户端的发布。</p><p>为了避免新版本的基础库给线上小程序带来未知的影响,微信客户端都是携带上一个稳定版的基础库发布的。等到微信客户端正式发布后,小程序会开始灰度推送新版本的基础库到微信客户端里,在这个过程需要仔细监控各类异常现象以及开发者和用户的反馈,一般灰度时长为12小时,灰度结束后,用户设备上就会有新版本的基础库。如果存在重大Bug,那此次推送会被回退。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=000c8e5f95c1c88b00865be425b00a" target="_blank" rel="external">《小程序开发指南——小程序基础库的更新迭代》</a></li><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=0000aac998c9b09b00863377251c0a" target="_blank" rel="external">《小程序开发指南——6.2 组件系统》</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>本节大致结合了小程序的启动来讲了下小程序的基础库。其实很多都能在小程序开发指南里找到,只是文字太多又有些乱,看一遍未必能记住,但是第二遍又找不到了。<br>哈哈哈吐槽下小程序的文档,很详细就是有点容易找不到。</p>]]></content>
<summary type="html">
<p>小程序的基础库,它包含了哪些东西,以及载入、更新的机制又是怎样的呢。<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>小程序页面管理与跳转</title>
<link href="https://godbasin.github.io/2018/09/08/wxapp-page-and-navigate/"/>
<id>https://godbasin.github.io/2018/09/08/wxapp-page-and-navigate/</id>
<published>2018-09-08T10:19:08.000Z</published>
<updated>2018-09-17T13:36:56.724Z</updated>
<content type="html"><![CDATA[<p>一个小程序有很多页面,每个页面又有各自的线程、生命周期和功能逻辑。关于小程序的生命周期、页面之间的跳转有哪些特殊的地方呢?<br><a id="more"></a></p><h2 id="小程序的启动"><a href="#小程序的启动" class="headerlink" title="小程序的启动"></a>小程序的启动</h2><hr><h3 id="小程序启动过程"><a href="#小程序启动过程" class="headerlink" title="小程序启动过程"></a>小程序启动过程</h3><p>初次进入小程序的时候,微信客户端初始化好宿主环境,同时从网络下载或者从本地缓存中拿到小程序的代码包,把它注入到宿主环境。大概是这么个过程:</p><ol><li>创建线程(渲染层和逻辑层),启动小程序。</li><li>载入基础库(WebView 基础库和 AppService 基础库)。</li><li>载入小程序业务代码(下载或者从本地缓存中拿到)。</li><li>使用<code>App()</code>注册程序实例。</li></ol><p>为了让小程序业务代码能够调用 API 以及组件,就需要在启动小程序后先载入基础库,接着再载入业务代码。<br>由于所有小程序都需要注入相同的基础库,所以<strong>小程序的基础库会被提前内置在微信客户端</strong>。而基础库是热更新的,故一般等微信客户端携带上一个稳定版的基础库正式发布后,再进行新版本基础库的灰度和推送。</p><h3 id="注册-App-实例"><a href="#注册-App-实例" class="headerlink" title="注册 App 实例"></a>注册 App 实例</h3><p>宿主环境提供了<code>App()</code>构造器用来注册一个程序 App。App 实例是单例对象,在其他 JS 脚本中可以使用宿主环境提供的<code>getApp()</code>来获取程序实例。</p><p><strong>App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。</strong></p><p><code>App()</code>函数用来注册一个小程序。接受一个<code>Object</code>参数,其指定小程序的生命周期回调等。</p><p><strong>onLaunch</strong><br>小程序初始化完成时(全局只触发一次)触发<code>onLaunch</code>回调。<br>在微信客户端中打开小程序有很多途径,对不同途径的打开方式,小程序有时需要做不同的业务处理。所以微信客户端会把打开方式带给<code>onLaunch</code>和<code>onShow</code>的调用参数<code>options</code>,我们可以根据参数来判断一些进入方式,以及做对应的逻辑处理。</p><p>例如,我需要拿到从另外一个小程序跳转过来携带的信息,此时场景值应该是1037(参考<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/scene.html" target="_blank" rel="external">场景值</a>):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">App({</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> onShow: <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</div><div class="line"> <span class="keyword">if</span>(e.scene === <span class="number">1037</span>){</div><div class="line"> <span class="keyword">const</span> data = e.referrerInfo && e.referrerInfo.extraData; <span class="comment">// 拿到对应的数据</span></div><div class="line"> <span class="keyword">const</span> refAppid = e.referrerInfo && e.referrerInfo.appId; <span class="comment">// 拿到对应的小程序appid</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">})</div></pre></td></tr></table></figure><p><strong>onShow</strong><br>小程序启动,或从后台进入前台显示时触发<code>onShow</code>回调。通常我们用来处理数据和状态的更新。<br>小程序进入后台状态:当用户点击左上角关闭,或者按了设备 Home 键离开微信,小程序并没有直接销毁。</p><p><strong>onHide</strong><br>小程序从前台进入后台时触发<code>onHide</code>回调。<br>小程序进入前台状态:当再次进入微信或再次打开小程序,又会从后台进入前台。</p><h3 id="获取-App-实例"><a href="#获取-App-实例" class="headerlink" title="获取 App 实例"></a>获取 App 实例</h3><p>我们可以使用全局的<code>getApp()</code>函数来获取到小程序 App 实例(在<code>App()</code>内的函数中使用<code>this</code>就可以拿到<code>app</code>实例。)。</p><p>前面我们可以看到,App 的生命周期是由微信客户端根据用户操作主动触发的。故我们通过<code>getApp()</code>获取实例之后,不应该私自调用生命周期函数。</p><p>具体的原理是什么呢?小程序的 JS 脚本是运行在 JsCore 的线程里,小程序的每个页面各自有一个 WebView 线程进行渲染,所以<strong>小程序切换页面时,小程序逻辑层的 JS 脚本运行上下文依旧在同一个 JsCore 线程中。</strong></p><p>因此,<strong>App 构造器可以传递其他参数作为全局属性以达到全局共享数据的目的。</strong></p><p>由于所有页面的脚本逻辑都跑在同一个 JsCore 线程,页面使用<code>setTimeout</code>或者<code>setInterval</code>的定时器,即使切换了页面,也需要自行清理定时器。可以选择:</p><ul><li>在页面离开<code>onUnload</code>、<code>onHide</code>等的时候自行清理</li><li>做全局的定时器管理(当然也还是需要关闭时清理)</li></ul><p>说到页面之间的数据共享,我们也该来讲讲小程序里页面的启动。</p><h2 id="小程序页面"><a href="#小程序页面" class="headerlink" title="小程序页面"></a>小程序页面</h2><hr><h3 id="页面生命周期"><a href="#页面生命周期" class="headerlink" title="页面生命周期"></a>页面生命周期</h3><p>宿主环境提供了<code>Page(Object)</code>构造器用来注册一个小程序页面,接受一个<code>Object</code>类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。</p><p>注意:<strong>Object 内容在页面加载时会进行一次深拷贝,需考虑数据大小对页面加载的开销。</strong></p><p>这里我们先来看看官方的生命周期图:</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/mina-lifecycle.png" alt="image"></p><p>左侧是渲染层,右侧是逻辑层。几件事:</p><ol><li>渲染层和逻辑层之间通信,是通过 Native 转发实现的。</li><li>逻辑层通过 Page 实例的<code>setData</code>方法传递数据到渲染层。由于需要两个线程的一些通信消耗,为了提高性能,每次只设置需要改变的最小单位数据。</li><li>生命周期顺序:<code>onLoad</code> -> <code>onShow</code> -> <code>onReady</code>。</li></ol><p><strong>页面生命周期函数</strong>:<br><strong>onLoad(Object query)</strong><br>页面加载时触发。一个页面只会调用一次,可以在<code>onLoad</code>的参数中获取打开当前页面路径中的参数。</p><p><strong>onShow()</strong><br>页面显示/切入前台时触发。</p><p><strong>onReady()</strong><br>页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。</p><p><strong>onHide()</strong><br>页面隐藏/切入后台时触发。</p><p><strong>onUnload()</strong><br>页面卸载时触发。</p><p>和小程序实例的生命周期对比,其实页面也是有些相似。这里需要注意几点:</p><ul><li>当前页面路径的参数获取,只能在<code>onLoad(query)</code>的<code>query</code>参数中获取,无法在<code>onShow()</code>中获取</li><li><code>onLoad</code>、<code>onReady</code>和<code>onUnload</code>,一个页面都只会调用一次</li><li>页面是卸载还是切换到后台,这些除了与小程序的后台切换有关系,还会与页面的跳转、切换逻辑有关系</li></ul><p>下面我们就来看下页面的逻辑。</p><h3 id="页面导航"><a href="#页面导航" class="headerlink" title="页面导航"></a>页面导航</h3><p>我们知道,一个小程序会拥有多个页面。在小程序里会有页面的层级关系,例如通过<code>wx.navigateTo</code>推入一个新的页面,在首页使用2次<code>wx.navigateTo</code>后,页面层级会有三层:</p><p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/111.png" alt="image"></p><p><strong>获取页面栈</strong><br><code>getCurrentPages()</code>函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。<br>需要注意的是:</p><ul><li>修改页面栈会导致路由以及页面状态错误</li><li><code>App.onLaunch</code>的时候 page 还没有生成,不能在这调用<code>getCurrentPages()</code></li></ul><p>但是其实不是每一次切换页面,都会被记录到页面栈里,我们看看页面导航的一些方法和行为:</p><table><thead><tr><th>路由方式</th><th>触发时机</th><th>页面栈表现</th><th>进入方式</th></tr></thead><tbody><tr><td>初始化</td><td>小程序打开的第一个页面</td><td>新页面入栈</td><td>从下往上升起</td></tr><tr><td>打开新页面</td><td>调用 API <code>wx.navigateTo</code></td><td>新页面入栈</td><td>从右往左切入</td></tr><tr><td>页面重定向</td><td>调用 API <code>wx.redirectTo</code></td><td>当前页面出栈,新页面入栈</td><td>页面重新加载</td></tr><tr><td>页面返回</td><td>返回/调用 API <code>wx.navigateBack</code></td><td>页面不断出栈,直到目标返回页</td><td>从右往左切回</td></tr><tr><td>Tab 切换</td><td>切换/调用 API <code>wx.switchTab</code></td><td>页面全部出栈,只留下新的 Tab 页面</td><td>页面重新加载</td></tr><tr><td>重加载</td><td>调用 API <code>wx.reLaunch</code></td><td>页面全部出栈,只留下新的页面</td><td>页面重新加载</td></tr></tbody></table><p>关于导航 API 的几个补充点:</p><ul><li><code>wx.navigateTo</code>和<code>wx.redirectTo</code>只能打开非 TabBar 页面,<code>wx.switchTab</code>只能打开 Tabbar 页面,<code>wx.reLaunch</code>可以打开任意页面</li><li>TabBar 页面指在 app.json 的 TabBar 字段定义的页面(客户端窗口的底部或顶部有 tab 栏可以切换页面)</li><li>跳转到 TabBar 页面,路径后不能带参数(注意,Tabbar 页面初始化之后不会被销毁)</li><li>调用页面路由带的参数可以在目标页面的<code>onLoad</code>中获取</li></ul><h3 id="页面层级准备"><a href="#页面层级准备" class="headerlink" title="页面层级准备"></a>页面层级准备</h3><p>我们知道页面栈的表现,以及一些常见的导航方法,而小程序基础库也在页面层级做了些体验优化。</p><p>对于每一个新的页面层级,视图层都需要进行一些额外的准备工作:</p><ul><li>在小程序启动前,微信会提前准备好一个页面层级用于展示小程序的首页</li><li>每当一个页面层级被用于渲染页面,微信都会提前开始准备一个新的页面层级,减少每次新开页面的耗时</li></ul><p>每个页面的准备都有三个阶段:</p><ol><li>启动一个 WebView。</li><li>WebView 中初始化基础库(此时还会进行一些基础库内部优化,以提升页面渲染性能)。</li><li>注入小程序 WXML 结构和 WXSS 样式(小程序能在接收到页面初始数据之后马上开始渲染页面)。</li></ol><p>PS:<code>wx.redirectTo</code>不会打开一个新的页面层级,而是将当前页面层级重新初始化。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/api/ui-navigate.html" target="_blank" rel="external">导航.小程序</a></li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/route.html" target="_blank" rel="external">路由.小程序</a></li><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=0004eec99acc808b00861a5bd5280a" target="_blank" rel="external">3.2 程序与页面</a></li><li><a href="https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=000a64a29c48b0eb0086f161b5940a" target="_blank" rel="external">7.2 页面层级准备</a></li></ul><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><hr><p>页面的路由和跳转、切入方式,其实和用户的使用和交互紧紧相关,设计合理也是能大大提升用户体验的。<br>其实这一节的内容,大部分都是小程序文档里面有的。只不过好些相关的内容被分散在各个地方,理解和使用起来还是需要查找,这一节就当作整理笔记吧。</p>]]></content>
<summary type="html">
<p>一个小程序有很多页面,每个页面又有各自的线程、生命周期和功能逻辑。关于小程序的生命周期、页面之间的跳转有哪些特殊的地方呢?<br>
</summary>
<category term="小程序双皮奶" scheme="https://godbasin.github.io/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8F%8C%E7%9A%AE%E5%A5%B6/"/>
<category term="教程" scheme="https://godbasin.github.io/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
</feed>