专业 靠谱 的软件外包伙伴

您的位置:首页 > 新闻动态 > 微信小程序开发外包全面实战总结

微信小程序开发外包全面实战总结

2017-02-18 10:30:28

本文主要分析如下两款微信小程序开发的经历总结,分别是好奇心日历(每天一条辞典+一个小投票)和好奇心日报(轻量版),下面是程序截图:

图片描述

本文将结合具体的实战经验,主要介绍微信小程序的基础知识、开发中遇到的难点、项目的架构设计、很好实践以及踩过的坑。

值得再次说明的是:微信小程序的内容部分是Hybrid模式,并非原生,所以性能并不好,绑定的tap事件也有明显的延迟。

图片描述

每一个由边框围起来的部分,都是一个最小粒度的原生view

如上图所示,每一个由边框围起来的部分,都是一个最小粒度的原生view,可以看出,整个微信小程序的内容部分,就是一个原生view。

小程序有哪些基础知识?

一个完整的微信小程序是由一个App实例和多个Page实例构成,其中App实例表示该小程序应用,多个Page表示该小程序的多个页面。

此外,微信小程序并没有提供自定义组件的方式,这就导致微信小程序在开发较复杂应用时,可能会比较艰难。

微信小程序本身很简单,和一个模板语言的难度几乎相当,翻翻官方教程就可以开始动手搞。

我也建议大家先动起来,然后再细致啃啃官方文档。由于微信官方文档仍在不断大幅更新中,所以务必查看最新官方文档。

微信小程序的基础知识主要分为以下几个部分:

  • 两种配置文件 && 两个核心函数;
  • WXML模板语法,页面渲染;
  • 页面间的跳转;
  • 交互事件;
  • 官方组件和官方API;

后文会就每个部分简单介绍介绍……

两种配置文件 && 两个核心函数

1.app.json 应用的全局配置文件

app.json是针对微信小程序的全局配置,主要包含以下几个配置:

  • pages:页面路径的数组,表示小程序要加载的所有页面,其中数组第一项代表小程序的初始页面。
  • window:微信原生功能,定制化不强。可设置小程序的状态栏、导航条、标题以及窗口背景色。
  • tabBar:微信原生功能,定制化不强。适用于常规的Tab应用,Tab栏可置于顶部或底部;tabBar是一个数组,仅支持2-5个tab。
  • networkTimeout:配置小程序网络请求的超时时间。
  • debug:调试模式开关,开发模式下建议开启,正式发布别忘了关闭。

图片描述

app.json配置的主要区域

2.page.json 页面的全局配置文件

除了上面提到的全局配置,每个页面还可以单独配置page.json,page.json会覆盖app.json中的配置,并只对当前页面生效。

page.json只能对window配置,有两个比较有用的配置项分别是:

  • enablePullDownRefresh:是否开启下拉刷新;
  • disableScroll:只能在page.json配置,禁止页面上下滚动,猜测可以实现完美滑屏滑动(未验证)。

3.App() 小程序注册入口,全局唯一

App()用来注册一个小程序,全局只有一个,全局的数据也可以放到这里面来操作。

// 注册微信小程序,全局只有一个
let appConfig = {
    // 小程序生命周期的各个阶段
    onLaunch: function(){},
    onShow: function(){},
    onHide: function(){},
    onError: function(){},

    // 自定义函数或者属性
    ...
};
App(appConfig);

// 在别的地方可以获取这个全局唯一的小程序实例
const app = getApp();

小程序并没有提供销毁的方式,所以只有当小程序进入后台一定时间,或者系统资源占用过高的时候,才会被真正的销毁。

4.Page() 页面注册入口

Page()用来注册一个页面,维护该页面的生命周期以及数据。

// 注册微信小程序,全局只有一个
let pageConfig = {
    data: {},
    // 页面生命周期的各个阶段
    onLoad: function(){},
    onShow: function(){},
    onReady: function(){},
    onHide: function(){},
    onUnload: function(){},
    onPullDownRefresh: function(){},
    onReachBottom: function(){},
    onShareAppMessage: function(){},

    // 自定义函数或者属性
    ...
};
Page(pageConfig);

// 获取页面堆栈,表示历史访问过的页面,最后一个元素为当前页面
const page = getCurrentPages();

关于各个生命周期的细节以及流程,参考下图,可以细细品味:

图片描述

app.json 和 page.json 维护了应用和页面的配置属性。App() 和 Page() 维护了应用和页面的各个生命周期以及数据。

那么,APP 和 Page 如何将数据传递到页面呢?页面又是如何渲染呢?

WXML模板语法,页面渲染

小程序虽然是Hybrid模式,但并不使用HTML渲染,而是全部通过自定义标签来渲染页面。这样做的好处我不清楚,但问题却不少:不能跨浏览器、富文本解析困难,iframe视频不支持,没办法外链跳转。

和所有的模板语言一样,WXML支持数据绑定、条件渲染、循环、模块化等功能。

1.数据绑定

在 WXML 中可以使用{{}}将 Page 的变量或者表达式包裹起来,实现数据绑定,举个例子:

// 将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>

2.条件渲染

条件渲染,适合根据数据输出不同状态的 WXML,举个例子:

<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>

3.循环渲染

循环渲染,适合遍历数据输出多段 WXML,举个例子:

// 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>

wx:key 有利于提升重新渲染时的效率,建议添加

4.模块化

WXML的模块化,可以让我们复用一些WXML片段,还挺重要的,举个例子:

