微信小程序全面实战,架构设计 && 躲坑攻略

AdrienneSpr 7年前
   <p>最近集中开发了两款微信小程序,分别是好奇心日历(每天一条辞典+一个小投票)和好奇心日报(轻量版),直接上图:</p>    <p><img src="https://simg.open-open.com/show/b8d36a032f50cf60d873dcbb3b1d42c7.png"></p>    <p>本文将结合具体的实战经验,主要介绍微信小程序的基础知识、开发中遇到的难点、项目的架构设计、最佳实践以及踩过的坑。 文章内容较多,如果想看架构设计和躲坑技巧,请直接浏览后面的正文 ,简书没有目录,也挺伤感的。</p>    <p>值得再次声明的是: <strong>微信小程序的内容部分是hybrid模式,并非原生</strong> ,所以性能并不好,绑定的tap事件也有明显的延迟。</p>    <p><img src="https://simg.open-open.com/show/813cbb955d56425d661354a5b6e99783.png"></p>    <p>每一个由边框围起来的部分,都是一个最小粒度的原生view</p>    <p>如上图所示,每一个由边框围起来的部分,都是一个最小粒度的原生view,可以看出,整个微信小程序的内容部分,就是一个原生view。</p>    <h2>小程序有哪些基础知识?</h2>    <p>一个完整的微信小程序是由一个App实例和多个Page实例构成,其中App实例表示该小程序应用,多个Page表示该小程序的多个页面。</p>    <p>此外,微信小程序并没有提供自定义组件的方式,这就导致微信小程序在开发较复杂应用时,可能会比较艰难。</p>    <p>微信小程序本身很简单,和一个模板语言的难度几乎相当,翻翻官方教程就可以开始动手搞。</p>    <p>我也建议大家先动起来,然后再细致啃啃官方文档。由于微信官方文档仍在不断大幅更新中,所以务必查看最新官方文档。</p>    <p>微信小程序的基础知识主要分为以下几个部分:</p>    <p>✦ 两种配置文件 && 两个核心函数</p>    <p>✦ WXML模板语法,页面渲染</p>    <p>✦ 页面间的跳转</p>    <p>✦ 交互事件</p>    <p>✦ 官方组件和官方API</p>    <p>后文会就每个部分简单介绍介绍...</p>    <p>两种配置文件 && 两个核心函数</p>    <p>app.json 应用的全局配置文件</p>    <p>app.json 是针对微信小程序的全局配置,主要包含以下几个配置:</p>    <p>✦ pages:页面路径的数组,表示小程序要加载的所有页面,其中数组第一项代表小程序的初始页面。</p>    <p>✦ window:微信原生功能,定制化不强。可设置小程序的状态栏、导航条、标题以及窗口背景色。</p>    <p>✦ tabBar:微信原生功能,定制化不强。适用于常规的Tab应用,Tab栏可置于顶部或底部;tabBar是一个数组,仅支持2-5个tab。</p>    <p>✦ networkTimeout:配置小程序网络请求的超时时间。</p>    <p>✦ debug:调试模式开关,开发模式下建议开启,正式发布别忘了关闭。</p>    <p><img src="https://simg.open-open.com/show/b4ce07ac1cc499c7140af5ac527bfb67.png"></p>    <p>app.json配置的主要区域</p>    <p>page.json 页面的全局配置文件</p>    <p>除了上面提到的全局配置,每个页面还可以单独配置 page.json , page.json 会覆盖 app.json 中的配置,并只对当前页面生效。</p>    <p>page.json 只能对window配置,有两个比较有用的配置项分别是:</p>    <p>✦ enablePullDownRefresh:是否开启下拉刷新</p>    <p>✦ disableScroll:只能在page.json配置,禁止页面上下滚动,猜测可以实现完美滑屏滑动(未验证)</p>    <p>App() 小程序注册入口,全局唯一</p>    <p>App()用来注册一个小程序,全局只有一个,全局的数据也可以放到这里面来操作。</p>    <pre>  <code class="language-javascript">// 注册微信小程序,全局只有一个  let appConfig = {      // 小程序生命周期的各个阶段      onLaunch: function(){},      onShow: function(){},      onHide: function(){},      onError: function(){},        // 自定义函数或者属性      ...  };  App(appConfig);    // 在别的地方可以获取这个全局唯一的小程序实例  const app = getApp();</code></pre>    <p>小程序并没有提供销毁的方式,所以只有当小程序进入后台一定时间、或者系统资源占用过高的时候,才会被真正的销毁。</p>    <p>Page() 页面注册入口</p>    <p>Page()用来注册一个页面,维护该页面的生命周期以及数据。</p>    <pre>  <code class="language-javascript">// 注册微信小程序,全局只有一个  let pageConfig = {      data: {},      // 页面生命周期的各个阶段      onLoad: function(){},      onShow: function(){},      onReady: function(){},      onHide: function(){},      onUnload: function(){},      onPullDownRefresh: function(){},      onReachBottom: function(){},      onShareAppMessage: function(){},        // 自定义函数或者属性      ...  };  Page(pageConfig);    // 获取页面堆栈,表示历史访问过的页面,最后一个元素为当前页面  const page = getCurrentPages();</code></pre>    <p>关于各个生命周期的细节以及流程,参考下图,可以细细品味:</p>    <p><img src="https://simg.open-open.com/show/b6f2be566b81c5d25b1b77dc82844243.png"></p>    <p>Paste_Image.png</p>    <p>app.json 和 page.json 维护了应用和页面的配置属性。 App() 和 Page() 维护了应用和页面的各个生命周期以及数据。</p>    <p>那么, APP 和 Page 如何将数据传递到页面呢?页面又是如何渲染呢?</p>    <p>WXML模板语法,页面渲染</p>    <p>小程序虽然是hybrid模式,但并不使用HTML渲染,而是全部通过自定义标签来渲染页面。这样做的好处我不清楚,但问题却不少:不能跨浏览器、富文本解析困难,iframe视频不支持,没办法外链跳转。</p>    <p>和所有的模板语言一样,WXML支持数据绑定、条件渲染、循环、模块化等功能。</p>    <p>数据绑定</p>    <p>在 WXML 中可以使用 {{}} 将 Page 的变量或者表达式包裹起来,实现数据绑定,举个例子:</p>    <pre>  <code class="language-javascript">// 将message的值渲染到view中  <view> {{ message }} </view>    // 将id的值渲染到view的id属性中  <view id="item-{{id}}"> </view>    // 根据isSelected的值,输出不同的class  <view class="{{isSelected ? 'selected' : ''}}"> HelloWorld </view>    // 结合template,可以传入更复杂的数据  <template is="objectCombine" data="{{...article, categoty, tag: '埃隆马斯克'}}"></template></code></pre>    <p>条件渲染</p>    <p>条件渲染,适合根据数据输出不同状态的 WXML,举个例子:</p>    <pre>  <code class="language-javascript"><view wx:if="{{length > 5}}"> 1 </view>  <view wx:elif="{{length > 2}}"> 2 </view>  <view wx:else> 3 </view></code></pre>    <p>循环渲染</p>    <p>循环渲染,适合遍历数据输出多段 WXML,举个例子:</p>    <pre>  <code class="language-javascript">// wx:for 表示需要遍历的数据  // wx:key 使用唯一的字段来标识,有利于提升性能  // wx:for-index 表示数组的下标  // wx:for-item 表示数组的元素  <view wx:for="{{[{id:1, message: 'HelloWorld1'}, {id:2, message: 'HelloWorld2'}]}}"       wx:key="id"       wx:for-index="idx"       wx:for-item="item">    {{idx}}: {{item.message}}  </view></code></pre>    <p>wx:key 有利于提升重新渲染时的效率,建议添加</p>    <p>模块化</p>    <p>WXML的模块化,可以让我们复用一些wxml片段,还挺重要的,举个例子:</p>    <pre>  <code class="language-javascript">// 引入wxml模块  <import src="../../components/grid-article/index"></import>    <block wx:for="{{posts}}" wx:for-item="post" wx:key="id">      // 调用wxml模块,同时可传入数据      <template is="grid-article" data="{{post}}"></template>  </block></code></pre>    <p>数据和页面的状态是一一对应的,微信小程序中,设计一份好的数据结构,对于Page和页面的代码都有很大的帮助。</p>    <p>微信小程序并不支持a标签,那么多个页面之间如何跳转呢?</p>    <p>页面间的跳转</p>    <p>小程序以栈的形式维护了历史访问的所有页面,并提供了多种页面间的跳转方式;结合前文提到的App()和Page()的各个生命周期,不同的跳转方式和不同的生命周期关联,如下图:</p>    <p><img src="https://simg.open-open.com/show/660841c96d2f7fe5b3c17c4df84b5355.png"></p>    <p>Paste_Image.png</p>    <p>举个例子,Tab 切换对应的生命周期(以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面为例)</p>    <p><img src="https://simg.open-open.com/show/94e22fd67f74b617a29283094e97895e.png"></p>    <p>Paste_Image.png</p>    <p>好了,APP和Page负责维护小程序的生命周期和数据,模板负责接受数据完成页面渲染,页面间的跳转负责将多个页面贯穿起来,那么,如何发生交互呢?接下来我们简单说一下事件。</p>    <p>交互事件</p>    <p>事件绑定</p>    <pre>  <code class="language-javascript">// bindtap 和 catchtap的区别在于  // bindtap 不会阻止事件冒泡  // catchtap会冒泡事件冒泡  <view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view>  <view id="tapTest" data-hi="WeChat" catchtap="tapName"> Click me! </view>    // 绑定的函数tapName只是一个函数名称,默认接受一个event对象作为参数  Page({    tapName: function(event) {      console.log(event)    }  })</code></pre>    <p>接下来,另一个问题是: tapName() 如何接受自定义参数呢?</p>    <p>事件传参</p>    <p>传递自定义参数主要有两种方式:</p>    <p>第一种:将参数绑定到wxml标签上,然后通过 event.target.dataset 获取</p>    <p>第二种:直接使用Page.data或其他数据</p>    <p>到目前为止,一个完整的小程序框架已经实现</p>    <p>✦ 小程序只有逻辑和视图两部分,而且不提供组件化解决方案</p>    <p>✦ 逻辑主要包含四个东西:两个配置文件 && 两个核心函数</p>    <p>✦ 视图很简单,模板语法稍微有点不完善</p>    <p>✦ 逻辑层的数据绑定到视图层是由小程序框架自动支持,数据变化,视图自动变化</p>    <p>✦ 视图层到逻辑层的,主要通过事件的方式来实现</p>    <p>✦ 视图之间的跳转,小程序也提供了它自己的方式,并不支持a标签</p>    <p>框架有了,小程序还提供了官方组件以便快速开发,提供了API以增强应用能力。</p>    <p>这块就不具体介绍了,也需要注意小程序的官方文档还在大规模的更新中,务必查看最新版</p>    <p>官方组件: <a href="/misc/goto?guid=4959733727317230205" rel="nofollow,noindex">https://mp.weixin.qq.com/debug/wxadoc/dev/component/?t=20161222</a></p>    <p>官方API: <a href="/misc/goto?guid=4959733727411567963" rel="nofollow,noindex">https://mp.weixin.qq.com/debug/wxadoc/dev/api/?t=20161222</a></p>    <p>微信小程序的基础知识就是以上的内容,并不复杂,边查边写都可以。</p>    <p>接下来会介绍更进阶一些的内容,内容主要结合好奇心日报这个小程序项目,先看效果:</p>    <p><img src="https://simg.open-open.com/show/85f1cb867d2719a8b483e95b2d0347d6.gif"></p>    <p>好奇心日报实际效果图</p>    <h2>如何设计微信小程序?</h2>    <p>构建系统 && 目录结构</p>    <p>构建系统</p>    <p>由于微信小程序本身对工程化几乎没有任何的支持,所以动手搭建一份: <a href="/misc/goto?guid=4959733727494953964" rel="nofollow,noindex">wxapp-redux-starter</a> 。</p>    <p>使用gulp进行编译构建,主要功能包括:</p>    <p>✦ 集成了 Redux ,数据管理更方便</p>    <p>✦ 开发过程中,使用 xml 取代 wxml ,对开发工具更友好</p>    <p>✦ 开发过程中,使用 less 取代 wxss ,功能更强大</p>    <p>✦ 引入 es-promise ,以便可以创建并使用 Promise</p>    <p>✦ 添加 promisify 工具函数,可以便捷的将官方 Api 转换成 Pormise 模式</p>    <p>✦ 引入 normalizr ,可以将数据扁平化,更方便进行数据管理</p>    <p>✦ 引入 babel 自动进行 ES2015 特性转换</p>    <p>✦ 对 wxml/wxss/js/img 压缩,相对开发者工具提供的压缩,会减小一丢丢体积。</p>    <p>目录结构设计</p>    <p>按照pages、components、redux、vendors/libs、images几个核心部分拆分,直接上目录。</p>    <p><img src="https://simg.open-open.com/show/523dc1c3f1b7b4a3351ebb5ae106c061.png"></p>    <p>小程序工程目录</p>    <p>✦ dist目录 :构建输出的文件存放到这个目录。</p>    <p>✦ src目录 :开发模式的文件,包括app、页面、组件、图片等静态资源、辅助函数库、Redux数据管理器、第三方工具库。</p>    <p>✦ gulpfile.js :不用多说,gulp构建任务的入口文件。</p>    <p>✦ package.json :不用多说,管理者构建任务的依赖。</p>    <p>✦ thirdPlugins :由于小程序并不支持直接使用npm,我们可以自主拉取构建,然后拷贝到vendors里,有时候需要简单修改。</p>    <p>构建系统会将src目录下的代码,编译处理后输出到dist目录,小程序开发工具只需要引入dist目录即可。</p>    <p>有了构建工具,代码开发起来更舒心,但很快就遇到另外一个糟心的问题,那就是如何管理散布在各处的数据?</p>    <p>要知道,微信小程序并没有提供组件化方案,所以 <strong>把组件写成无状态组件</strong> 特别适合,但是页面管理太多数据很凌乱,有什么办法可以将数据集中管理呢?</p>    <p>引入Redux进行数据集中管理</p>    <p>关于Redux相关的内容,之前有三篇博客详细介绍,有兴趣的建议先移步:</p>    <p>Redux整体介绍: <a href="/misc/goto?guid=4959732549854910684" rel="nofollow,noindex">Redux 入门教程,应用的状态管理器</a></p>    <p>对State进行横向和纵向拆分设计: <a href="/misc/goto?guid=4959732549950605244" rel="nofollow,noindex">State设计,Redux 开发第一步</a></p>    <p>Reducer的最佳实践: <a href="/misc/goto?guid=4959732550031144497" rel="nofollow,noindex">Reducer 最佳实践,Redux 开发最重要的部分</a></p>    <p>这儿就简单介绍一下,如何在微信小程序中引入Redux 以及 如何将微信小程序和Redux连接起来。</p>    <p>引入Redux</p>    <p>直接在 thirdPlugins目录 运行 yarn add redux / npm install redux ,等redux安装好了之后,将 dist目录 的 redux.js/redux.min.js 拷贝到vendors目录中。</p>    <p>需要进行简单的修改才能使用,将压缩版208行代码 (i) 改成 (i || {}) 即可。</p>    <p><img src="https://simg.open-open.com/show/eef6d59a970ba32d635b281d82757b83.png"></p>    <p>简单修改,Redux就可以正常使用</p>    <p>连接微信小程序和Redux</p>    <p>将Redux和微信小程序连接起来才会真的有用处。找了个现成的方案 charleyw/wechat-weapp-redux ,可以直接使用。</p>    <p>一个完整的Redux方案如下,包括:将Store注入到App中、将state的数据和reducer的方法映射到Page中。一旦state发生变化,Page.data也会更新,进而触发页面的重新渲染。</p>    <pre>  <code class="language-javascript">// APP的逻辑  import { createStore, applyMiddleware, combineReducers } from './vendors/redux.js';  import thunk from './vendors/redux-thunk.js';  import { Provider } from './vendors/weapp-redux.js';    // import reducers  import { rootReducer } from './redux/reducer.js';    // 从Storage读取数据  let entities = wx.getStorageSync('entities') || {};    const store = createStore(      rootReducer, {          // 将读取的数据注入到store中          entities: entities      },      applyMiddleware(          thunk      )  );    let appConfig = {      onLaunch: function() {},        onHide: function() {          let state = store.getState(),              cacheEntities = {};            // 体积大于2M,直接清空,防止缓存占用过大体积          if (sizeof(state.entities) <= 2 * 1024 * 1024) {              cacheEntities = state.entities;          }            // 退出时将entities缓存下来          wx.setStorageSync('entities', cacheEntities);      }  };    App(Provider(store)(appConfig));      // Page的逻辑  import { connect } from '../../vendors/weapp-redux.js';    import { fetchArticleDetail } from '../../redux/models/articles.js';    let pageConfig = {      data: {          id: 0,          postsHash: {}      },      onLoad: function(params) {          var me = this,              { id, postsHash } = me.data;            me.fetchArticleDetail(id, function() {}, function() {});      }  }    // 考虑到列表页已经获取到部分数据  // 为了在详情页第一时间利用这些数据,我们将params传入  // 防止以后需要用data的数据,我们将data也一并传入  let mapStateToData = (state, params, data) => {      return {          id: params.id,          postsHash: state.entities.posts      }  };    let mapDispatchToPage = dispatch => ({      fetchArticleDetail: (id, callback, errorCallback) => dispatch(fetchArticleDetail(id, callback, errorCallback)),  });    pageConfig = connect(mapStateToData, mapDispatchToPage)(pageConfig)  Page(pageConfig);</code></pre>    <p>需要注意的是,为了保证第一时间能拿到数据,我们对 wechat-weapp-redux/src/connect.js 做了优化调整,修改的地方如下</p>    <pre>  <code class="language-javascript">// 修改了以下两个函数  // 可以对照原项目修改,也可以直接拿我的模板项目使用  function handleChange(options) {      if (!this.unsubscribe) {          return      }        const state = this.store.getState();      // 将data也一并传入      const mappedState = mapState(state, options, this.data);      if (!this.data || shallowEqual(this.data, mappedState)) {          return;      }      this.setData(mappedState)  }    function onLoad(options) {      this.store = app.store;      if (!this.store) {          warning("Store对象不存在!")      }      if (shouldSubscribe) {          this.unsubscribe = this.store.subscribe(handleChange.bind(this, options))          // 第一次处理的时候也传入options          handleChange.apply(this, [options])      }      if (typeof _onLoad === 'function') {          _onLoad.call(this, options)      }  }</code></pre>    <p>引入Redux的优势</p>    <p>引入Redux的好处在于可以集中管理数据,并且让Page的代码保持绝对简单,让所有的组件都变成简单可复用的无状态组件。</p>    <p>此外,Redux还让离线缓存更方便,数据复用更简单。</p>    <p>引入Redux解决了数据散布各处的问题,参考Redux的管理思路,我们构思了一套简单组件化解决方案:假设我们把所有的组件都设计成无状态组件,而组件的数据来源是Page.data,那么我们是否也可以将组件数据的管理抽离到一个单独的文件中呢?接下来讲讲我们使用的简单的组件化解决方案。</p>    <p>简单的组件化解决方案</p>    <p>这份组件化解决方案的核心就在于把组件的关联数据集中起来管理,只暴露出默认数据和数据的操作函数。</p>    <p>比如好奇心日报的详情页有个Toolbar,该Toolbar并不复杂,主要提供返回和点赞功能,其中点赞功能只对文章详情有效,研究所详情页会将点赞功能隐藏。</p>    <p><img src="https://simg.open-open.com/show/6fbb88a7e6787aae32601236508fe5e1.png"></p>    <p>Toolbar组件</p>    <pre>  <code class="language-javascript">// components/toolbar/index.js 文件  // 仅提供默认值,不需要和page中的数据保持同步  let defaultData = {      isPraised: false,      praiseCount: 0,      showPraiseIcon: false,  };  // 切换点赞状态  function togglePraise() {      // 本质上是修改Page.data中的toolbarData  }  // 返回上一级  function navigateToBack(wx) {      wx.navigateBack({ delta: 1 });  }  module.exports = {      defaultData,        togglePraise,      navigateToBack  }    // pages/articles/show.js 文件  import Toolbar from '../../components/toolbar/index.js';    let pageConfig = {      data: {          // 其他数据          id: 0,          // Toolbar数据,单独的一份数据,便于维护          toolbarData: Toolbar.defaultData      },      // 点赞或者取消赞      togglePraise: function() {          let me = this;            Toolbar.togglePraise.call(me);      }  }    // 这儿的组件化并不是真正的组件化  // 而是将组件相关的逻辑和函数抽离到单独的文件中,保证Page代码清晰。  // 同时也为这部分组件逻辑复用提供了可能。  // 本质上来说,抽离出的组件都是“操作Page.data的工具函数”,他们也是纯函数,和“操作state的reducer”类似。</code></pre>    <p>这种Redux的组件化解决方案既简单又好用,保持一定的代码规范即可。这样设计当然是为了复用,同时也让Page的逻辑保持极度精简。</p>    <h2>开发中遇到了哪些难点 && 微信小程序有多少坑?</h2>    <p>微信小程序目前的确算不上公测的版本,开发者工具不完善、真机表现和开发环境差异很大、部分组件性能较差、部分功能有缺陷,只有经历了这些大坑,才会真的知道你有多“爱”微信小程序。这儿总结了开发中的难点以及微信小程序中遇到的比较明显的坑。</p>    <p>富文本解析</p>    <p>微信小程序并不支持HTML标签,所以对于富文本解析来说,难度较大,而且有些功能还没有办法实现,比如:iframe视频、连接跳转等</p>    <p>这块功能建议由后台统一转换,如果非得前端转换,建议参考下面的思路。</p>    <p>非常感谢 <a href="/misc/goto?guid=4959726374493306468" rel="nofollow,noindex">wxParse</a> 这款组件,替我省了不少时间。推荐大家使用,期间遇到一些问题,也分享给大家。</p>    <p>✦ wxParse 默认层级只支持10层html嵌套,如果想要支持更深的层级,在wxParse.xml复制几份template即可。</p>    <p>✦ wxParse 提供了图片加载成功的回调 wxParseImgLoad ,很好用。但如果富文本中的图片已经预设宽高比,那么可以不用依赖该回调,在html2jons.js中根据屏幕宽度直接计算出图片高度,先占位,可以避免页面频繁抖动的问题。</p>    <p>✦ 如果你的富文本中有自定义模块,对wxParse.xml中的template进行改造即可。</p>    <p><img src="https://simg.open-open.com/show/15bebcfcfdd12cab7e24c4c346e3daf9.png"></p>    <p>自定义模块样式</p>    <p>数据扁平化</p>    <p>具体如何扁平化,请移步上一篇博客 <a href="/misc/goto?guid=4959732549950605244" rel="nofollow,noindex">State设计,Redux 开发第一步</a> 。</p>    <p>这儿只简单介绍下扁平化应用场景:</p>    <p>好奇心日报的研究所是三级表结构: papers > questions > options ,后台返回的数据是三级嵌套数据,如果想要修改option.selected字段,需要三级嵌套循环!如果想要获取所有选中的option,需要三级嵌套循环!</p>    <p>页面展现速度优化</p>    <p>数据复用,比如复用列表页的数据,可以让详情页的标题等字段第一时间呈现出来。</p>    <p>离线缓存,同样可以让列表页和详情页第一时间呈现出来,甚至有可能减少请求数量。</p>    <p>无论是数据复用还是离线缓存,配合数据扁平化,都非常好用。</p>    <p>小程序默认设置代理,会和Shadowsocks等V*N冲突(最新版又坏了)</p>    <p>解决方法很简单,设置微信小程序不使用代理;或者临时关闭V*N即可。</p>    <p>上一版开发者工具已经解决该问题, <strong>最新版又坏了</strong> 。</p>    <p>最新版微信小程序移除了对Promise的支持。</p>    <p>开发者自行引入兼容库即可,推荐 <a href="/misc/goto?guid=4959732070239396322" rel="nofollow,noindex">es6-promise</a> 。使用的时候,直接引入Promise即可。</p>    <pre>  <code class="language-javascript">// 引入Promise  import Promise from '../vendors/es6-promise.js';    // 用Promise封装wx.request网络请求  function request(method = 'GET') {      return function(url, data = {}) {          return new Promise(function(resolve, reject) {              wx.request({                  url,                  data,                  method,                  header: {                      'Content-Type': 'application/json'                  },                  success: function(res) {                      let { statusCode, errMsg, data } = res;                        if (statusCode == 200 && data.meta && data.meta.status == 200) {                          resolve(data.response);                      } else {                          reject('网路请求错误,请稍后再试~');                      }                  },                  fail: function(err) {                      reject('网路请求不符合规范,请检查域名是否符合要求~');                  }              });          })      }  }  export const GET = request('GET');  export const POST = request('POST');  export const PUT = request('PUT');  export const DELETE = request('DELETE');    // 用Promise封装小程序的其他API  export const promisify = (api) => {      return (options, ...params) => {          return new Promise((resolve, reject) => {              api(Object.assign({}, options, { success: resolve, fail: reject }), ...params);          });      }  }  // 使用  const getLocation = promisify(wx.getLocation);</code></pre>    <p>不清楚微信为何会临时移除Promise,统一内置不也挺好?</p>    <p>小程序不能实现完美的fullpage效果,会出现上下拉扯的感觉(最新版预计已修复,待实际验证)。</p>    <p>小程序一旦滚动顶部或者底部,继续滑动的时候,就会出现拉扯现象。而这个拉扯现象还无法禁止。</p>    <p>最新版可以对页面配置disableScroll,预计可以修复这个问题,待实际验证。</p>    <p><img src="https://simg.open-open.com/show/5d19c11e3552bcf8663073c7c874153c.png"></p>    <p>fullpage效果</p>    <p>swiper组件不支持轮播,性能差,文档模糊(部分最新版已修复)</p>    <p>✦ swiper组件之前并不支持轮播,最新版已修复</p>    <p>✦ swiper组件之前是通过设置left属性来实现动画,在小米5、华为V8等高端等安卓机上能够感受到明显的卡顿,当然原因是X5内核引起的。最新版已修复,换成了transform,性能有一定的提升。</p>    <p><img src="https://simg.open-open.com/show/242d17b3218c9ad83f3778380841384f.png"></p>    <p>swiper性能提升</p>    <p>✦ 文档并未标记可以垂直轮播,但其实是可以的。</p>    <pre>  <code class="language-javascript">// 简单设置vertical即可,但由于官方文档并未备注,尽量不要使用。可以自己开发一个swiper组件。  <swiper vertical="true"></swiper></code></pre>    <p>✦ swiper组件的小圆点其实是可以定制化的,但是官方文档并未说明,而且开发者工具也看不出来,只有鼠标hover到元素上的时候可以看到相关的class,简单猜测一下,最后分析出来它的实现方式。</p>    <p><img src="https://simg.open-open.com/show/19a7b7b3ccd01f4209b20dba9890b74d.png"></p>    <p>swiper圆点的实现原理</p>    <pre>  <code class="language-javascript">// 圆点的父元素,用来控制圆点间的间距  .wx-swiper-dot {      width: 30rpx;      // 圆点,可以通过font-size修改圆点的大小,color修改圆点的颜色      &:before {          width: 100%;          display: inline-block;          font-size: 56rpx;          content: '圆点编码';      }      // active状态的圆点      &.wx-swiper-dot-active {          &:before {              color: #ffc81f;          }      }  }</code></pre>    <p>小程序WXSS的font-face的url不接受路径作为参数</p>    <p>可以将字体文件转换为base64,然后引用。</p>    <p><img src="https://simg.open-open.com/show/e03e6b20464313b10a1ea63963f904d6.png"></p>    <p>font-face接受base64,不接受url</p>    <p>同样,如果想要使用iconfont,也可以使用类似的方案,将iconfont字体文件base64之后再引入。</p>    <p>小程序的margin表现有问题(最新版已经修复)</p>    <p>之前发生margin折叠的时候,会取小的那个值。会导致底部留白等设置失效。</p>    <p>canvas问题</p>    <p>canvas并没有深入研究,目前的发现的问题主要是两个,如下图标记:</p>    <p>✦ 层级问题,canvas总是会盖在其他元素上面。</p>    <p>✦ 支持度不好,在小米5、iPhone5s画图会出现畸形。</p>    <p><img src="https://simg.open-open.com/show/303c0c4796a840db352e80b1c6c6f9d2.png"></p>    <p>canvas绘制饼图有Bug</p>    <p>最后通过CSS3的方式绘制饼图</p>    <pre>  <code class="language-javascript"><template name="pie">      <view class="com-pie">          <!-- 小于50% -->          <view class="percent-1" style="transform: rotate(0.4turn);"></view>          <view class="percent-2"></view>          <!-- 大于50% -->          <view class="percent-1" style="transform: rotate(0.5turn);"></view>          <view class="percent-2" style="opacity:1; transform: rotate(0.3turn);"></view>      </view>  </template>    .com-pie {      position: relative;      z-index: 0;      display: inline-block;      width: 100rpx;      height: 100rpx;      line-height: 100rpx;      border-radius: 50%;      color: #000;      background-color: #ebebeb;      background-image: linear-gradient(to right, transparent 50%, #cccccc 0);      overflow: hidden;      .percent-1,      .percent-2 {          position: absolute;          top: 0;          width: 60%;          height: 100%;          left: 50%;          transform-origin: center left;      }      .percent-1 {          background-color: inherit;          z-index: -2;      }      .percent-2 {          height: 110%;          opacity: 0;          z-index: -1;          background-color: #cccccc;      }      &.selected {          background-color: #ffe9a5;          background-image: linear-gradient(to right, transparent 50%, #ffc81f 0);          .percent-2 {              background-color: #ffc81f;          }      }  }</code></pre>    <p>微信小程序的rpx会出现精度问题</p>    <p>设置margin-left/margin-right负值,可能导致页面能够左右晃动。 <strong>猜测</strong> 是rpx导致的精度问题。</p>    <p>rpx本质上会转换为px,在不同宽度的设备上,实际的rpx值会转换为带小数的px值,四舍五入可能出现问题,之前使用rem布局的时候在QQ浏览器遇到过类似的问题。</p>    <p><img src="https://simg.open-open.com/show/0c9b14560c62a8f9dabbd43343177d24.png"></p>    <p>rpx精度问题</p>    <p>wx.request表现不合理,并且携带特殊字符会报错</p>    <p>✦ 请求返回404错误,也会触发success回调。</p>    <p>不要想当然的认为会触发fail回调,判断一个请求成功或失败,请使用wx.request返回的状态来判断。只有不符合规范的请求,才会触发fail。</p>    <p><img src="https://simg.open-open.com/show/f2b052cdfd3dc26b1eef0955991db9ed.png"></p>    <p>wx.request回调</p>    <p>✦ 请求的数据中,如果有特殊字符(比如\u2820),会报错。</p>    <p>只会在真机上出现,开发者工具没毛病。估计会有更多的特殊字符会导致这个问题。</p>    <p><img src="https://simg.open-open.com/show/f2bb61a334e0372a2708237da84d6d8d.png"></p>    <p>特殊字符导致wx.request挂掉</p>    <p>开发者工具,切换页面的时候,有时候wxml不会同步切换</p>    <p>希望微信什么时候能解决一下。</p>    <p>微信小程序给wxml模板赋值的时候,解构放到前面可能会报错</p>    <p>最新版会遇到这个问题,老版本虽然不会报错,但是在部分真机上会出现问题。</p>    <p>原因未知,遇到这个问题的朋友可以考虑绕过去。</p>    <p><img src="https://simg.open-open.com/show/37fa78bb1464a0bb124d26483ccb21f7.png"></p>    <p>解构赋值导致报错</p>    <p>微信小程序的scroll-view暴露的bindscroll函数并不能实时监听</p>    <p>依赖实时获取滚动位置的功能不能实现。比如滚动时toolbar的动态隐藏和显示。</p>    <p>最新版开发工具不能关掉自动刷新</p>    <p>微信小程序的会默认监听文件变化,然后自动刷新。</p>    <p>但不足的是每次都是全量刷新,而不是模块的热替换,反而会影响开发速度,尤其对于喜欢频繁Command + S的开发者,你会发现你的小程序在不断的刷新。建议关闭。</p>    <p><img src="https://simg.open-open.com/show/ab2c9fa6db4a7addac21929b0863ebc8.png"></p>    <p>建议关闭监听文件变化</p>    <p>但最新版开发者工具,不勾选也会自动刷新。</p>    <p>微信小程序不支持 requestAnimationFrame</p>    <p>微信小程序不支持 requestAnimationFrame ,所以部分性能优化做不了。不支持的原因未知。</p>    <p>Page.onload函数可以接受参数</p>    <p>该参数是有URL决定的,也就是URL携带的参数。</p>    <p>官方文档这块写的有点混淆,特意拿出来说一下。举个例子:url中传递的时候 id=1 ,那么 option.id=1 ,而不是什么 option.query 。</p>    <p><img src="https://simg.open-open.com/show/a3961fda29c8b70a358d4bba016fd166.png"></p>    <p>Page.onload参数文档描述混淆</p>    <p>不要给Page.data传入太多无用数据,会影响渲染效率,在iOS上表现特别明显</p>    <p>尽量传入精简的数据,保持Page.data和view间简单的绑定关系即可。</p>    <p>真机上有概率卡死,目前不确定是代码问题还是小程序的问题。</p>    <p>有遇到类似问题的朋友欢迎指出。</p>    <h2>总结说点啥?</h2>    <p>本文主要围绕微信小程序的基础知识、如何设计微信小程序、开发过程中遇到的问题三个方面介绍。</p>    <p>微信小程序的基础知识主要包括:</p>    <p>✦ 两种配置文件 && 两个核心函数</p>    <p>✦ WXML模板语法,页面渲染</p>    <p>✦ 页面间的跳转</p>    <p>✦ 交互事件</p>    <p>✦ 官方组件和官方API</p>    <p>如何设计微信小程序的内容主要包括:</p>    <p>✦ 构建系统 && 目录结构</p>    <p>✦ 引入Redux进行数据集中管理</p>    <p>✦ 简单的组件化解决方案</p>    <p>最后还介绍开发过程中遇到的难点 以及 微信小程序的大小坑。</p>    <p>微信小程序本身并不复杂,开发过程却比较艰辛,尤其是第一次在真机上运行的时候,觉得这个世界恶意满满。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/4433d46e6235#comments</p>    <p> </p>