- 小打卡|如何组件化拆分一个200+页面的小程序
大家好,我是小打卡的前端唐驰。刚才金轩正同学分享了基于原生小程序底层架构,在此基础上我为大家分享下如何拆分一个200+页面的小程序,主要通过以下几点来聊一聊小打卡在组件化路上的一些实践 1.背景 2.组件与方案 3.组件间通讯 4.基于组件我们做了哪些事 [图片] [图片] 1. 其实一开始小打卡是没有引入组件化的,因为微信最开始是不支持组件化的。当时js代码已经4k+行了,各种功能代码,有用的没有用的,不知道干什么的代码就躺在那里,一动不动。举个例子,一个头像点击跳转的逻辑搜索了下,遍布在各个页面。修改起来可想而知的胆战心惊。另一个原因就是当时由于业务功能直线上升,很快我们就遇到了代码包超包了。在微信还没有实现分包之前,我们就只能一个一个页面的去review剔除代码,效率极低。这也是促成我们决定寻求出路的原因之一。可是删代码删功能是不能解决问题,期间我们也考虑过h5的方式,跑了demo之后却发现h5方式的多次渲染, 与加载首页白屏,尽管有各种服务端渲染方案,但是我们一致觉得为了用户体验,放弃了。 [图片] 2. 对于小打卡来说,我们不能再任由项目裸奔了,需要一种开发方式来进行约束,主要是有几个诉求: 在之前的项目上,为了方便。功能与功能之间的耦合程度极其的高,各种为了使用方便而随意修改某一个方法。 1.降低页面上各个功能点的耦合程度 我们不希望同一个功能点同样的代码在页面肆意copy,这样带来了极高的维护成本。以至后面无法维护。并且功能的复用不希望是copy,前端与后端不同的是不仅是单单的逻辑复用、更有布局、样式等。 2.提供代码的可复用性、可维护性 对于一个程序员来说,如果你打开一个代码文件。映入眼帘的是密密麻麻的代码,行数达到好几千K行,我相信大家的第一反应是抗拒的,更别说去修改代码,天知道会改出什么问题。 3.降低单一文件的复杂度 4.如果是公共功能的化我们还希望它能够有自己的作用域,保持自己的独立性。 [图片] 3. 根据以上几点,我们用一个页面举例,如何去拆分一个页面,首先我们需要有以下几点认识: 决定一个页面如何组件化的前提是该页面的功能是否是有全局都需要的功能模块 功能模块是否需要与页面其他模块强耦合 单个功能模块逻辑是否过于复杂(占用代码空间过大)——>单纯是为了页面代码的可读性。 不是全拆成组件就是最好的,不能为了组件而组件化 [图片] 4. 说了这么多,其实我们应该首先应该了解下,组件的特性? 专一性(一个组件只干一件事情,或者某一类事情。)功能的高度内聚 比如说右侧的feed集上的头像、它是一个组件、就负责显示头像跟跳转,其他的事它都不参与 可配置(能够适应通过设置属性值的方式来输出不同的东西)输入影响部分输出 然后我们同时可以设置头像组件上的size属性来设置头像在不同页面下的大小样式 生命周期(组件可以在自身或者说所在页面的生命周期内可以做不同的事情)比如可以在组件生成的时候进行数的初始化、属性值的类型校验、组件销毁时并同时销毁定时器等其他任务 事件传递 (既然要让组件与页面保持独立性,那么组件与页面的通讯交互就得需要一个标准) 右侧的feed组件其实是一个组件集合、我们通过组合不同的组件然后就形成了feed组件。就跟搭积木一样、只需要引入组件就行了。特别方便。 [图片] 5. 说到组件,那么小程序早期的不支持自定义组件开发这就很让人头疼、同样的feed组件我们经历了几乎三个版本的大改动、从最开始的直接写在页面里,后台使用template方式、再到后来的自定义组件方式。所以我们的演进步骤就成了page->template->component, 这儿列了一个表格对比了下几种组件化方式的对比。 可以看到,include的方式其实是最鸡肋的,include的方式其实实际意义上我理解成更多的是代码的切割,并且还不能将(template、wxs)分出去、所以这种方式我们直接pass掉了, 而template的方式其实是我们曾经主力使用的方式、到现在我们也还在使用、相对于include来说,template有了独立的作用域、虽然css、跟js还是与页面共享的。但是已经可以做一些比较简单的事情了。 对于component来说,完完全全的组件,满足了组件的所有要求。 [图片] 6. 先说说template的方式吧,举个列子,这个是我们的使用template构建的头像组件。跟写页面的方式很像、同样是js、wxss、wxml组成。用名称来命名。但是由于微信当时没有很方的方式去引用这些文件,或者说没有一种方法可以打包供我们很方便的使用。但是比起之前直接copy代码的方式、这样通过引用的方式使用其实感觉已经好了很多了。 [图片] [图片] 7. 具体的使用方式我画了张图,对应组件内文件与页面文件的对应方式、这里对于js的引用其实我们是做了一些小动作, 我们在调用Page方法前做了一次page方法与组件方法的check,因为在page代码里我们不能保证所有的方法名不会跟组件内的方法名不会冲突,所以我们做了这个一个检查、 然后mix函数还做了另一个事情就是将page方法与组件方法合并。然后对于mix函数其实我们还可以做很多事情、、比如规范生命周期回调函数放在一个对象内,然后我们自己定义的方法放在另一个对象里,就跟vue一样。 But,在经历了一段template组件化的时间后,我们又觉得这个方式还是有点烂,为什么呢?在使用时仍然不能避免引入众多的文件、虽然我们对js文件做了处理,但是wxss的样式仍然会被污染、js与page仍然共享作用域。并不能成为一个真正的标准组件。好在后来,微信上了自定义组件的功能,接下来聊聊这个标准的微信自定义组件吧。 8. 微信提供了自定义组件的功能后我们也第一时间跟进了,相对于template这种方式来说,现在是真正的独立于页面存在。使用也比之前更为方便与简洁,右图是我们对component的一个项目目录划分。我们将component划分为了公共组件与页面组件、为什么会有页面组件, 1.是为了降低页面代码的复杂度 2.为了好看。 公共组件就不说了,一定是最基础、最通用的组件。 [图片] 9. 转向component方式后有一个问题逐渐便凸显出来了,由于组件的独立作用域,组件间的通讯就成了一个问题,接下来聊一聊组件的事件传递。微信最开始的时候提供了一种triggerEvent的方式,可是这样的方式似乎并不能满足我们某些场景下的需求。后来又提供了page下selectComponent方法来直接操作组件内部的属性与方法。然后还有就是基于我们自己的事件广播机制。这几种方式构成了小打卡现目前最主要的组件与page、组件与组件间的数据交互方式 [图片] 10. 先来说说triggerEvent模式,微信在自定义组件上可以自定义监听函数。我们在组件内将需要向外抛出的事件统一通过this.triggerEvent(‘invoke’,{handler:’fun’,data:{}})这个方法来执行。其中invoke对应了我们绑定在组件标签上的监听函数。而将需要外部执行的方法与数据通过数据的方式传给监听函数。而在page上面我通过统一的监听回调函数去自动执行需要执行的方法、这里的trigger与event都不要我们去手写在组件与page创建的时候底层就已经帮我们预置了,我们只需要关注业务开发就行。这是对于一部分需要page与组件交互的模式。而对于我们想直接操作组件方法而不需要反馈的模式就得使用selectComponent的模式 [图片] 11. 一个简单的列子:全局的toast组件。在需要弹出toast的时候我们想直接调用就行、不用在通过传值给组件、然后由组件来执行显示或隐藏。这类组件我们在组件目录里新增了一个lib的文件。在page里只需要引入这个lib文件然后就可以直接调用toast组件。lib主要是对this.selectCompent与执行逻辑的一个封装。 [图片] 12. 事件发布订阅模式:基于底层的eventBus。简化后我们用在了组件与组件之间的通讯上、特点是简单。 [图片] 13. 解决了组件间的通讯问题,可是对于公共组件的引用仍然让我们觉得麻烦与不畅快、所以我们构建了全局通用模版、它是干什么的呢。它提供给了一些基础的全局组件、比如自定义导航头、toast、loading等等。小打卡所有的页面都通过slot的方式插入到这个模版组件x-page下面。这样就解决了我们需要在每个页面引入公共组件的问题。另一个问题使用自定义导航栏的时定位起点会有状态栏下移动到屏幕左上方。会造成布局的错误。通过x-page可以很好解决这个问题而不用重新布局。并且通信问题也不用担心,都是由x-page组件作为中台来对内对外进行分发与执行。 [图片] [图片] [图片] 14. 通过以上小打卡的开发模式就基本形成。要做的事情还有很多,更多组件的玩儿法,对于现在或者将来我们正在做的。 是构建小打卡的组件与基础sdk的仓库。 拆分组件开发与业务开发。 通过npm包管理的方式来应对越来越多的小程序平台的开发。 或者通过形成小程序插件的方式供其他小伙伴使用。 [图片] [图片] 以上就是我今天分享的内容。谢谢。
2019-04-26 - 如何使用scroll-view制作左右滚动导航条效果
最新:2020/06/13。修改为scroll-view与swiper联动效果,新增下拉刷新以及上拉加载效果。。具体效果查看代码片段,以下文章内容和就不改了 刚刚在社区里看到 有老哥在问如何做滚动的导航栏。这里简单给他写了个代码片段,需要的大哥拿去随便改改,先看效果图: [图片] 代码如下: wxml [代码]<scroll-view class="scroll-wrapper" scroll-x scroll-with-animation="true" scroll-into-view="item{{currentTab < 4 ? 0 : currentTab - 3}}" > <view class="navigate-item" id="item{{index}}" wx:for="{{taskList}}" wx:key="{{index}}" data-index="{{index}}" bindtap="handleClick"> <view class="names {{currentTab === index ? 'active' : ''}}">{{item.name}}</view> <view class="currtline {{currentTab === index ? 'active' : ''}}"></view> </view> </scroll-view> [代码] wxss [代码].scroll-wrapper { white-space: nowrap; -webkit-overflow-scrolling: touch; background: #FFF; height: 90rpx; padding: 0 32rpx; box-sizing: border-box; } ::-webkit-scrollbar { width: 0; height: 0; color: transparent; } .navigate-item { display: inline-block; text-align: center; height: 90rpx; line-height: 90rpx; margin: 0 16rpx; } .names { font-size: 28rpx; color: #3c3c3c; } .names.active { color: #00cc88; font-weight: bold; font-size: 34rpx; } .currtline { margin: -8rpx auto 0 auto; width: 100rpx; height: 8rpx; border-radius: 4rpx; } .currtline.active { background: #47CD88; transition: all .3s; } [代码] JS [代码]const app = getApp() Page({ data: { currentTab: 0, taskList: [{ name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, ] }, onLoad() { }, handleClick(e) { let currentTab = e.currentTarget.dataset.index this.setData({ currentTab }) }, }) [代码] 最后奉上代码片段: https://developers.weixin.qq.com/s/nkyp64mN7fim
2020-06-13 - 如何用小程序实现类原生APP下一条无限刷体验
1.背景 如今信息流业务是各大互联网公司争先抢占的一个大面包,为了提高用户的后续消费,产品想出了各种各样的方法,例如在微视中,用户可以无限上拉出下一条视频;在知乎中,也可以无限上拉出下一条回答。这样的操作方式用户体验更好,后续消费也更多。最近几年的时间,微信小程序已经从一颗小小的萌芽成长为参天大树,形成了较大规模的生态,小程序也拥有了一个很大的流量入口。 2.demo体验 那如何才能在小程序中实现类原生APP效果的下一条无限刷体验? 这篇文章详细记录了下一条无限刷效果的实现原理,以及细节和体验优化,并将相关代码抽象成一个微信小程序代码片段,有需要的同学可查看demo源码。 线上效果请用微信扫码体验: [图片] 小程序demo体验请点击:https://developers.weixin.qq.com/s/vIfPUomP7f9a 3.实现原理 出于性能和兼容性考虑,我们尽量采用小程序官方提供的原生组件来实现下一条无限刷效果。我们发现,可以将无限上拉下一篇的文章看作一个竖向滚动的轮播图,又由于每一篇文章的内容长度高于一屏幕高度,所以需要实现文章内部可滚动,以及文章之间可以上拉和下拉切换的功能。 在多次尝试后,我们最终采用了在[代码]<swiper>[代码]组件内部嵌套一个[代码]<scroll-view>[代码]组件的方式实现,利用[代码]<swiper>[代码]组件来实现文章之间上拉和下拉切换的功能,利用[代码]<scroll-view>[代码]来实现一篇文章内部可上下滚动的功能。 所以页面的dom结构如下所示: [代码]<swiper class='scroll-swiper' circular="{{false}}" vertical="{{true}}" bindchange="bindChange" skip-hidden-item-layout="{{true}}" duration="{{500}}" easing-function="easeInCubic" > <block wx:for="{{articleData}}"> <swiper-item> <scroll-view scroll-top="0" scroll-with-animation="{{false}}" scroll-y > content </scroll-view> </swiper-item> </block> </swiper> [代码] 4.性能优化 我们知道view部分是运行在webview上的,所以前端领域的大多数优化方式都有用。例如减少代码包体积,使用分包,渲染性能优化等。下面主要讲一下渲染性能优化。 4.1 dom优化 由于页面需要无限上拉刷新,所以要在[代码]<swiper>[代码]组件中不断的增加[代码]<swiper-item>[代码],这样必然会导致页面的dom节点成倍数的增加,最后非常卡顿。 为了优化页面的dom节点,我们利用[代码]<swiper>[代码]的[代码]current[代码]和[代码]<swiper-item>[代码]的[代码]index[代码]来做优化,控制是否渲染dom节点。首先,仅当[代码]index <= current + 1[代码]时渲染[代码]<swiper-item>[代码],也就是页面中最多预先加载出下一条,而不是将接口返回的所有后续数据都渲染出来;其次,对于用户已经消费过的之前的[代码]<swiper-item>[代码],不能直接销毁dom节点,否则会导致[代码]<swiper>[代码]的[代码]current[代码]值出现错乱,但是我们可以控制是否渲染[代码]<swiper-item>[代码]内部的子节点,我们设置了仅当[代码]current <= index + 1 && index -1 <= current[代码]时才会渲染[代码]<swiper-item>[代码]中的内容,也就是仅渲染当先文章,及上一篇和下一篇的文章内容,其他文章的dom节点都被销毁了。 这样,无论用户上拉刷新了多少次,页面中最多只会渲染3篇文章的内容,避免了因为上拉次数太多导致的页面卡顿。 4.2 分页时setData的优化 setData工作原理 [图片] 小程序的视图层目前使用[代码]WebView[代码]作为渲染载体,而逻辑层是由独立的 [代码]JavascriptCore[代码] 作为运行环境。在架构上,[代码]WebView[代码] 和 [代码]JavascriptCore[代码] 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 [代码]evaluateJavascript[代码] 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 [代码]JS[代码] 脚本,再通过执行 [代码]JS[代码] 脚本的形式传递到两边独立环境。 而 [代码]evaluateJavascript[代码] 的执行会受很多方面的影响,数据到达视图层并不是实时的。 每次 [代码]setData[代码] 的调用都是一次进程间通信过程,通信开销与 setData 的数据量正相关。 [代码]setData[代码] 会引发视图层页面内容的更新,这一耗时操作一定时间中会阻塞用户交互。 [代码]setData[代码] 是小程序开发中使用最频繁的接口,也是最容易引发性能问题的接口。 避免不当使用setData [代码]data[代码] 应仅包括与页面渲染相关的数据,其他数据可绑定在this上。使用 [代码]data[代码] 在方法间共享数据,会增加 setData 传输的数据量,。 使用 [代码]setData[代码] 传输大量数据,通讯耗时与数据正相关,页面更新延迟可能造成页面更新开销增加。仅传输页面中发生变化的数据,使用 [代码]setData[代码] 的特殊 [代码]key[代码] 实现局部更新。 避免不必要的 [代码]setData[代码],避免短时间内频繁调用 [代码]setData[代码],对连续的setData调用进行合并。不然会导致操作卡顿,交互延迟,阻塞通信,页面渲染延迟。 避免在后台页面进行 [代码]setData[代码],这样会抢占前台页面的渲染资源。可将页面切入后台后的[代码]setData[代码]调用延迟到页面重新展示时执行。 优化示例 无限上拉刷新的数据会采用分页接口的形式,分多次请求回来。在使用分页接口拉取到下一刷的数据后,我们需要调用[代码]setData[代码]将数据写进[代码]data[代码]的[代码]articleData[代码]中,这个[代码]articleData[代码]是一个数组,里面存放着所有的文章数据,数据量十分庞大,如果直接[代码]setData[代码]会增加通讯耗时和页面更新开销,导致操作卡顿,交互延迟。 为了避免这个问题,我们将[代码]articleData[代码]改进为一个二维数组,每一次[代码]setData[代码]通过分页的 [代码]cachedCount[代码]标识来实现局部更新,具体代码如下: [代码]this.setData({ [`articleData[${cachedCount}]`]: [...data], cachedCount: cachedCount + 1, }) [代码] [代码]articleData[代码]的结构如下: [图片] 4.3 体验优化 解决了操作卡顿,交互延迟等问题,我们还需要对动画和交互的体验进行优化,以达到类原生APP效果的体验。 在文章间上拉切换时,我们使用了[代码]<swiper>[代码]组件自带的动画效果,并通过设置[代码]duration[代码]和[代码]easing-function[代码]来优化滚动细节和动画。 当用户阅读文章到底部时,会提示下一篇文章的标题等信息,而在页面上拉时,由于下一篇文章的内容已经加载出来了,这样在滑动过程中会出现两个重复的标题。为了避免这种情况出现,我们通过一个占满屏幕宽高的空白[代码]<view>[代码]来将下一篇文章的内容撑出屏幕,并在滚动结束时,通过[代码]hidden="{{index !== current && index !== current + 1}}"[代码]来隐藏这个空白[代码]<view>[代码],并对这个空白[代码]<view>[代码]的高度变化增加动画,来实现下一篇文章从屏幕底部滚动到屏幕顶部的效果: [代码].fake-scroll { height: 100%; width: 100%; transition: height 0.3s cubic-bezier(0.167,0.167,0.4,1); } [代码] [图片] 而当用户想要上拉查看之前阅读过的文章时,我们需要给用户一个“下滑查看上一条”提示,所以也可以采用同上的方式,通过一个占满屏幕宽高的提示语[代码]<view>[代码]来将上一篇文章的内容撑出屏幕,并在滚动结束时,通过[代码]wx:if="{{index + 1 === current}}"[代码]来隐藏这个提示语[代码]<view>[代码],并对这个提示语[代码]<view>[代码]的透明度变化增加动画,来实现下拉时提示“下滑查看上一条”的效果: [代码].fake-previous { height: 100%; width: 100%; opacity: 0; transition: opacity 1s ease-in; } .fake-previous.show-fake-previous { opacity: 1; } [代码] 至此,这个类原生APP效果的下一条无限刷体验的需求的所有要点和细节都已实现。 记录在此,欢迎交流和讨论。 小程序demo体验请点击:https://developers.weixin.qq.com/s/vIfPUomP7f9a
2019-06-25 - 微盟小程序性能优化实践(上)
微盟小程序性能优化要分享的内容分为三部分,启动性能加载、首屏加载的体验建议和渲染性能优化。 今天主要讲启动性能加载的性能优化实践,先看启动加载过程的流程: [图片] · 公共库注入 · 资源准备(基础UI创建,代码包下载) · 业务代码注入和渲染 · 渲染首屏 · 异步请求 优化方案 1、控制代码包大小 · 开启开发者工具中的 “ 上传代码时自动压缩 ” · 及时清理无用代码和资源文件 · 减少代码包中的图片等资源文件的大小和数量 · 将图片等资源文件放到CND中 · 提取公共样式 · 代码压缩,图片格式,压缩,或者外联 · 公共组件提取,代码复用 2、 分包加载 分包加载过程流程 [图片] 在开发小程序分包项目时,会有一个或者多个分包,其中没有分包小程序必须包含一个主包,即放置启动页面或者tabBar页面,以及一些分包都需要用到的公共资源脚本。 在小程序启动时,默认会下载主包并且启动主包内页面,如果用户打开分包内的页面,客户端会把分包下载下来,下载完之后再进行展示。 · 分包加载流程 [图片] 使用分包加载的优点: · 能够增加小程序更大的代码体积,开发更多的功能 · 对于用户,可以更快地打开小程序,同时不影响启动速度 使用分包加载有哪些限制: · 整个小程序所有分包不能超过8M · 单个主包/分包不能超过2M 3、 运行机制优化 · 代码中减少立即执行的代码数量 · 避免高开销和长时间阻塞代码 · 业务代码都写入页面的生命周期中 · 做好缓存策略 4、 数据管理优化 · 首屏请求数量尽量不能超过5个,超过的可以做接口合并(node层,服务端都可以处理) · 对多次提交的数据可以做合并处理 首屏加载的体验建议和渲染性能优化这两部分的内容,将在下次分享给大家。微盟小程序性能优化实践(下)
2018-09-25 - 微盟小程序性能优化实践(下)
在上一篇分享中,给大家分享了启动性能加载的相关实践,详情可戳右方链接:微盟小程序性能优化实践(上) 接下来和大家聊一聊首屏加载的体验建议和渲染性能优化。 二、首屏加载的体验建议 · 提前请求 异步数据请求不需要等待页面渲染完成。 · 利用缓存 利用storage API对异步请求数据进行缓存,二次渲染页面,再进行后台更新。 · 避免白屏 先展示页面骨架和基础内容。 三、渲染性能优化 · 每次 setData 的调用都是一次进程间通信过程,通信开销与 setData 的数据量正相关 · setData 会引发视图层页面内容的更新,这一耗时操作一定时间中会阻塞用户交互 · setData 是小程序开发使用最频繁,也是最容易引发性能问题的 · 在页面列表中使用懒加载+动态移除非可视区域范围内的内容,让dom小下去 · 耗时比较长的js做到异步,不要阻塞进程(js属于单线程) · 少使用scroll-view,这个组件对性能的影响太大,单纯的只是需要一块区域滚动,可以使用view+css的方式实现 · 在页面频繁滚动触发回调函数,会导致页面卡顿,这时必须和防抖动函数或者节流函数相结合做一些处理 · 页面中的图片可以使用懒加载的方式(添加lazy-load属性,只针对page与scroll-view下的image有效) · 页面跳转要做一下限制,如果页面快速点击会出现跳转多次的情况 避免不正当的使用setData · 使用data在方法间共享数据,可能增加setData传输的数据量。data 应该仅仅包含与页面渲染相关的数据 · 使用setData 传输大量的数据,通讯耗时与数据量成正比,导致页面更新延迟 可能造成页面更新开销增加。所以setData 仅传输页面需要的数据,使用setData 的特殊Key 实现局部更新 · 短时间内频繁调用setData (操作卡顿、交互延迟 阻塞通信、页面渲染延迟),对连续的setData 调用进行合并 · 后台进行页面setData (抢占前台页面的渲染资源) 例如 活动定时器 再页面切入后台时应该将关闭 避免不正当的使用onPageScroll · 只在必要的时候监听pageScroll 事件 · 避免在onPageScroll 中执行复杂的逻辑 · 避免在onPageScroll 中频繁调用setData · 避免频繁查询节点信息(SelectQuery) 部分场景建议使用节点布局相交状态 · 监听( IntersectionObserver) 替代 使用自定义组件 在需要频繁更新的场景下,自定义组件的更新只在组件内部进行,不受页面部分内容的复杂性的影响。 使用体验评分功能 在开发过程中使用体验评分可以测试出代码中一些需要优化的点,准备定位到影响性能的原因,很大程序提高页面的性能。
2018-09-25 - 模拟器里面看不到鼠标位置或不显示鼠标
其他问题有答案说加上鼠标轨迹,这个方法确实管用,但是治标不治本。这里推荐个治本的方法: 一般这类问题出现在外接显示器或者更换新的显示器的时候,按如下操作 [图片] 保存即可。
2019-01-04 - 组件加载外部类externalClasses和addGlobalClass到底是
组件引用外部类,文档是这么说的: https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html 可是externalClasses和addGlobalClass到底是什么逻辑 官方能否在文档详细说明一下 经过NNNNNN验证,发现这2个是互斥的,同一个类不能同时使用2种方法 addGlobalClass引用外部类的时候, 为什么有的时候起作用,有时候有不可以呢(需要在类前面写上'view.'才能生效) 头大头大啦~~~~~~~~~~~
2018-12-27