// 引入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>

数据和页面的状态是一一对应的,微信小程序中,设计一份好的数据结构,对于Page和页面的代码都有很大的帮助。

微信小程序并不支持a标签,那么多个页面之间如何跳转呢?

页面间的跳转

小程序以栈的形式维护了历史访问的所有页面,并提供了多种页面间的跳转方式;结合前文提到的App()和Page()的各个生命周期,不同的跳转方式和不同的生命周期关联,如下图:

图片描述

举个例子,Tab 切换对应的生命周期(以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面为例)。

图片描述

好了,APP和Page负责维护小程序的生命周期和数据,模板负责接受数据完成页面渲染,页面间的跳转负责将多个页面贯穿起来,那么,如何发生交互呢?接下来我们简单说一下事件。

交互事件

事件绑定

// 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)
  }
})

接下来,另一个问题是:tapName() 如何接受自定义参数呢?

事件传参

传递自定义参数主要有两种方式:

第一种:将参数绑定到wxml标签上,然后通过event.target.dataset获取。

第二种:直接使用Page.data或其他数据。

到目前为止,一个完整的小程序框架已经实现:

  • 小程序只有逻辑和视图两部分,而且不提供组件化解决方案;
  • 逻辑主要包含四个东西:两个配置文件 && 两个核心函数;
  • 视图很简单,模板语法稍微有点不完善;
  • 逻辑层的数据绑定到视图层是由小程序框架自动支持,数据变化,视图自动变化;
  • 视图层到逻辑层的,主要通过事件的方式来实现;
  • 视图之间的跳转,小程序也提供了它自己的方式,并不支持a标签。

框架有了,小程序还提供了官方组件以便快速开发,提供了API以增强应用能力。

这块就不具体介绍了,也需要注意小程序的官方文档还在大规模的更新中,务必查看最新版。 
官方组件:https://mp.weixin.qq.com/debug/wxadoc/dev/component/?t=20161222。 
官方API:https://mp.weixin.qq.com/debug/wxadoc/dev/api/?t=20161222

微信小程序的基础知识就是以上的内容,并不复杂,边查边写都可以。

接下来会介绍更进阶一些的内容,内容主要结合好奇心日报这个小程序项目,先看效果:

图片描述

好奇心日报实际效果图

如何设计微信小程序?

构建系统 && 目录结构

构建系统

由于微信小程序本身对工程化几乎没有任何的支持,所以动手搭建一份:wxapp-redux-starter

使用gulp进行编译构建,主要功能包括:

  • 集成了Redux,数据管理更方便;
  • 开发过程中,使用XML取代WXML,对开发工具更友好;
  • 开发过程中,使用less取代WXSS,功能更强大;
  • 引入es-promise,以便可以创建并使用Promise;
  • 添加promisify工具函数,可以便捷的将官方Api转换成Pormise模式;
  • 引入normalizr,可以将数据扁平化,更方便进行数据管理;
  • 引入babel自动进行ES2015特性转换;
  • 对WXML/WXSS/JS/img压缩,相对开发者工具提供的压缩,会减小一丢丢体积。

目录结构设计

按照pages、components、redux、vendors/libs、images几个核心部分拆分,直接上目录。

图片描述

小程序工程目录

  • dist目录:构建输出的文件存放到这个目录。
  • src目录:开发模式的文件,包括app、页面、组件、图片等静态资源、辅助函数库、Redux数据管理器、第三方工具库。
  • gulpfile.js:不用多说,gulp构建任务的入口文件。
  • package.json:不用多说,管理者构建任务的依赖。
  • thirdPlugins:由于小程序并不支持直接使用npm,我们可以自主拉取构建,然后拷贝到vendors里,有时候需要简单修改。

构建系统会将src目录下的代码,编译处理后输出到dist目录,小程序开发工具只需要引入dist目录即可。

有了构建工具,代码开发起来更舒心,但很快就遇到另外一个糟心的问题,那就是如何管理散布在各处的数据?

要知道,微信小程序并没有提供组件化方案,所以把组件写成无状态组件特别适合,但是页面管理太多数据很凌乱,有什么办法可以将数据集中管理呢?

引入Redux进行数据集中管理

关于Redux相关的内容,之前有三篇博客详细介绍,有兴趣的建议先移步: 
Redux整体介绍:Redux 入门教程,应用的状态管理器 
对State进行横向和纵向拆分设计:State设计,Redux 开发第一步 
Reducer的很好实践:Reducer 很好实践,Redux 开发最重要的部分

这儿就简单介绍一下,如何在微信小程序中引入Redux 以及 如何将微信小程序和Redux连接起来。

引入Redux

直接在 thirdPlugins目录运行 yarn add redux / npm install redux,等redux安装好了之后,将 dist目录 的 redux.js/redux.min.js 拷贝到vendors目录中。

需要进行简单的修改才能使用,将压缩版208行代码 (i) 改成 (i || {})即可。

图片描述

简单修改,Redux就可以正常使用

连接微信小程序和Redux

将Redux和微信小程序连接起来才会真的有用处。找了个现成的方案charleyw/wechat-weapp-redux,可以直接使用。

一个完整的Redux方案如下,包括:将Store注入到App中、将state的数据和reducer的方法映射到Page中。一旦state发生变化,Page.data也会更新,进而触发页面的重新渲染。

// 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也一										
  上一篇   [返回首页] [打印] [返回上页]   下一篇